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.Resources;
     22 import android.util.Log;
     23 import android.view.ContextThemeWrapper;
     24 import android.view.InflateException;
     25 import android.view.LayoutInflater;
     26 import android.view.View;
     27 import android.view.inputmethod.EditorInfo;
     28 
     29 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
     30 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
     31 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
     32 import com.android.inputmethod.keyboard.internal.KeyboardState;
     33 import com.android.inputmethod.latin.DebugSettings;
     34 import com.android.inputmethod.latin.ImfUtils;
     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.R;
     39 import com.android.inputmethod.latin.SettingsValues;
     40 import com.android.inputmethod.latin.SubtypeSwitcher;
     41 import com.android.inputmethod.latin.Utils;
     42 
     43 public class KeyboardSwitcher implements KeyboardState.SwitchActions {
     44     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
     45 
     46     public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
     47 
     48     static class KeyboardTheme {
     49         public final String mName;
     50         public final int mThemeId;
     51         public final int mStyleId;
     52 
     53         public KeyboardTheme(String name, int themeId, int styleId) {
     54             mName = name;
     55             mThemeId = themeId;
     56             mStyleId = styleId;
     57         }
     58     }
     59 
     60     private static final KeyboardTheme[] KEYBOARD_THEMES = {
     61         new KeyboardTheme("Basic",            0, R.style.KeyboardTheme),
     62         new KeyboardTheme("HighContrast",     1, R.style.KeyboardTheme_HighContrast),
     63         new KeyboardTheme("Stone",            6, R.style.KeyboardTheme_Stone),
     64         new KeyboardTheme("Stne.Bold",        7, R.style.KeyboardTheme_Stone_Bold),
     65         new KeyboardTheme("GingerBread",      8, R.style.KeyboardTheme_Gingerbread),
     66         new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
     67     };
     68 
     69     private SubtypeSwitcher mSubtypeSwitcher;
     70     private SharedPreferences mPrefs;
     71     private boolean mForceNonDistinctMultitouch;
     72 
     73     private InputView mCurrentInputView;
     74     private LatinKeyboardView mKeyboardView;
     75     private LatinIME mLatinIME;
     76     private Resources mResources;
     77 
     78     private KeyboardState mState;
     79 
     80     private KeyboardLayoutSet mKeyboardLayoutSet;
     81 
     82     /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
     83      * what user actually typed. */
     84     private boolean mIsAutoCorrectionActive;
     85 
     86     private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[0];
     87     private Context mThemeContext;
     88 
     89     private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
     90 
     91     public static KeyboardSwitcher getInstance() {
     92         return sInstance;
     93     }
     94 
     95     private KeyboardSwitcher() {
     96         // Intentional empty constructor for singleton.
     97     }
     98 
     99     public static void init(LatinIME latinIme, SharedPreferences prefs) {
    100         sInstance.initInternal(latinIme, prefs);
    101     }
    102 
    103     private void initInternal(LatinIME latinIme, SharedPreferences prefs) {
    104         mLatinIME = latinIme;
    105         mResources = latinIme.getResources();
    106         mPrefs = prefs;
    107         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
    108         mState = new KeyboardState(this);
    109         setContextThemeWrapper(latinIme, getKeyboardTheme(latinIme, prefs));
    110         mForceNonDistinctMultitouch = prefs.getBoolean(
    111                 DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false);
    112     }
    113 
    114     private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) {
    115         final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index);
    116         final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex);
    117         try {
    118             final int index = Integer.valueOf(themeIndex);
    119             if (index >= 0 && index < KEYBOARD_THEMES.length) {
    120                 return KEYBOARD_THEMES[index];
    121             }
    122         } catch (NumberFormatException e) {
    123             // Format error, keyboard theme is default to 0.
    124         }
    125         Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0");
    126         return KEYBOARD_THEMES[0];
    127     }
    128 
    129     private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) {
    130         if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
    131             mKeyboardTheme = keyboardTheme;
    132             mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
    133             KeyboardLayoutSet.clearKeyboardCache();
    134         }
    135     }
    136 
    137     public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
    138         final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
    139                 mThemeContext, editorInfo);
    140         builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation,
    141                 mThemeContext.getResources().getDisplayMetrics().widthPixels);
    142         builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
    143         builder.setOptions(
    144                 settingsValues.isVoiceKeyEnabled(editorInfo),
    145                 settingsValues.isVoiceKeyOnMain(),
    146                 settingsValues.isLanguageSwitchKeyEnabled(mThemeContext));
    147         mKeyboardLayoutSet = builder.build();
    148         try {
    149             mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
    150         } catch (KeyboardLayoutSetException e) {
    151             Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
    152             LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
    153             return;
    154         }
    155     }
    156 
    157     public void saveKeyboardState() {
    158         if (getKeyboard() != null) {
    159             mState.onSaveKeyboardState();
    160         }
    161     }
    162 
    163     public void onFinishInputView() {
    164         mIsAutoCorrectionActive = false;
    165     }
    166 
    167     public void onHideWindow() {
    168         mIsAutoCorrectionActive = false;
    169     }
    170 
    171     private void setKeyboard(final Keyboard keyboard) {
    172         final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
    173         mKeyboardView.setKeyboard(keyboard);
    174         mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
    175         mKeyboardView.setKeyPreviewPopupEnabled(
    176                 SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
    177                 SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
    178         mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
    179         mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
    180         final boolean subtypeChanged = (oldKeyboard == null)
    181                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
    182         final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
    183                 keyboard.mId.mLocale);
    184         mKeyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage,
    185                 ImfUtils.hasMultipleEnabledIMEsOrSubtypes(mLatinIME, true));
    186     }
    187 
    188     public Keyboard getKeyboard() {
    189         if (mKeyboardView != null) {
    190             return mKeyboardView.getKeyboard();
    191         }
    192         return null;
    193     }
    194 
    195     /**
    196      * Update keyboard shift state triggered by connected EditText status change.
    197      */
    198     public void updateShiftState() {
    199         mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
    200     }
    201 
    202     public void onPressKey(int code) {
    203         if (isVibrateAndSoundFeedbackRequired()) {
    204             mLatinIME.hapticAndAudioFeedback(code);
    205         }
    206         mState.onPressKey(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState());
    207     }
    208 
    209     public void onReleaseKey(int code, boolean withSliding) {
    210         mState.onReleaseKey(code, withSliding);
    211     }
    212 
    213     public void onCancelInput() {
    214         mState.onCancelInput(isSinglePointer());
    215     }
    216 
    217     // Implements {@link KeyboardState.SwitchActions}.
    218     @Override
    219     public void setAlphabetKeyboard() {
    220         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
    221     }
    222 
    223     // Implements {@link KeyboardState.SwitchActions}.
    224     @Override
    225     public void setAlphabetManualShiftedKeyboard() {
    226         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
    227     }
    228 
    229     // Implements {@link KeyboardState.SwitchActions}.
    230     @Override
    231     public void setAlphabetAutomaticShiftedKeyboard() {
    232         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
    233     }
    234 
    235     // Implements {@link KeyboardState.SwitchActions}.
    236     @Override
    237     public void setAlphabetShiftLockedKeyboard() {
    238         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
    239     }
    240 
    241     // Implements {@link KeyboardState.SwitchActions}.
    242     @Override
    243     public void setAlphabetShiftLockShiftedKeyboard() {
    244         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
    245     }
    246 
    247     // Implements {@link KeyboardState.SwitchActions}.
    248     @Override
    249     public void setSymbolsKeyboard() {
    250         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
    251     }
    252 
    253     // Implements {@link KeyboardState.SwitchActions}.
    254     @Override
    255     public void setSymbolsShiftedKeyboard() {
    256         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
    257     }
    258 
    259     // Implements {@link KeyboardState.SwitchActions}.
    260     @Override
    261     public void requestUpdatingShiftState() {
    262         mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
    263     }
    264 
    265     // Implements {@link KeyboardState.SwitchActions}.
    266     @Override
    267     public void startDoubleTapTimer() {
    268         final LatinKeyboardView keyboardView = getKeyboardView();
    269         if (keyboardView != null) {
    270             final TimerProxy timer = keyboardView.getTimerProxy();
    271             timer.startDoubleTapTimer();
    272         }
    273     }
    274 
    275     // Implements {@link KeyboardState.SwitchActions}.
    276     @Override
    277     public void cancelDoubleTapTimer() {
    278         final LatinKeyboardView keyboardView = getKeyboardView();
    279         if (keyboardView != null) {
    280             final TimerProxy timer = keyboardView.getTimerProxy();
    281             timer.cancelDoubleTapTimer();
    282         }
    283     }
    284 
    285     // Implements {@link KeyboardState.SwitchActions}.
    286     @Override
    287     public boolean isInDoubleTapTimeout() {
    288         final LatinKeyboardView keyboardView = getKeyboardView();
    289         return (keyboardView != null)
    290                 ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false;
    291     }
    292 
    293     // Implements {@link KeyboardState.SwitchActions}.
    294     @Override
    295     public void startLongPressTimer(int code) {
    296         final LatinKeyboardView keyboardView = getKeyboardView();
    297         if (keyboardView != null) {
    298             final TimerProxy timer = keyboardView.getTimerProxy();
    299             timer.startLongPressTimer(code);
    300         }
    301     }
    302 
    303     // Implements {@link KeyboardState.SwitchActions}.
    304     @Override
    305     public void cancelLongPressTimer() {
    306         final LatinKeyboardView keyboardView = getKeyboardView();
    307         if (keyboardView != null) {
    308             final TimerProxy timer = keyboardView.getTimerProxy();
    309             timer.cancelLongPressTimer();
    310         }
    311     }
    312 
    313     // Implements {@link KeyboardState.SwitchActions}.
    314     @Override
    315     public void hapticAndAudioFeedback(int code) {
    316         mLatinIME.hapticAndAudioFeedback(code);
    317     }
    318 
    319     public void onLongPressTimeout(int code) {
    320         mState.onLongPressTimeout(code);
    321     }
    322 
    323     public boolean isInMomentarySwitchState() {
    324         return mState.isInMomentarySwitchState();
    325     }
    326 
    327     private boolean isVibrateAndSoundFeedbackRequired() {
    328         return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
    329     }
    330 
    331     private boolean isSinglePointer() {
    332         return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
    333     }
    334 
    335     public boolean hasDistinctMultitouch() {
    336         return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
    337     }
    338 
    339     /**
    340      * Updates state machine to figure out when to automatically switch back to the previous mode.
    341      */
    342     public void onCodeInput(int code) {
    343         mState.onCodeInput(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState());
    344     }
    345 
    346     public LatinKeyboardView getKeyboardView() {
    347         return mKeyboardView;
    348     }
    349 
    350     public View onCreateInputView() {
    351         if (mKeyboardView != null) {
    352             mKeyboardView.closing();
    353         }
    354 
    355         Utils.GCUtils.getInstance().reset();
    356         boolean tryGC = true;
    357         for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
    358             try {
    359                 setContextThemeWrapper(mLatinIME, mKeyboardTheme);
    360                 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
    361                         R.layout.input_view, null);
    362                 tryGC = false;
    363             } catch (OutOfMemoryError e) {
    364                 Log.w(TAG, "load keyboard failed: " + e);
    365                 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
    366             } catch (InflateException e) {
    367                 Log.w(TAG, "load keyboard failed: " + e);
    368                 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
    369             }
    370         }
    371 
    372         mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
    373         mKeyboardView.setKeyboardActionListener(mLatinIME);
    374         if (mForceNonDistinctMultitouch) {
    375             mKeyboardView.setDistinctMultitouch(false);
    376         }
    377 
    378         // This always needs to be set since the accessibility state can
    379         // potentially change without the input view being re-created.
    380         AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView);
    381 
    382         return mCurrentInputView;
    383     }
    384 
    385     public void onNetworkStateChanged() {
    386         if (mKeyboardView != null) {
    387             mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
    388         }
    389     }
    390 
    391     public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
    392         if (mIsAutoCorrectionActive != isAutoCorrection) {
    393             mIsAutoCorrectionActive = isAutoCorrection;
    394             if (mKeyboardView != null) {
    395                 mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
    396             }
    397         }
    398     }
    399 }
    400