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