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