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