Home | History | Annotate | Download | only in keyboard
      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 
     17 package com.android.inputmethod.keyboard;
     18 
     19 import android.content.Context;
     20 import android.content.SharedPreferences;
     21 import android.content.res.Configuration;
     22 import android.content.res.Resources;
     23 import android.text.TextUtils;
     24 import android.util.DisplayMetrics;
     25 import android.util.Log;
     26 import android.view.ContextThemeWrapper;
     27 import android.view.InflateException;
     28 import android.view.LayoutInflater;
     29 import android.view.View;
     30 import android.view.inputmethod.EditorInfo;
     31 
     32 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
     33 import com.android.inputmethod.keyboard.internal.ModifierKeyState;
     34 import com.android.inputmethod.keyboard.internal.ShiftKeyState;
     35 import com.android.inputmethod.latin.InputView;
     36 import com.android.inputmethod.latin.LatinIME;
     37 import com.android.inputmethod.latin.LatinImeLogger;
     38 import com.android.inputmethod.latin.LocaleUtils;
     39 import com.android.inputmethod.latin.R;
     40 import com.android.inputmethod.latin.Settings;
     41 import com.android.inputmethod.latin.SubtypeSwitcher;
     42 import com.android.inputmethod.latin.Utils;
     43 
     44 import java.lang.ref.SoftReference;
     45 import java.util.HashMap;
     46 import java.util.Locale;
     47 
     48 public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceChangeListener {
     49     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
     50     private static final boolean DEBUG_CACHE = LatinImeLogger.sDBG;
     51     public static final boolean DEBUG_STATE = false;
     52 
     53     public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
     54     private static final int[] KEYBOARD_THEMES = {
     55         R.style.KeyboardTheme,
     56         R.style.KeyboardTheme_HighContrast,
     57         R.style.KeyboardTheme_Stone,
     58         R.style.KeyboardTheme_Stone_Bold,
     59         R.style.KeyboardTheme_Gingerbread,
     60         R.style.KeyboardTheme_IceCreamSandwich,
     61     };
     62 
     63     private SubtypeSwitcher mSubtypeSwitcher;
     64     private SharedPreferences mPrefs;
     65 
     66     private InputView mCurrentInputView;
     67     private LatinKeyboardView mKeyboardView;
     68     private LatinIME mInputMethodService;
     69     private String mPackageName;
     70     private Resources mResources;
     71 
     72     // TODO: Combine these key state objects with auto mode switch state.
     73     private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
     74     private ModifierKeyState mSymbolKeyState = new ModifierKeyState("Symbol");
     75 
     76     private KeyboardId mMainKeyboardId;
     77     private KeyboardId mSymbolsKeyboardId;
     78     private KeyboardId mSymbolsShiftedKeyboardId;
     79 
     80     private KeyboardId mCurrentId;
     81     private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
     82             new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
     83 
     84     private KeyboardLayoutState mSavedKeyboardState = new KeyboardLayoutState();
     85 
     86     /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
     87      * what user actually typed. */
     88     private boolean mIsAutoCorrectionActive;
     89 
     90     // TODO: Encapsulate these state handling to separate class and combine with ShiftKeyState
     91     // and ModifierKeyState.
     92     private static final int SWITCH_STATE_ALPHA = 0;
     93     private static final int SWITCH_STATE_SYMBOL_BEGIN = 1;
     94     private static final int SWITCH_STATE_SYMBOL = 2;
     95     // The following states are used only on the distinct multi-touch panel devices.
     96     private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3;
     97     private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4;
     98     private static final int SWITCH_STATE_CHORDING_ALPHA = 5;
     99     private static final int SWITCH_STATE_CHORDING_SYMBOL = 6;
    100     private int mSwitchState = SWITCH_STATE_ALPHA;
    101 
    102     private static String mLayoutSwitchBackSymbols;
    103 
    104     private int mThemeIndex = -1;
    105     private Context mThemeContext;
    106 
    107     private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
    108 
    109     private class KeyboardLayoutState {
    110         private boolean mIsValid;
    111         private boolean mIsAlphabetMode;
    112         private boolean mIsShiftLocked;
    113         private boolean mIsShifted;
    114 
    115         public void save() {
    116             if (mCurrentId == null) {
    117                 return;
    118             }
    119             mIsAlphabetMode = isAlphabetMode();
    120             if (mIsAlphabetMode) {
    121                 mIsShiftLocked = isShiftLocked();
    122                 mIsShifted = !mIsShiftLocked && isShiftedOrShiftLocked();
    123             } else {
    124                 mIsShiftLocked = false;
    125                 mIsShifted = mCurrentId.equals(mSymbolsShiftedKeyboardId);
    126             }
    127             mIsValid = true;
    128         }
    129 
    130         public KeyboardId getKeyboardId() {
    131             if (!mIsValid) return mMainKeyboardId;
    132 
    133             if (mIsAlphabetMode) {
    134                 return mMainKeyboardId;
    135             } else {
    136                 return mIsShifted ? mSymbolsShiftedKeyboardId : mSymbolsKeyboardId;
    137             }
    138         }
    139 
    140         public void restore() {
    141             if (!mIsValid) return;
    142             mIsValid = false;
    143 
    144             if (mIsAlphabetMode) {
    145                 final boolean isAlphabetMode = isAlphabetMode();
    146                 final boolean isShiftLocked = isAlphabetMode && isShiftLocked();
    147                 final boolean isShifted = !isShiftLocked && isShiftedOrShiftLocked();
    148                 if (mIsShiftLocked != isShiftLocked) {
    149                     toggleCapsLock();
    150                 } else if (mIsShifted != isShifted) {
    151                     onPressShift(false);
    152                     onReleaseShift(false);
    153                 }
    154             }
    155         }
    156     }
    157 
    158     public static KeyboardSwitcher getInstance() {
    159         return sInstance;
    160     }
    161 
    162     private KeyboardSwitcher() {
    163         // Intentional empty constructor for singleton.
    164     }
    165 
    166     public static void init(LatinIME ims, SharedPreferences prefs) {
    167         sInstance.initInternal(ims, prefs);
    168     }
    169 
    170     private void initInternal(LatinIME ims, SharedPreferences prefs) {
    171         mInputMethodService = ims;
    172         mPackageName = ims.getPackageName();
    173         mResources = ims.getResources();
    174         mPrefs = prefs;
    175         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
    176         setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
    177         prefs.registerOnSharedPreferenceChangeListener(this);
    178     }
    179 
    180     private static int getKeyboardThemeIndex(Context context, SharedPreferences prefs) {
    181         final String defaultThemeId = context.getString(R.string.config_default_keyboard_theme_id);
    182         final String themeId = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultThemeId);
    183         try {
    184             final int themeIndex = Integer.valueOf(themeId);
    185             if (themeIndex >= 0 && themeIndex < KEYBOARD_THEMES.length)
    186                 return themeIndex;
    187         } catch (NumberFormatException e) {
    188             // Format error, keyboard theme is default to 0.
    189         }
    190         Log.w(TAG, "Illegal keyboard theme in preference: " + themeId + ", default to 0");
    191         return 0;
    192     }
    193 
    194     private void setContextThemeWrapper(Context context, int themeIndex) {
    195         if (mThemeIndex != themeIndex) {
    196             mThemeIndex = themeIndex;
    197             mThemeContext = new ContextThemeWrapper(context, KEYBOARD_THEMES[themeIndex]);
    198             mKeyboardCache.clear();
    199         }
    200     }
    201 
    202     public void loadKeyboard(EditorInfo editorInfo, Settings.Values settingsValues) {
    203         try {
    204             mMainKeyboardId = getKeyboardId(editorInfo, false, false, settingsValues);
    205             mSymbolsKeyboardId = getKeyboardId(editorInfo, true, false, settingsValues);
    206             mSymbolsShiftedKeyboardId = getKeyboardId(editorInfo, true, true, settingsValues);
    207             mLayoutSwitchBackSymbols = mResources.getString(R.string.layout_switch_back_symbols);
    208             setKeyboard(getKeyboard(mSavedKeyboardState.getKeyboardId()));
    209             mSavedKeyboardState.restore();
    210         } catch (RuntimeException e) {
    211             Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
    212             LatinImeLogger.logOnException(mMainKeyboardId.toString(), e);
    213         }
    214     }
    215 
    216     public void saveKeyboardState() {
    217         mSavedKeyboardState.save();
    218     }
    219 
    220     public void onFinishInputView() {
    221         mIsAutoCorrectionActive = false;
    222     }
    223 
    224     public void onHideWindow() {
    225         mIsAutoCorrectionActive = false;
    226     }
    227 
    228     private void setKeyboard(final Keyboard keyboard) {
    229         final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
    230         mKeyboardView.setKeyboard(keyboard);
    231         mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
    232         mCurrentId = keyboard.mId;
    233         mSwitchState = getSwitchState(mCurrentId);
    234         updateShiftLockState(keyboard);
    235         mKeyboardView.setKeyPreviewPopupEnabled(
    236                 Settings.Values.isKeyPreviewPopupEnabled(mPrefs, mResources),
    237                 Settings.Values.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
    238         final boolean localeChanged = (oldKeyboard == null)
    239                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
    240         mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
    241         updateShiftState();
    242     }
    243 
    244     private int getSwitchState(KeyboardId id) {
    245         return id.equals(mMainKeyboardId) ? SWITCH_STATE_ALPHA : SWITCH_STATE_SYMBOL_BEGIN;
    246     }
    247 
    248     private void updateShiftLockState(Keyboard keyboard) {
    249         if (mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
    250             // Symbol keyboard may have an ALT key that has a caps lock style indicator (a.k.a.
    251             // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked()
    252             // that takes care of the current keyboard having such ALT key or not.
    253             keyboard.setShiftLocked(keyboard.hasShiftLockKey());
    254         } else if (mCurrentId.equals(mSymbolsKeyboardId)) {
    255             // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
    256             // indicator, we need to call setShiftLocked(false).
    257             keyboard.setShiftLocked(false);
    258         }
    259     }
    260 
    261     private LatinKeyboard getKeyboard(KeyboardId id) {
    262         final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
    263         LatinKeyboard keyboard = (ref == null) ? null : ref.get();
    264         if (keyboard == null) {
    265             final Locale savedLocale = LocaleUtils.setSystemLocale(mResources, id.mLocale);
    266             try {
    267                 final LatinKeyboard.Builder builder = new LatinKeyboard.Builder(mThemeContext);
    268                 builder.load(id);
    269                 builder.setTouchPositionCorrectionEnabled(
    270                         mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
    271                                 LatinIME.SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION));
    272                 keyboard = builder.build();
    273             } finally {
    274                 LocaleUtils.setSystemLocale(mResources, savedLocale);
    275             }
    276             mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard));
    277 
    278             if (DEBUG_CACHE) {
    279                 Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": "
    280                         + ((ref == null) ? "LOAD" : "GCed") + " id=" + id
    281                         + " theme=" + Keyboard.themeName(keyboard.mThemeId));
    282             }
    283         } else if (DEBUG_CACHE) {
    284             Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT  id=" + id
    285                     + " theme=" + Keyboard.themeName(keyboard.mThemeId));
    286         }
    287 
    288         keyboard.onAutoCorrectionStateChanged(mIsAutoCorrectionActive);
    289         keyboard.setShiftLocked(false);
    290         keyboard.setShifted(false);
    291         // If the cached keyboard had been switched to another keyboard while the language was
    292         // displayed on its spacebar, it might have had arbitrary text fade factor. In such case,
    293         // we should reset the text fade factor. It is also applicable to shortcut key.
    294         keyboard.setSpacebarTextFadeFactor(0.0f, null);
    295         keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady(), null);
    296         return keyboard;
    297     }
    298 
    299     private KeyboardId getKeyboardId(EditorInfo editorInfo, final boolean isSymbols,
    300             final boolean isShift, Settings.Values settingsValues) {
    301         final int mode = Utils.getKeyboardMode(editorInfo);
    302         final int xmlId;
    303         switch (mode) {
    304         case KeyboardId.MODE_PHONE:
    305             xmlId = (isSymbols && isShift) ? R.xml.kbd_phone_shift : R.xml.kbd_phone;
    306             break;
    307         case KeyboardId.MODE_NUMBER:
    308             xmlId = R.xml.kbd_number;
    309             break;
    310         default:
    311             if (isSymbols) {
    312                 xmlId = isShift ? R.xml.kbd_symbols_shift : R.xml.kbd_symbols;
    313             } else {
    314                 xmlId = R.xml.kbd_qwerty;
    315             }
    316             break;
    317         }
    318 
    319         final boolean settingsKeyEnabled = settingsValues.isSettingsKeyEnabled();
    320         @SuppressWarnings("deprecation")
    321         final boolean noMicrophone = Utils.inPrivateImeOptions(
    322                 mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo)
    323                 || Utils.inPrivateImeOptions(
    324                         null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo);
    325         final boolean voiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo)
    326                 && !noMicrophone;
    327         final boolean voiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
    328         final boolean noSettingsKey = Utils.inPrivateImeOptions(
    329                 mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, editorInfo);
    330         final boolean hasSettingsKey = settingsKeyEnabled && !noSettingsKey;
    331         final int f2KeyMode = getF2KeyMode(settingsKeyEnabled, noSettingsKey);
    332         final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != voiceKeyOnMain);
    333         final boolean forceAscii = Utils.inPrivateImeOptions(
    334                 mPackageName, LatinIME.IME_OPTION_FORCE_ASCII, editorInfo);
    335         final boolean asciiCapable = mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
    336                 LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE);
    337         final Locale locale = (forceAscii && !asciiCapable)
    338                 ? Locale.US : mSubtypeSwitcher.getInputLocale();
    339         final Configuration conf = mResources.getConfiguration();
    340         final DisplayMetrics dm = mResources.getDisplayMetrics();
    341 
    342         return new KeyboardId(
    343                 mResources.getResourceEntryName(xmlId), xmlId, locale, conf.orientation,
    344                 dm.widthPixels, mode, editorInfo, hasSettingsKey, f2KeyMode, noSettingsKey,
    345                 voiceKeyEnabled, hasShortcutKey);
    346     }
    347 
    348     public int getKeyboardMode() {
    349         return mCurrentId != null ? mCurrentId.mMode : KeyboardId.MODE_TEXT;
    350     }
    351 
    352     public boolean isAlphabetMode() {
    353         return mCurrentId != null && mCurrentId.isAlphabetKeyboard();
    354     }
    355 
    356     public boolean isInputViewShown() {
    357         return mCurrentInputView != null && mCurrentInputView.isShown();
    358     }
    359 
    360     public boolean isKeyboardAvailable() {
    361         if (mKeyboardView != null)
    362             return mKeyboardView.getKeyboard() != null;
    363         return false;
    364     }
    365 
    366     public LatinKeyboard getLatinKeyboard() {
    367         if (mKeyboardView != null) {
    368             final Keyboard keyboard = mKeyboardView.getKeyboard();
    369             if (keyboard instanceof LatinKeyboard)
    370                 return (LatinKeyboard)keyboard;
    371         }
    372         return null;
    373     }
    374 
    375     public boolean isShiftedOrShiftLocked() {
    376         LatinKeyboard latinKeyboard = getLatinKeyboard();
    377         if (latinKeyboard != null)
    378             return latinKeyboard.isShiftedOrShiftLocked();
    379         return false;
    380     }
    381 
    382     public boolean isShiftLocked() {
    383         LatinKeyboard latinKeyboard = getLatinKeyboard();
    384         if (latinKeyboard != null)
    385             return latinKeyboard.isShiftLocked();
    386         return false;
    387     }
    388 
    389     private boolean isShiftLockShifted() {
    390         LatinKeyboard latinKeyboard = getLatinKeyboard();
    391         if (latinKeyboard != null)
    392             return latinKeyboard.isShiftLockShifted();
    393         return false;
    394     }
    395 
    396     public boolean isAutomaticTemporaryUpperCase() {
    397         LatinKeyboard latinKeyboard = getLatinKeyboard();
    398         if (latinKeyboard != null)
    399             return latinKeyboard.isAutomaticTemporaryUpperCase();
    400         return false;
    401     }
    402 
    403     public boolean isManualTemporaryUpperCase() {
    404         LatinKeyboard latinKeyboard = getLatinKeyboard();
    405         if (latinKeyboard != null)
    406             return latinKeyboard.isManualTemporaryUpperCase();
    407         return false;
    408     }
    409 
    410     private boolean isManualTemporaryUpperCaseFromAuto() {
    411         LatinKeyboard latinKeyboard = getLatinKeyboard();
    412         if (latinKeyboard != null)
    413             return latinKeyboard.isManualTemporaryUpperCaseFromAuto();
    414         return false;
    415     }
    416 
    417     private void setManualTemporaryUpperCase(boolean shifted) {
    418         LatinKeyboard latinKeyboard = getLatinKeyboard();
    419         if (latinKeyboard != null) {
    420             // On non-distinct multi touch panel device, we should also turn off the shift locked
    421             // state when shift key is pressed to go to normal mode.
    422             // On the other hand, on distinct multi touch panel device, turning off the shift locked
    423             // state with shift key pressing is handled by onReleaseShift().
    424             if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
    425                 latinKeyboard.setShiftLocked(false);
    426             }
    427             if (latinKeyboard.setShifted(shifted)) {
    428                 mKeyboardView.invalidateAllKeys();
    429             }
    430         }
    431     }
    432 
    433     private void setShiftLocked(boolean shiftLocked) {
    434         LatinKeyboard latinKeyboard = getLatinKeyboard();
    435         if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) {
    436             mKeyboardView.invalidateAllKeys();
    437         }
    438     }
    439 
    440     /**
    441      * Toggle keyboard shift state triggered by user touch event.
    442      */
    443     public void toggleShift() {
    444         mInputMethodService.mHandler.cancelUpdateShiftState();
    445         if (DEBUG_STATE)
    446             Log.d(TAG, "toggleShift:"
    447                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    448                     + " shiftKeyState=" + mShiftKeyState);
    449         if (isAlphabetMode()) {
    450             setManualTemporaryUpperCase(!isShiftedOrShiftLocked());
    451         } else {
    452             toggleShiftInSymbol();
    453         }
    454     }
    455 
    456     public void toggleCapsLock() {
    457         mInputMethodService.mHandler.cancelUpdateShiftState();
    458         if (DEBUG_STATE)
    459             Log.d(TAG, "toggleCapsLock:"
    460                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    461                     + " shiftKeyState=" + mShiftKeyState);
    462         if (isAlphabetMode()) {
    463             if (isShiftLocked()) {
    464                 // Shift key is long pressed while caps lock state, we will toggle back to normal
    465                 // state. And mark as if shift key is released.
    466                 setShiftLocked(false);
    467                 mShiftKeyState.onRelease();
    468             } else {
    469                 setShiftLocked(true);
    470             }
    471         }
    472     }
    473 
    474     private void setAutomaticTemporaryUpperCase() {
    475         if (mKeyboardView == null) return;
    476         final Keyboard keyboard = mKeyboardView.getKeyboard();
    477         if (keyboard == null) return;
    478         keyboard.setAutomaticTemporaryUpperCase();
    479         mKeyboardView.invalidateAllKeys();
    480     }
    481 
    482     /**
    483      * Update keyboard shift state triggered by connected EditText status change.
    484      */
    485     public void updateShiftState() {
    486         final ShiftKeyState shiftKeyState = mShiftKeyState;
    487         if (DEBUG_STATE)
    488             Log.d(TAG, "updateShiftState:"
    489                     + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()
    490                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    491                     + " shiftKeyState=" + shiftKeyState
    492                     + " isAlphabetMode=" + isAlphabetMode()
    493                     + " isShiftLocked=" + isShiftLocked());
    494         if (isAlphabetMode()) {
    495             if (!isShiftLocked() && !shiftKeyState.isIgnoring()) {
    496                 if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
    497                     // Only when shift key is releasing, automatic temporary upper case will be set.
    498                     setAutomaticTemporaryUpperCase();
    499                 } else {
    500                     setManualTemporaryUpperCase(shiftKeyState.isMomentary());
    501                 }
    502             }
    503         } else {
    504             // In symbol keyboard mode, we should clear shift key state because only alphabet
    505             // keyboard has shift key.
    506             shiftKeyState.onRelease();
    507         }
    508     }
    509 
    510     public void changeKeyboardMode() {
    511         if (DEBUG_STATE)
    512             Log.d(TAG, "changeKeyboardMode:"
    513                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    514                     + " shiftKeyState=" + mShiftKeyState);
    515         toggleKeyboardMode();
    516         if (isShiftLocked() && isAlphabetMode())
    517             setShiftLocked(true);
    518         updateShiftState();
    519     }
    520 
    521     public void onPressShift(boolean withSliding) {
    522         if (!isKeyboardAvailable())
    523             return;
    524         ShiftKeyState shiftKeyState = mShiftKeyState;
    525         if (DEBUG_STATE)
    526             Log.d(TAG, "onPressShift:"
    527                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    528                     + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding);
    529         if (isAlphabetMode()) {
    530             if (isShiftLocked()) {
    531                 // Shift key is pressed while caps lock state, we will treat this state as shifted
    532                 // caps lock state and mark as if shift key pressed while normal state.
    533                 shiftKeyState.onPress();
    534                 setManualTemporaryUpperCase(true);
    535             } else if (isAutomaticTemporaryUpperCase()) {
    536                 // Shift key is pressed while automatic temporary upper case, we have to move to
    537                 // manual temporary upper case.
    538                 shiftKeyState.onPress();
    539                 setManualTemporaryUpperCase(true);
    540             } else if (isShiftedOrShiftLocked()) {
    541                 // In manual upper case state, we just record shift key has been pressing while
    542                 // shifted state.
    543                 shiftKeyState.onPressOnShifted();
    544             } else {
    545                 // In base layout, chording or manual temporary upper case mode is started.
    546                 shiftKeyState.onPress();
    547                 toggleShift();
    548             }
    549         } else {
    550             // In symbol mode, just toggle symbol and symbol more keyboard.
    551             shiftKeyState.onPress();
    552             toggleShift();
    553             mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
    554         }
    555     }
    556 
    557     public void onReleaseShift(boolean withSliding) {
    558         if (!isKeyboardAvailable())
    559             return;
    560         ShiftKeyState shiftKeyState = mShiftKeyState;
    561         if (DEBUG_STATE)
    562             Log.d(TAG, "onReleaseShift:"
    563                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    564                     + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding);
    565         if (isAlphabetMode()) {
    566             if (shiftKeyState.isMomentary()) {
    567                 // After chording input while normal state.
    568                 toggleShift();
    569             } else if (isShiftLocked() && !isShiftLockShifted() && shiftKeyState.isPressing()
    570                     && !withSliding) {
    571                 // Shift has been long pressed, ignore this release.
    572             } else if (isShiftLocked() && !shiftKeyState.isIgnoring() && !withSliding) {
    573                 // Shift has been pressed without chording while caps lock state.
    574                 toggleCapsLock();
    575                 // To be able to turn off caps lock by "double tap" on shift key, we should ignore
    576                 // the second tap of the "double tap" from now for a while because we just have
    577                 // already turned off caps lock above.
    578                 mKeyboardView.startIgnoringDoubleTap();
    579             } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()
    580                     && !withSliding) {
    581                 // Shift has been pressed without chording while shifted state.
    582                 toggleShift();
    583             } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing()
    584                     && !withSliding) {
    585                 // Shift has been pressed without chording while manual temporary upper case
    586                 // transited from automatic temporary upper case.
    587                 toggleShift();
    588             }
    589         } else {
    590             // In symbol mode, snap back to the previous keyboard mode if the user chords the shift
    591             // key and another key, then releases the shift key.
    592             if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) {
    593                 toggleShift();
    594             }
    595         }
    596         shiftKeyState.onRelease();
    597     }
    598 
    599     public void onPressSymbol() {
    600         if (DEBUG_STATE)
    601             Log.d(TAG, "onPressSymbol:"
    602                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    603                     + " symbolKeyState=" + mSymbolKeyState);
    604         changeKeyboardMode();
    605         mSymbolKeyState.onPress();
    606         mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
    607     }
    608 
    609     public void onReleaseSymbol() {
    610         if (DEBUG_STATE)
    611             Log.d(TAG, "onReleaseSymbol:"
    612                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    613                     + " symbolKeyState=" + mSymbolKeyState);
    614         // Snap back to the previous keyboard mode if the user chords the mode change key and
    615         // another key, then releases the mode change key.
    616         if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) {
    617             changeKeyboardMode();
    618         }
    619         mSymbolKeyState.onRelease();
    620     }
    621 
    622     public void onOtherKeyPressed() {
    623         if (DEBUG_STATE)
    624             Log.d(TAG, "onOtherKeyPressed:"
    625                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
    626                     + " shiftKeyState=" + mShiftKeyState
    627                     + " symbolKeyState=" + mSymbolKeyState);
    628         mShiftKeyState.onOtherKeyPressed();
    629         mSymbolKeyState.onOtherKeyPressed();
    630     }
    631 
    632     public void onCancelInput() {
    633         // Snap back to the previous keyboard mode if the user cancels sliding input.
    634         if (getPointerCount() == 1) {
    635             if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) {
    636                 changeKeyboardMode();
    637             } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) {
    638                 toggleShift();
    639             }
    640         }
    641     }
    642 
    643     private void toggleShiftInSymbol() {
    644         if (isAlphabetMode())
    645             return;
    646         final LatinKeyboard keyboard;
    647         if (mCurrentId.equals(mSymbolsKeyboardId)
    648                 || !mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
    649             keyboard = getKeyboard(mSymbolsShiftedKeyboardId);
    650         } else {
    651             keyboard = getKeyboard(mSymbolsKeyboardId);
    652         }
    653         setKeyboard(keyboard);
    654     }
    655 
    656     public boolean isInMomentarySwitchState() {
    657         return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
    658                 || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
    659     }
    660 
    661     public boolean isVibrateAndSoundFeedbackRequired() {
    662         return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
    663     }
    664 
    665     private int getPointerCount() {
    666         return mKeyboardView == null ? 0 : mKeyboardView.getPointerCount();
    667     }
    668 
    669     private void toggleKeyboardMode() {
    670         if (mCurrentId.equals(mMainKeyboardId)) {
    671             setKeyboard(getKeyboard(mSymbolsKeyboardId));
    672         } else {
    673             setKeyboard(getKeyboard(mMainKeyboardId));
    674         }
    675     }
    676 
    677     public boolean hasDistinctMultitouch() {
    678         return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
    679     }
    680 
    681     private static boolean isSpaceCharacter(int c) {
    682         return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER;
    683     }
    684 
    685     private static boolean isLayoutSwitchBackCharacter(int c) {
    686         if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false;
    687         if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true;
    688         return false;
    689     }
    690 
    691     /**
    692      * Updates state machine to figure out when to automatically snap back to the previous mode.
    693      */
    694     public void onKey(int code) {
    695         if (DEBUG_STATE)
    696             Log.d(TAG, "onKey: code=" + code + " switchState=" + mSwitchState
    697                     + " pointers=" + getPointerCount());
    698         switch (mSwitchState) {
    699         case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
    700             // Only distinct multi touch devices can be in this state.
    701             // On non-distinct multi touch devices, mode change key is handled by
    702             // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and
    703             // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts
    704             // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from
    705             // {@link #SWITCH_STATE_MOMENTARY}.
    706             if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
    707                 // Detected only the mode change key has been pressed, and then released.
    708                 if (mCurrentId.equals(mMainKeyboardId)) {
    709                     mSwitchState = SWITCH_STATE_ALPHA;
    710                 } else {
    711                     mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
    712                 }
    713             } else if (getPointerCount() == 1) {
    714                 // Snap back to the previous keyboard mode if the user pressed the mode change key
    715                 // and slid to other key, then released the finger.
    716                 // If the user cancels the sliding input, snapping back to the previous keyboard
    717                 // mode is handled by {@link #onCancelInput}.
    718                 changeKeyboardMode();
    719             } else {
    720                 // Chording input is being started. The keyboard mode will be snapped back to the
    721                 // previous mode in {@link onReleaseSymbol} when the mode change key is released.
    722                 mSwitchState = SWITCH_STATE_CHORDING_ALPHA;
    723             }
    724             break;
    725         case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
    726             if (code == Keyboard.CODE_SHIFT) {
    727                 // Detected only the shift key has been pressed on symbol layout, and then released.
    728                 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
    729             } else if (getPointerCount() == 1) {
    730                 // Snap back to the previous keyboard mode if the user pressed the shift key on
    731                 // symbol mode and slid to other key, then released the finger.
    732                 toggleShift();
    733                 mSwitchState = SWITCH_STATE_SYMBOL;
    734             } else {
    735                 // Chording input is being started. The keyboard mode will be snapped back to the
    736                 // previous mode in {@link onReleaseShift} when the shift key is released.
    737                 mSwitchState = SWITCH_STATE_CHORDING_SYMBOL;
    738             }
    739             break;
    740         case SWITCH_STATE_SYMBOL_BEGIN:
    741             if (!isSpaceCharacter(code) && code >= 0) {
    742                 mSwitchState = SWITCH_STATE_SYMBOL;
    743             }
    744             // Snap back to alpha keyboard mode immediately if user types a quote character.
    745             if (isLayoutSwitchBackCharacter(code)) {
    746                 changeKeyboardMode();
    747             }
    748             break;
    749         case SWITCH_STATE_SYMBOL:
    750         case SWITCH_STATE_CHORDING_SYMBOL:
    751             // Snap back to alpha keyboard mode if user types one or more non-space/enter
    752             // characters followed by a space/enter or a quote character.
    753             if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) {
    754                 changeKeyboardMode();
    755             }
    756             break;
    757         }
    758     }
    759 
    760     public LatinKeyboardView getKeyboardView() {
    761         return mKeyboardView;
    762     }
    763 
    764     public View onCreateInputView() {
    765         return createInputView(mThemeIndex, true);
    766     }
    767 
    768     private View createInputView(final int newThemeIndex, final boolean forceRecreate) {
    769         if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate)
    770             return mCurrentInputView;
    771 
    772         if (mKeyboardView != null) {
    773             mKeyboardView.closing();
    774         }
    775 
    776         final int oldThemeIndex = mThemeIndex;
    777         Utils.GCUtils.getInstance().reset();
    778         boolean tryGC = true;
    779         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
    780             try {
    781                 setContextThemeWrapper(mInputMethodService, newThemeIndex);
    782                 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
    783                         R.layout.input_view, null);
    784                 tryGC = false;
    785             } catch (OutOfMemoryError e) {
    786                 Log.w(TAG, "load keyboard failed: " + e);
    787                 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
    788                         oldThemeIndex + "," + newThemeIndex, e);
    789             } catch (InflateException e) {
    790                 Log.w(TAG, "load keyboard failed: " + e);
    791                 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
    792                         oldThemeIndex + "," + newThemeIndex, e);
    793             }
    794         }
    795 
    796         mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
    797         mKeyboardView.setKeyboardActionListener(mInputMethodService);
    798 
    799         // This always needs to be set since the accessibility state can
    800         // potentially change without the input view being re-created.
    801         AccessibleKeyboardViewProxy.setView(mKeyboardView);
    802 
    803         return mCurrentInputView;
    804     }
    805 
    806     private void postSetInputView(final View newInputView) {
    807         mInputMethodService.mHandler.post(new Runnable() {
    808             @Override
    809             public void run() {
    810                 if (newInputView != null) {
    811                     mInputMethodService.setInputView(newInputView);
    812                 }
    813                 mInputMethodService.updateInputViewShown();
    814             }
    815         });
    816     }
    817 
    818     @Override
    819     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    820         if (PREF_KEYBOARD_LAYOUT.equals(key)) {
    821             final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences);
    822             postSetInputView(createInputView(themeIndex, false));
    823         } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) {
    824             postSetInputView(createInputView(mThemeIndex, true));
    825         }
    826     }
    827 
    828     public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
    829         if (mIsAutoCorrectionActive != isAutoCorrection) {
    830             mIsAutoCorrectionActive = isAutoCorrection;
    831             final LatinKeyboard keyboard = getLatinKeyboard();
    832             if (keyboard != null && keyboard.needsAutoCorrectionSpacebarLed()) {
    833                 final Key invalidatedKey = keyboard.onAutoCorrectionStateChanged(isAutoCorrection);
    834                 final LatinKeyboardView keyboardView = getKeyboardView();
    835                 if (keyboardView != null)
    836                     keyboardView.invalidateKey(invalidatedKey);
    837             }
    838         }
    839     }
    840 
    841     private static int getF2KeyMode(boolean settingsKeyEnabled, boolean noSettingsKey) {
    842         if (noSettingsKey) {
    843             // Never shows the Settings key
    844             return KeyboardId.F2KEY_MODE_SHORTCUT_IME;
    845         }
    846 
    847         if (settingsKeyEnabled) {
    848             return KeyboardId.F2KEY_MODE_SETTINGS;
    849         } else {
    850             // It should be alright to fall back to the Settings key on 7-inch layouts
    851             // even when the Settings key is not explicitly enabled.
    852             return KeyboardId.F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS;
    853         }
    854     }
    855 }
    856