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