• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
save()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 
getKeyboardId()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 
restore()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 
getInstance()158     public static KeyboardSwitcher getInstance() {
159         return sInstance;
160     }
161 
KeyboardSwitcher()162     private KeyboardSwitcher() {
163         // Intentional empty constructor for singleton.
164     }
165 
init(LatinIME ims, SharedPreferences prefs)166     public static void init(LatinIME ims, SharedPreferences prefs) {
167         sInstance.initInternal(ims, prefs);
168     }
169 
initInternal(LatinIME ims, SharedPreferences prefs)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 
getKeyboardThemeIndex(Context context, SharedPreferences prefs)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 
setContextThemeWrapper(Context context, int themeIndex)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 
loadKeyboard(EditorInfo editorInfo, Settings.Values settingsValues)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 
saveKeyboardState()216     public void saveKeyboardState() {
217         mSavedKeyboardState.save();
218     }
219 
onFinishInputView()220     public void onFinishInputView() {
221         mIsAutoCorrectionActive = false;
222     }
223 
onHideWindow()224     public void onHideWindow() {
225         mIsAutoCorrectionActive = false;
226     }
227 
setKeyboard(final Keyboard keyboard)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 
getSwitchState(KeyboardId id)244     private int getSwitchState(KeyboardId id) {
245         return id.equals(mMainKeyboardId) ? SWITCH_STATE_ALPHA : SWITCH_STATE_SYMBOL_BEGIN;
246     }
247 
updateShiftLockState(Keyboard keyboard)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 
getKeyboard(KeyboardId id)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 
getKeyboardId(EditorInfo editorInfo, final boolean isSymbols, final boolean isShift, Settings.Values settingsValues)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 
getKeyboardMode()348     public int getKeyboardMode() {
349         return mCurrentId != null ? mCurrentId.mMode : KeyboardId.MODE_TEXT;
350     }
351 
isAlphabetMode()352     public boolean isAlphabetMode() {
353         return mCurrentId != null && mCurrentId.isAlphabetKeyboard();
354     }
355 
isInputViewShown()356     public boolean isInputViewShown() {
357         return mCurrentInputView != null && mCurrentInputView.isShown();
358     }
359 
isKeyboardAvailable()360     public boolean isKeyboardAvailable() {
361         if (mKeyboardView != null)
362             return mKeyboardView.getKeyboard() != null;
363         return false;
364     }
365 
getLatinKeyboard()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 
isShiftedOrShiftLocked()375     public boolean isShiftedOrShiftLocked() {
376         LatinKeyboard latinKeyboard = getLatinKeyboard();
377         if (latinKeyboard != null)
378             return latinKeyboard.isShiftedOrShiftLocked();
379         return false;
380     }
381 
isShiftLocked()382     public boolean isShiftLocked() {
383         LatinKeyboard latinKeyboard = getLatinKeyboard();
384         if (latinKeyboard != null)
385             return latinKeyboard.isShiftLocked();
386         return false;
387     }
388 
isAutomaticTemporaryUpperCase()389     public boolean isAutomaticTemporaryUpperCase() {
390         LatinKeyboard latinKeyboard = getLatinKeyboard();
391         if (latinKeyboard != null)
392             return latinKeyboard.isAutomaticTemporaryUpperCase();
393         return false;
394     }
395 
isManualTemporaryUpperCase()396     public boolean isManualTemporaryUpperCase() {
397         LatinKeyboard latinKeyboard = getLatinKeyboard();
398         if (latinKeyboard != null)
399             return latinKeyboard.isManualTemporaryUpperCase();
400         return false;
401     }
402 
isManualTemporaryUpperCaseFromAuto()403     private boolean isManualTemporaryUpperCaseFromAuto() {
404         LatinKeyboard latinKeyboard = getLatinKeyboard();
405         if (latinKeyboard != null)
406             return latinKeyboard.isManualTemporaryUpperCaseFromAuto();
407         return false;
408     }
409 
setManualTemporaryUpperCase(boolean shifted)410     private void setManualTemporaryUpperCase(boolean shifted) {
411         LatinKeyboard latinKeyboard = getLatinKeyboard();
412         if (latinKeyboard != null) {
413             // On non-distinct multi touch panel device, we should also turn off the shift locked
414             // state when shift key is pressed to go to normal mode.
415             // On the other hand, on distinct multi touch panel device, turning off the shift locked
416             // state with shift key pressing is handled by onReleaseShift().
417             if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
418                 latinKeyboard.setShiftLocked(false);
419             }
420             if (latinKeyboard.setShifted(shifted)) {
421                 mKeyboardView.invalidateAllKeys();
422             }
423         }
424     }
425 
setShiftLocked(boolean shiftLocked)426     private void setShiftLocked(boolean shiftLocked) {
427         LatinKeyboard latinKeyboard = getLatinKeyboard();
428         if (latinKeyboard != null && latinKeyboard.setShiftLocked(shiftLocked)) {
429             mKeyboardView.invalidateAllKeys();
430         }
431     }
432 
433     /**
434      * Toggle keyboard shift state triggered by user touch event.
435      */
toggleShift()436     public void toggleShift() {
437         mInputMethodService.mHandler.cancelUpdateShiftState();
438         if (DEBUG_STATE)
439             Log.d(TAG, "toggleShift:"
440                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
441                     + " shiftKeyState=" + mShiftKeyState);
442         if (isAlphabetMode()) {
443             setManualTemporaryUpperCase(!isShiftedOrShiftLocked());
444         } else {
445             toggleShiftInSymbol();
446         }
447     }
448 
toggleCapsLock()449     public void toggleCapsLock() {
450         mInputMethodService.mHandler.cancelUpdateShiftState();
451         if (DEBUG_STATE)
452             Log.d(TAG, "toggleCapsLock:"
453                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
454                     + " shiftKeyState=" + mShiftKeyState);
455         if (isAlphabetMode()) {
456             if (isShiftLocked()) {
457                 // Shift key is long pressed while caps lock state, we will toggle back to normal
458                 // state. And mark as if shift key is released.
459                 setShiftLocked(false);
460                 mShiftKeyState.onRelease();
461             } else {
462                 setShiftLocked(true);
463             }
464         }
465     }
466 
setAutomaticTemporaryUpperCase()467     private void setAutomaticTemporaryUpperCase() {
468         if (mKeyboardView == null) return;
469         final Keyboard keyboard = mKeyboardView.getKeyboard();
470         if (keyboard == null) return;
471         keyboard.setAutomaticTemporaryUpperCase();
472         mKeyboardView.invalidateAllKeys();
473     }
474 
475     /**
476      * Update keyboard shift state triggered by connected EditText status change.
477      */
updateShiftState()478     public void updateShiftState() {
479         final ShiftKeyState shiftKeyState = mShiftKeyState;
480         if (DEBUG_STATE)
481             Log.d(TAG, "updateShiftState:"
482                     + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()
483                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
484                     + " shiftKeyState=" + shiftKeyState
485                     + " isAlphabetMode=" + isAlphabetMode()
486                     + " isShiftLocked=" + isShiftLocked());
487         if (isAlphabetMode()) {
488             if (!isShiftLocked() && !shiftKeyState.isIgnoring()) {
489                 if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
490                     // Only when shift key is releasing, automatic temporary upper case will be set.
491                     setAutomaticTemporaryUpperCase();
492                 } else {
493                     setManualTemporaryUpperCase(shiftKeyState.isMomentary());
494                 }
495             }
496         } else {
497             // In symbol keyboard mode, we should clear shift key state because only alphabet
498             // keyboard has shift key.
499             shiftKeyState.onRelease();
500         }
501     }
502 
changeKeyboardMode()503     public void changeKeyboardMode() {
504         if (DEBUG_STATE)
505             Log.d(TAG, "changeKeyboardMode:"
506                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
507                     + " shiftKeyState=" + mShiftKeyState);
508         toggleKeyboardMode();
509         if (isShiftLocked() && isAlphabetMode())
510             setShiftLocked(true);
511         updateShiftState();
512     }
513 
onPressShift(boolean withSliding)514     public void onPressShift(boolean withSliding) {
515         if (!isKeyboardAvailable())
516             return;
517         ShiftKeyState shiftKeyState = mShiftKeyState;
518         if (DEBUG_STATE)
519             Log.d(TAG, "onPressShift:"
520                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
521                     + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding);
522         if (isAlphabetMode()) {
523             if (isShiftLocked()) {
524                 // Shift key is pressed while caps lock state, we will treat this state as shifted
525                 // caps lock state and mark as if shift key pressed while normal state.
526                 shiftKeyState.onPress();
527                 setManualTemporaryUpperCase(true);
528             } else if (isAutomaticTemporaryUpperCase()) {
529                 // Shift key is pressed while automatic temporary upper case, we have to move to
530                 // manual temporary upper case.
531                 shiftKeyState.onPress();
532                 setManualTemporaryUpperCase(true);
533             } else if (isShiftedOrShiftLocked()) {
534                 // In manual upper case state, we just record shift key has been pressing while
535                 // shifted state.
536                 shiftKeyState.onPressOnShifted();
537             } else {
538                 // In base layout, chording or manual temporary upper case mode is started.
539                 shiftKeyState.onPress();
540                 toggleShift();
541             }
542         } else {
543             // In symbol mode, just toggle symbol and symbol more keyboard.
544             shiftKeyState.onPress();
545             toggleShift();
546             mSwitchState = SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
547         }
548     }
549 
onReleaseShift(boolean withSliding)550     public void onReleaseShift(boolean withSliding) {
551         if (!isKeyboardAvailable())
552             return;
553         ShiftKeyState shiftKeyState = mShiftKeyState;
554         if (DEBUG_STATE)
555             Log.d(TAG, "onReleaseShift:"
556                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
557                     + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding);
558         if (isAlphabetMode()) {
559             if (shiftKeyState.isMomentary()) {
560                 // After chording input while normal state.
561                 toggleShift();
562             } else if (isShiftLocked() && !shiftKeyState.isIgnoring() && !withSliding) {
563                 // Shift has been pressed without chording while caps lock state.
564                 toggleCapsLock();
565                 // To be able to turn off caps lock by "double tap" on shift key, we should ignore
566                 // the second tap of the "double tap" from now for a while because we just have
567                 // already turned off caps lock above.
568                 mKeyboardView.startIgnoringDoubleTap();
569             } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()
570                     && !withSliding) {
571                 // Shift has been pressed without chording while shifted state.
572                 toggleShift();
573             } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing()
574                     && !withSliding) {
575                 // Shift has been pressed without chording while manual temporary upper case
576                 // transited from automatic temporary upper case.
577                 toggleShift();
578             }
579         } else {
580             // In symbol mode, snap back to the previous keyboard mode if the user chords the shift
581             // key and another key, then releases the shift key.
582             if (mSwitchState == SWITCH_STATE_CHORDING_SYMBOL) {
583                 toggleShift();
584             }
585         }
586         shiftKeyState.onRelease();
587     }
588 
onPressSymbol()589     public void onPressSymbol() {
590         if (DEBUG_STATE)
591             Log.d(TAG, "onPressSymbol:"
592                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
593                     + " symbolKeyState=" + mSymbolKeyState);
594         changeKeyboardMode();
595         mSymbolKeyState.onPress();
596         mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL;
597     }
598 
onReleaseSymbol()599     public void onReleaseSymbol() {
600         if (DEBUG_STATE)
601             Log.d(TAG, "onReleaseSymbol:"
602                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
603                     + " symbolKeyState=" + mSymbolKeyState);
604         // Snap back to the previous keyboard mode if the user chords the mode change key and
605         // another key, then releases the mode change key.
606         if (mSwitchState == SWITCH_STATE_CHORDING_ALPHA) {
607             changeKeyboardMode();
608         }
609         mSymbolKeyState.onRelease();
610     }
611 
onOtherKeyPressed()612     public void onOtherKeyPressed() {
613         if (DEBUG_STATE)
614             Log.d(TAG, "onOtherKeyPressed:"
615                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
616                     + " shiftKeyState=" + mShiftKeyState
617                     + " symbolKeyState=" + mSymbolKeyState);
618         mShiftKeyState.onOtherKeyPressed();
619         mSymbolKeyState.onOtherKeyPressed();
620     }
621 
onCancelInput()622     public void onCancelInput() {
623         // Snap back to the previous keyboard mode if the user cancels sliding input.
624         if (getPointerCount() == 1) {
625             if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) {
626                 changeKeyboardMode();
627             } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) {
628                 toggleShift();
629             }
630         }
631     }
632 
toggleShiftInSymbol()633     private void toggleShiftInSymbol() {
634         if (isAlphabetMode())
635             return;
636         final LatinKeyboard keyboard;
637         if (mCurrentId.equals(mSymbolsKeyboardId)
638                 || !mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
639             keyboard = getKeyboard(mSymbolsShiftedKeyboardId);
640         } else {
641             keyboard = getKeyboard(mSymbolsKeyboardId);
642         }
643         setKeyboard(keyboard);
644     }
645 
isInMomentarySwitchState()646     public boolean isInMomentarySwitchState() {
647         return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
648                 || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
649     }
650 
isVibrateAndSoundFeedbackRequired()651     public boolean isVibrateAndSoundFeedbackRequired() {
652         return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
653     }
654 
getPointerCount()655     private int getPointerCount() {
656         return mKeyboardView == null ? 0 : mKeyboardView.getPointerCount();
657     }
658 
toggleKeyboardMode()659     private void toggleKeyboardMode() {
660         if (mCurrentId.equals(mMainKeyboardId)) {
661             setKeyboard(getKeyboard(mSymbolsKeyboardId));
662         } else {
663             setKeyboard(getKeyboard(mMainKeyboardId));
664         }
665     }
666 
hasDistinctMultitouch()667     public boolean hasDistinctMultitouch() {
668         return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
669     }
670 
isSpaceCharacter(int c)671     private static boolean isSpaceCharacter(int c) {
672         return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER;
673     }
674 
isLayoutSwitchBackCharacter(int c)675     private static boolean isLayoutSwitchBackCharacter(int c) {
676         if (TextUtils.isEmpty(mLayoutSwitchBackSymbols)) return false;
677         if (mLayoutSwitchBackSymbols.indexOf(c) >= 0) return true;
678         return false;
679     }
680 
681     /**
682      * Updates state machine to figure out when to automatically snap back to the previous mode.
683      */
onKey(int code)684     public void onKey(int code) {
685         if (DEBUG_STATE)
686             Log.d(TAG, "onKey: code=" + code + " switchState=" + mSwitchState
687                     + " pointers=" + getPointerCount());
688         switch (mSwitchState) {
689         case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL:
690             // Only distinct multi touch devices can be in this state.
691             // On non-distinct multi touch devices, mode change key is handled by
692             // {@link LatinIME#onCodeInput}, not by {@link LatinIME#onPress} and
693             // {@link LatinIME#onRelease}. So, on such devices, {@link #mSwitchState} starts
694             // from {@link #SWITCH_STATE_SYMBOL_BEGIN}, or {@link #SWITCH_STATE_ALPHA}, not from
695             // {@link #SWITCH_STATE_MOMENTARY}.
696             if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
697                 // Detected only the mode change key has been pressed, and then released.
698                 if (mCurrentId.equals(mMainKeyboardId)) {
699                     mSwitchState = SWITCH_STATE_ALPHA;
700                 } else {
701                     mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
702                 }
703             } else if (getPointerCount() == 1) {
704                 // Snap back to the previous keyboard mode if the user pressed the mode change key
705                 // and slid to other key, then released the finger.
706                 // If the user cancels the sliding input, snapping back to the previous keyboard
707                 // mode is handled by {@link #onCancelInput}.
708                 changeKeyboardMode();
709             } else {
710                 // Chording input is being started. The keyboard mode will be snapped back to the
711                 // previous mode in {@link onReleaseSymbol} when the mode change key is released.
712                 mSwitchState = SWITCH_STATE_CHORDING_ALPHA;
713             }
714             break;
715         case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE:
716             if (code == Keyboard.CODE_SHIFT) {
717                 // Detected only the shift key has been pressed on symbol layout, and then released.
718                 mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
719             } else if (getPointerCount() == 1) {
720                 // Snap back to the previous keyboard mode if the user pressed the shift key on
721                 // symbol mode and slid to other key, then released the finger.
722                 toggleShift();
723                 mSwitchState = SWITCH_STATE_SYMBOL;
724             } else {
725                 // Chording input is being started. The keyboard mode will be snapped back to the
726                 // previous mode in {@link onReleaseShift} when the shift key is released.
727                 mSwitchState = SWITCH_STATE_CHORDING_SYMBOL;
728             }
729             break;
730         case SWITCH_STATE_SYMBOL_BEGIN:
731             if (!isSpaceCharacter(code) && code >= 0) {
732                 mSwitchState = SWITCH_STATE_SYMBOL;
733             }
734             // Snap back to alpha keyboard mode immediately if user types a quote character.
735             if (isLayoutSwitchBackCharacter(code)) {
736                 changeKeyboardMode();
737             }
738             break;
739         case SWITCH_STATE_SYMBOL:
740         case SWITCH_STATE_CHORDING_SYMBOL:
741             // Snap back to alpha keyboard mode if user types one or more non-space/enter
742             // characters followed by a space/enter or a quote character.
743             if (isSpaceCharacter(code) || isLayoutSwitchBackCharacter(code)) {
744                 changeKeyboardMode();
745             }
746             break;
747         }
748     }
749 
getKeyboardView()750     public LatinKeyboardView getKeyboardView() {
751         return mKeyboardView;
752     }
753 
onCreateInputView()754     public View onCreateInputView() {
755         return createInputView(mThemeIndex, true);
756     }
757 
createInputView(final int newThemeIndex, final boolean forceRecreate)758     private View createInputView(final int newThemeIndex, final boolean forceRecreate) {
759         if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate)
760             return mCurrentInputView;
761 
762         if (mKeyboardView != null) {
763             mKeyboardView.closing();
764         }
765 
766         final int oldThemeIndex = mThemeIndex;
767         Utils.GCUtils.getInstance().reset();
768         boolean tryGC = true;
769         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
770             try {
771                 setContextThemeWrapper(mInputMethodService, newThemeIndex);
772                 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
773                         R.layout.input_view, null);
774                 tryGC = false;
775             } catch (OutOfMemoryError e) {
776                 Log.w(TAG, "load keyboard failed: " + e);
777                 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
778                         oldThemeIndex + "," + newThemeIndex, e);
779             } catch (InflateException e) {
780                 Log.w(TAG, "load keyboard failed: " + e);
781                 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
782                         oldThemeIndex + "," + newThemeIndex, e);
783             }
784         }
785 
786         mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
787         mKeyboardView.setKeyboardActionListener(mInputMethodService);
788 
789         // This always needs to be set since the accessibility state can
790         // potentially change without the input view being re-created.
791         AccessibleKeyboardViewProxy.setView(mKeyboardView);
792 
793         return mCurrentInputView;
794     }
795 
postSetInputView(final View newInputView)796     private void postSetInputView(final View newInputView) {
797         mInputMethodService.mHandler.post(new Runnable() {
798             @Override
799             public void run() {
800                 if (newInputView != null) {
801                     mInputMethodService.setInputView(newInputView);
802                 }
803                 mInputMethodService.updateInputViewShown();
804             }
805         });
806     }
807 
808     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)809     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
810         if (PREF_KEYBOARD_LAYOUT.equals(key)) {
811             final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences);
812             postSetInputView(createInputView(themeIndex, false));
813         } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) {
814             postSetInputView(createInputView(mThemeIndex, true));
815         }
816     }
817 
onAutoCorrectionStateChanged(boolean isAutoCorrection)818     public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
819         if (mIsAutoCorrectionActive != isAutoCorrection) {
820             mIsAutoCorrectionActive = isAutoCorrection;
821             final LatinKeyboard keyboard = getLatinKeyboard();
822             if (keyboard != null && keyboard.needsAutoCorrectionSpacebarLed()) {
823                 final Key invalidatedKey = keyboard.onAutoCorrectionStateChanged(isAutoCorrection);
824                 final LatinKeyboardView keyboardView = getKeyboardView();
825                 if (keyboardView != null)
826                     keyboardView.invalidateKey(invalidatedKey);
827             }
828         }
829     }
830 
getF2KeyMode(boolean settingsKeyEnabled, boolean noSettingsKey)831     private static int getF2KeyMode(boolean settingsKeyEnabled, boolean noSettingsKey) {
832         if (noSettingsKey) {
833             // Never shows the Settings key
834             return KeyboardId.F2KEY_MODE_SHORTCUT_IME;
835         }
836 
837         if (settingsKeyEnabled) {
838             return KeyboardId.F2KEY_MODE_SETTINGS;
839         } else {
840             // It should be alright to fall back to the Settings key on 7-inch layouts
841             // even when the Settings key is not explicitly enabled.
842             return KeyboardId.F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS;
843         }
844     }
845 }
846