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.Log;
     24 import android.view.ContextThemeWrapper;
     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.compat.InputMethodServiceCompatUtils;
     31 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
     32 import com.android.inputmethod.keyboard.internal.KeyboardState;
     33 import com.android.inputmethod.latin.InputView;
     34 import com.android.inputmethod.latin.LatinIME;
     35 import com.android.inputmethod.latin.LatinImeLogger;
     36 import com.android.inputmethod.latin.R;
     37 import com.android.inputmethod.latin.RichInputMethodManager;
     38 import com.android.inputmethod.latin.SubtypeSwitcher;
     39 import com.android.inputmethod.latin.WordComposer;
     40 import com.android.inputmethod.latin.settings.Settings;
     41 import com.android.inputmethod.latin.settings.SettingsValues;
     42 import com.android.inputmethod.latin.utils.ResourceUtils;
     43 
     44 public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
     45     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
     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(final int themeId, final int styleId) {
     54             mThemeId = themeId;
     55             mStyleId = styleId;
     56         }
     57     }
     58 
     59     public static final int THEME_INDEX_ICS = 0;
     60     public static final int THEME_INDEX_GB = 1;
     61     public static final int THEME_INDEX_KLP = 2;
     62     public static final int THEME_INDEX_DEFAULT = THEME_INDEX_KLP;
     63     public static final KeyboardTheme[] KEYBOARD_THEMES = {
     64         new KeyboardTheme(THEME_INDEX_ICS, R.style.KeyboardTheme_ICS),
     65         new KeyboardTheme(THEME_INDEX_GB, R.style.KeyboardTheme_GB),
     66         new KeyboardTheme(THEME_INDEX_KLP, R.style.KeyboardTheme_KLP),
     67     };
     68 
     69     private SubtypeSwitcher mSubtypeSwitcher;
     70     private SharedPreferences mPrefs;
     71 
     72     private InputView mCurrentInputView;
     73     private View mMainKeyboardFrame;
     74     private MainKeyboardView mKeyboardView;
     75     private EmojiPalettesView mEmojiPalettesView;
     76     private LatinIME mLatinIME;
     77     private Resources mResources;
     78     private boolean mIsHardwareAcceleratedDrawingEnabled;
     79 
     80     private KeyboardState mState;
     81 
     82     private KeyboardLayoutSet mKeyboardLayoutSet;
     83 
     84     /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
     85      * what user actually typed. */
     86     private boolean mIsAutoCorrectionActive;
     87 
     88     private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[THEME_INDEX_DEFAULT];
     89     private Context mThemeContext;
     90 
     91     private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
     92 
     93     public static KeyboardSwitcher getInstance() {
     94         return sInstance;
     95     }
     96 
     97     private KeyboardSwitcher() {
     98         // Intentional empty constructor for singleton.
     99     }
    100 
    101     public static void init(final LatinIME latinIme) {
    102         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(latinIme);
    103         sInstance.initInternal(latinIme, prefs);
    104     }
    105 
    106     private void initInternal(final LatinIME latinIme, final SharedPreferences prefs) {
    107         mLatinIME = latinIme;
    108         mResources = latinIme.getResources();
    109         mPrefs = prefs;
    110         mSubtypeSwitcher = SubtypeSwitcher.getInstance();
    111         mState = new KeyboardState(this);
    112         mIsHardwareAcceleratedDrawingEnabled =
    113                 InputMethodServiceCompatUtils.enableHardwareAcceleration(mLatinIME);
    114     }
    115 
    116     public void updateKeyboardTheme() {
    117         final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper(
    118                 mLatinIME, getKeyboardTheme(mLatinIME, mPrefs));
    119         if (themeUpdated && mKeyboardView != null) {
    120             mLatinIME.setInputView(onCreateInputView(mIsHardwareAcceleratedDrawingEnabled));
    121         }
    122     }
    123 
    124     private static KeyboardTheme getKeyboardTheme(final Context context,
    125             final SharedPreferences prefs) {
    126         final Resources res = context.getResources();
    127         final int index = Settings.readKeyboardThemeIndex(prefs, res);
    128         if (index >= 0 && index < KEYBOARD_THEMES.length) {
    129             return KEYBOARD_THEMES[index];
    130         }
    131         final int defaultThemeIndex = Settings.resetAndGetDefaultKeyboardThemeIndex(prefs, res);
    132         Log.w(TAG, "Illegal keyboard theme in preference: " + index + ", default to "
    133                 + defaultThemeIndex);
    134         return KEYBOARD_THEMES[defaultThemeIndex];
    135     }
    136 
    137     private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context,
    138             final KeyboardTheme keyboardTheme) {
    139         if (mThemeContext == null || mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
    140             mKeyboardTheme = keyboardTheme;
    141             mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
    142             KeyboardLayoutSet.clearKeyboardCache();
    143             return true;
    144         }
    145         return false;
    146     }
    147 
    148     public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues) {
    149         final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
    150                 mThemeContext, editorInfo);
    151         final Resources res = mThemeContext.getResources();
    152         final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
    153         final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res);
    154         builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
    155         builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
    156         builder.setOptions(
    157                 settingsValues.isVoiceKeyEnabled(editorInfo),
    158                 true /* always show a voice key on the main keyboard */,
    159                 settingsValues.isLanguageSwitchKeyEnabled());
    160         mKeyboardLayoutSet = builder.build();
    161         try {
    162             mState.onLoadKeyboard();
    163         } catch (KeyboardLayoutSetException e) {
    164             Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
    165             LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
    166             return;
    167         }
    168     }
    169 
    170     public void saveKeyboardState() {
    171         if (getKeyboard() != null || isShowingEmojiPalettes()) {
    172             mState.onSaveKeyboardState();
    173         }
    174     }
    175 
    176     public void onFinishInputView() {
    177         mIsAutoCorrectionActive = false;
    178     }
    179 
    180     public void onHideWindow() {
    181         mIsAutoCorrectionActive = false;
    182     }
    183 
    184     private void setKeyboard(final Keyboard keyboard) {
    185         // Make {@link MainKeyboardView} visible and hide {@link EmojiPalettesView}.
    186         setMainKeyboardFrame();
    187         final MainKeyboardView keyboardView = mKeyboardView;
    188         final Keyboard oldKeyboard = keyboardView.getKeyboard();
    189         keyboardView.setKeyboard(keyboard);
    190         mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
    191         keyboardView.setKeyPreviewPopupEnabled(
    192                 Settings.readKeyPreviewPopupEnabled(mPrefs, mResources),
    193                 Settings.readKeyPreviewPopupDismissDelay(mPrefs, mResources));
    194         keyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
    195         keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
    196         final boolean subtypeChanged = (oldKeyboard == null)
    197                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
    198         final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
    199                 keyboard.mId.mLocale);
    200         keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage,
    201                 RichInputMethodManager.getInstance().hasMultipleEnabledIMEsOrSubtypes(true));
    202     }
    203 
    204     public Keyboard getKeyboard() {
    205         if (mKeyboardView != null) {
    206             return mKeyboardView.getKeyboard();
    207         }
    208         return null;
    209     }
    210 
    211     /**
    212      * Update keyboard shift state triggered by connected EditText status change.
    213      */
    214     public void updateShiftState() {
    215         mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
    216                 mLatinIME.getCurrentRecapitalizeState());
    217     }
    218 
    219     // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
    220     // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
    221     public void resetKeyboardStateToAlphabet() {
    222         mState.onResetKeyboardStateToAlphabet();
    223     }
    224 
    225     public void onPressKey(final int code, final boolean isSinglePointer) {
    226         mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState());
    227     }
    228 
    229     public void onReleaseKey(final int code, final boolean withSliding) {
    230         mState.onReleaseKey(code, withSliding);
    231     }
    232 
    233     public void onFinishSlidingInput() {
    234         mState.onFinishSlidingInput();
    235     }
    236 
    237     // Implements {@link KeyboardState.SwitchActions}.
    238     @Override
    239     public void setAlphabetKeyboard() {
    240         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
    241     }
    242 
    243     // Implements {@link KeyboardState.SwitchActions}.
    244     @Override
    245     public void setAlphabetManualShiftedKeyboard() {
    246         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
    247     }
    248 
    249     // Implements {@link KeyboardState.SwitchActions}.
    250     @Override
    251     public void setAlphabetAutomaticShiftedKeyboard() {
    252         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
    253     }
    254 
    255     // Implements {@link KeyboardState.SwitchActions}.
    256     @Override
    257     public void setAlphabetShiftLockedKeyboard() {
    258         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
    259     }
    260 
    261     // Implements {@link KeyboardState.SwitchActions}.
    262     @Override
    263     public void setAlphabetShiftLockShiftedKeyboard() {
    264         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
    265     }
    266 
    267     // Implements {@link KeyboardState.SwitchActions}.
    268     @Override
    269     public void setSymbolsKeyboard() {
    270         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
    271     }
    272 
    273     private void setMainKeyboardFrame() {
    274         mMainKeyboardFrame.setVisibility(View.VISIBLE);
    275         mEmojiPalettesView.setVisibility(View.GONE);
    276         mEmojiPalettesView.stopEmojiPalettes();
    277     }
    278 
    279     // Implements {@link KeyboardState.SwitchActions}.
    280     @Override
    281     public void setEmojiKeyboard() {
    282         mMainKeyboardFrame.setVisibility(View.GONE);
    283         mEmojiPalettesView.startEmojiPalettes();
    284         mEmojiPalettesView.setVisibility(View.VISIBLE);
    285     }
    286 
    287     // Implements {@link KeyboardState.SwitchActions}.
    288     @Override
    289     public void setSymbolsShiftedKeyboard() {
    290         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
    291     }
    292 
    293     // Implements {@link KeyboardState.SwitchActions}.
    294     @Override
    295     public void requestUpdatingShiftState() {
    296         mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
    297                 mLatinIME.getCurrentRecapitalizeState());
    298     }
    299 
    300     // Implements {@link KeyboardState.SwitchActions}.
    301     @Override
    302     public void startDoubleTapShiftKeyTimer() {
    303         final MainKeyboardView keyboardView = getMainKeyboardView();
    304         if (keyboardView != null) {
    305             keyboardView.startDoubleTapShiftKeyTimer();
    306         }
    307     }
    308 
    309     // Implements {@link KeyboardState.SwitchActions}.
    310     @Override
    311     public void cancelDoubleTapShiftKeyTimer() {
    312         final MainKeyboardView keyboardView = getMainKeyboardView();
    313         if (keyboardView != null) {
    314             keyboardView.cancelDoubleTapShiftKeyTimer();
    315         }
    316     }
    317 
    318     // Implements {@link KeyboardState.SwitchActions}.
    319     @Override
    320     public boolean isInDoubleTapShiftKeyTimeout() {
    321         final MainKeyboardView keyboardView = getMainKeyboardView();
    322         return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout();
    323     }
    324 
    325     /**
    326      * Updates state machine to figure out when to automatically switch back to the previous mode.
    327      */
    328     public void onCodeInput(final int code) {
    329         mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState());
    330     }
    331 
    332     private boolean isShowingMainKeyboard() {
    333         return null != mKeyboardView && mKeyboardView.isShown();
    334     }
    335 
    336     public boolean isShowingEmojiPalettes() {
    337         return mEmojiPalettesView != null && mEmojiPalettesView.isShown();
    338     }
    339 
    340     public boolean isShowingMoreKeysPanel() {
    341         if (isShowingEmojiPalettes()) {
    342             return false;
    343         }
    344         return mKeyboardView.isShowingMoreKeysPanel();
    345     }
    346 
    347     public View getVisibleKeyboardView() {
    348         if (isShowingEmojiPalettes()) {
    349             return mEmojiPalettesView;
    350         }
    351         return mKeyboardView;
    352     }
    353 
    354     public MainKeyboardView getMainKeyboardView() {
    355         return mKeyboardView;
    356     }
    357 
    358     public void deallocateMemory() {
    359         if (mKeyboardView != null) {
    360             mKeyboardView.cancelAllOngoingEvents();
    361             mKeyboardView.deallocateMemory();
    362         }
    363         if (mEmojiPalettesView != null) {
    364             mEmojiPalettesView.stopEmojiPalettes();
    365         }
    366     }
    367 
    368     public boolean isShowingMainKeyboardOrEmojiPalettes() {
    369         return isShowingMainKeyboard() || isShowingEmojiPalettes();
    370     }
    371 
    372     public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) {
    373         if (mKeyboardView != null) {
    374             mKeyboardView.closing();
    375         }
    376 
    377         updateKeyboardThemeAndContextThemeWrapper(mLatinIME, mKeyboardTheme);
    378         mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
    379                 R.layout.input_view, null);
    380         mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
    381         mEmojiPalettesView = (EmojiPalettesView)mCurrentInputView.findViewById(
    382                 R.id.emoji_keyboard_view);
    383 
    384         mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
    385         mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled);
    386         mKeyboardView.setKeyboardActionListener(mLatinIME);
    387         mEmojiPalettesView.setHardwareAcceleratedDrawingEnabled(
    388                 isHardwareAcceleratedDrawingEnabled);
    389         mEmojiPalettesView.setKeyboardActionListener(mLatinIME);
    390 
    391         // This always needs to be set since the accessibility state can
    392         // potentially change without the input view being re-created.
    393         AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView);
    394 
    395         return mCurrentInputView;
    396     }
    397 
    398     public void onNetworkStateChanged() {
    399         if (mKeyboardView != null) {
    400             mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
    401         }
    402     }
    403 
    404     public void onAutoCorrectionStateChanged(final boolean isAutoCorrection) {
    405         if (mIsAutoCorrectionActive != isAutoCorrection) {
    406             mIsAutoCorrectionActive = isAutoCorrection;
    407             if (mKeyboardView != null) {
    408                 mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
    409             }
    410         }
    411     }
    412 
    413     public int getKeyboardShiftMode() {
    414         final Keyboard keyboard = getKeyboard();
    415         if (keyboard == null) {
    416             return WordComposer.CAPS_MODE_OFF;
    417         }
    418         switch (keyboard.mId.mElementId) {
    419         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
    420         case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
    421             return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED;
    422         case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
    423             return WordComposer.CAPS_MODE_MANUAL_SHIFTED;
    424         case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
    425             return WordComposer.CAPS_MODE_AUTO_SHIFTED;
    426         default:
    427             return WordComposer.CAPS_MODE_OFF;
    428         }
    429     }
    430 }
    431