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