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.LayoutInflater;
     25 import android.view.View;
     26 import android.view.inputmethod.EditorInfo;
     27 
     28 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
     29 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
     30 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
     31 import com.android.inputmethod.keyboard.internal.KeyboardState;
     32 import com.android.inputmethod.latin.DebugSettings;
     33 import com.android.inputmethod.latin.ImfUtils;
     34 import com.android.inputmethod.latin.InputView;
     35 import com.android.inputmethod.latin.LatinIME;
     36 import com.android.inputmethod.latin.LatinImeLogger;
     37 import com.android.inputmethod.latin.R;
     38 import com.android.inputmethod.latin.SettingsValues;
     39 import com.android.inputmethod.latin.SubtypeSwitcher;
     40 import com.android.inputmethod.latin.WordComposer;
     41 
     42 public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
     43     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
     44 
     45     public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
     46 
     47     static final class KeyboardTheme {
     48         public final int mThemeId;
     49         public final int mStyleId;
     50 
     51         // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
     52         // in values/style.xml.
     53         public KeyboardTheme(int themeId, int styleId) {
     54             mThemeId = themeId;
     55             mStyleId = styleId;
     56         }
     57     }
     58 
     59     private static final KeyboardTheme[] KEYBOARD_THEMES = {
     60         new KeyboardTheme(0, R.style.KeyboardTheme),
     61         new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
     62         new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
     63         new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
     64         new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
     65         new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
     66     };
     67 
     68     private SubtypeSwitcher mSubtypeSwitcher;
     69     private SharedPreferences mPrefs;
     70     private boolean mForceNonDistinctMultitouch;
     71 
     72     private InputView mCurrentInputView;
     73     private MainKeyboardView mKeyboardView;
     74     private LatinIME mLatinIME;
     75     private Resources mResources;
     76 
     77     private KeyboardState mState;
     78 
     79     private KeyboardLayoutSet mKeyboardLayoutSet;
     80 
     81     /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
     82      * what user actually typed. */
     83     private boolean mIsAutoCorrectionActive;
     84 
     85     private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[0];
     86     private Context mThemeContext;
     87 
     88     private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
     89 
     90     public static KeyboardSwitcher getInstance() {
     91         return sInstance;
     92     }
     93 
     94     private KeyboardSwitcher() {
     95         // Intentional empty constructor for singleton.
     96     }
     97 
     98     public static void init(LatinIME latinIme, SharedPreferences prefs) {
     99         sInstance.initInternal(latinIme, prefs);
    100     }
    101 
    102     private void initInternal(LatinIME latinIme, SharedPreferences prefs) {
    103         mLatinIME = latinIme;
    104         mResources = latinIme.getResources();
    105         mPrefs = prefs;
    106         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
    107         mState = new KeyboardState(this);
    108         setContextThemeWrapper(latinIme, getKeyboardTheme(latinIme, prefs));
    109         mForceNonDistinctMultitouch = prefs.getBoolean(
    110                 DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false);
    111     }
    112 
    113     private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) {
    114         final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index);
    115         final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex);
    116         try {
    117             final int index = Integer.valueOf(themeIndex);
    118             if (index >= 0 && index < KEYBOARD_THEMES.length) {
    119                 return KEYBOARD_THEMES[index];
    120             }
    121         } catch (NumberFormatException e) {
    122             // Format error, keyboard theme is default to 0.
    123         }
    124         Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0");
    125         return KEYBOARD_THEMES[0];
    126     }
    127 
    128     private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) {
    129         if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
    130             mKeyboardTheme = keyboardTheme;
    131             mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
    132             KeyboardLayoutSet.clearKeyboardCache();
    133         }
    134     }
    135 
    136     public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
    137         final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
    138                 mThemeContext, editorInfo);
    139         final Resources res = mThemeContext.getResources();
    140         builder.setScreenGeometry(res.getInteger(R.integer.config_device_form_factor),
    141                 res.getConfiguration().orientation, res.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 MainKeyboardView keyboardView = mKeyboardView;
    173         final Keyboard oldKeyboard = keyboardView.getKeyboard();
    174         keyboardView.setKeyboard(keyboard);
    175         mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
    176         keyboardView.setKeyPreviewPopupEnabled(
    177                 SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
    178                 SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
    179         keyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
    180         keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
    181         final boolean subtypeChanged = (oldKeyboard == null)
    182                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
    183         final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
    184                 keyboard.mId.mLocale);
    185         keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage,
    186                 ImfUtils.hasMultipleEnabledIMEsOrSubtypes(mLatinIME, true));
    187     }
    188 
    189     public Keyboard getKeyboard() {
    190         if (mKeyboardView != null) {
    191             return mKeyboardView.getKeyboard();
    192         }
    193         return null;
    194     }
    195 
    196     /**
    197      * Update keyboard shift state triggered by connected EditText status change.
    198      */
    199     public void updateShiftState() {
    200         mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
    201     }
    202 
    203     // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
    204     // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
    205     public void resetKeyboardStateToAlphabet() {
    206         mState.onResetKeyboardStateToAlphabet();
    207     }
    208 
    209     public void onPressKey(int code) {
    210         if (isVibrateAndSoundFeedbackRequired()) {
    211             mLatinIME.hapticAndAudioFeedback(code);
    212         }
    213         mState.onPressKey(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState());
    214     }
    215 
    216     public void onReleaseKey(int code, boolean withSliding) {
    217         mState.onReleaseKey(code, withSliding);
    218     }
    219 
    220     public void onCancelInput() {
    221         mState.onCancelInput(isSinglePointer());
    222     }
    223 
    224     // Implements {@link KeyboardState.SwitchActions}.
    225     @Override
    226     public void setAlphabetKeyboard() {
    227         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
    228     }
    229 
    230     // Implements {@link KeyboardState.SwitchActions}.
    231     @Override
    232     public void setAlphabetManualShiftedKeyboard() {
    233         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
    234     }
    235 
    236     // Implements {@link KeyboardState.SwitchActions}.
    237     @Override
    238     public void setAlphabetAutomaticShiftedKeyboard() {
    239         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
    240     }
    241 
    242     // Implements {@link KeyboardState.SwitchActions}.
    243     @Override
    244     public void setAlphabetShiftLockedKeyboard() {
    245         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
    246     }
    247 
    248     // Implements {@link KeyboardState.SwitchActions}.
    249     @Override
    250     public void setAlphabetShiftLockShiftedKeyboard() {
    251         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
    252     }
    253 
    254     // Implements {@link KeyboardState.SwitchActions}.
    255     @Override
    256     public void setSymbolsKeyboard() {
    257         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
    258     }
    259 
    260     // Implements {@link KeyboardState.SwitchActions}.
    261     @Override
    262     public void setSymbolsShiftedKeyboard() {
    263         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
    264     }
    265 
    266     // Implements {@link KeyboardState.SwitchActions}.
    267     @Override
    268     public void requestUpdatingShiftState() {
    269         mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
    270     }
    271 
    272     // Implements {@link KeyboardState.SwitchActions}.
    273     @Override
    274     public void startDoubleTapTimer() {
    275         final MainKeyboardView keyboardView = getMainKeyboardView();
    276         if (keyboardView != null) {
    277             final TimerProxy timer = keyboardView.getTimerProxy();
    278             timer.startDoubleTapTimer();
    279         }
    280     }
    281 
    282     // Implements {@link KeyboardState.SwitchActions}.
    283     @Override
    284     public void cancelDoubleTapTimer() {
    285         final MainKeyboardView keyboardView = getMainKeyboardView();
    286         if (keyboardView != null) {
    287             final TimerProxy timer = keyboardView.getTimerProxy();
    288             timer.cancelDoubleTapTimer();
    289         }
    290     }
    291 
    292     // Implements {@link KeyboardState.SwitchActions}.
    293     @Override
    294     public boolean isInDoubleTapTimeout() {
    295         final MainKeyboardView keyboardView = getMainKeyboardView();
    296         return (keyboardView != null)
    297                 ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false;
    298     }
    299 
    300     // Implements {@link KeyboardState.SwitchActions}.
    301     @Override
    302     public void startLongPressTimer(int code) {
    303         final MainKeyboardView keyboardView = getMainKeyboardView();
    304         if (keyboardView != null) {
    305             final TimerProxy timer = keyboardView.getTimerProxy();
    306             timer.startLongPressTimer(code);
    307         }
    308     }
    309 
    310     // Implements {@link KeyboardState.SwitchActions}.
    311     @Override
    312     public void cancelLongPressTimer() {
    313         final MainKeyboardView keyboardView = getMainKeyboardView();
    314         if (keyboardView != null) {
    315             final TimerProxy timer = keyboardView.getTimerProxy();
    316             timer.cancelLongPressTimer();
    317         }
    318     }
    319 
    320     // Implements {@link KeyboardState.SwitchActions}.
    321     @Override
    322     public void hapticAndAudioFeedback(int code) {
    323         mLatinIME.hapticAndAudioFeedback(code);
    324     }
    325 
    326     public void onLongPressTimeout(int code) {
    327         mState.onLongPressTimeout(code);
    328     }
    329 
    330     public boolean isInMomentarySwitchState() {
    331         return mState.isInMomentarySwitchState();
    332     }
    333 
    334     private boolean isVibrateAndSoundFeedbackRequired() {
    335         return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
    336     }
    337 
    338     private boolean isSinglePointer() {
    339         return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
    340     }
    341 
    342     public boolean hasDistinctMultitouch() {
    343         return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
    344     }
    345 
    346     /**
    347      * Updates state machine to figure out when to automatically switch back to the previous mode.
    348      */
    349     public void onCodeInput(int code) {
    350         mState.onCodeInput(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState());
    351     }
    352 
    353     public MainKeyboardView getMainKeyboardView() {
    354         return mKeyboardView;
    355     }
    356 
    357     public View onCreateInputView(boolean isHardwareAcceleratedDrawingEnabled) {
    358         if (mKeyboardView != null) {
    359             mKeyboardView.closing();
    360         }
    361 
    362         setContextThemeWrapper(mLatinIME, mKeyboardTheme);
    363         mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
    364                 R.layout.input_view, null);
    365 
    366         mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
    367         if (isHardwareAcceleratedDrawingEnabled) {
    368             mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
    369             // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
    370         }
    371         mKeyboardView.setKeyboardActionListener(mLatinIME);
    372         if (mForceNonDistinctMultitouch) {
    373             mKeyboardView.setDistinctMultitouch(false);
    374         }
    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(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