KeyboardSwitcher.java revision 2a9882a433e2372ac32fbc0def578d4d9a97a676
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.DisplayMetrics;
24import android.util.Log;
25import android.view.ContextThemeWrapper;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.inputmethod.EditorInfo;
29
30import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
31import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
32import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
33import com.android.inputmethod.keyboard.internal.KeyboardState;
34import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
35import com.android.inputmethod.latin.Constants;
36import com.android.inputmethod.latin.InputView;
37import com.android.inputmethod.latin.LatinIME;
38import com.android.inputmethod.latin.LatinImeLogger;
39import com.android.inputmethod.latin.R;
40import com.android.inputmethod.latin.RichInputMethodManager;
41import com.android.inputmethod.latin.Settings;
42import com.android.inputmethod.latin.SettingsValues;
43import com.android.inputmethod.latin.SubtypeSwitcher;
44import com.android.inputmethod.latin.WordComposer;
45
46public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
47    private static final String TAG = KeyboardSwitcher.class.getSimpleName();
48
49    public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
50
51    static final class KeyboardTheme {
52        public final int mThemeId;
53        public final int mStyleId;
54
55        // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
56        // in values/style.xml.
57        public KeyboardTheme(final int themeId, final int styleId) {
58            mThemeId = themeId;
59            mStyleId = styleId;
60        }
61    }
62
63    private static final KeyboardTheme[] KEYBOARD_THEMES = {
64        new KeyboardTheme(0, R.style.KeyboardTheme),
65        new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast),
66        new KeyboardTheme(6, R.style.KeyboardTheme_Stone),
67        new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold),
68        new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread),
69        new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
70    };
71
72    private SubtypeSwitcher mSubtypeSwitcher;
73    private SharedPreferences mPrefs;
74
75    private InputView mCurrentInputView;
76    private MainKeyboardView mKeyboardView;
77    private LatinIME mLatinIME;
78    private Resources mResources;
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[0];
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        setContextThemeWrapper(latinIme, getKeyboardTheme(latinIme, prefs));
113    }
114
115    private static KeyboardTheme getKeyboardTheme(final Context context,
116            final SharedPreferences prefs) {
117        final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index);
118        final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex);
119        try {
120            final int index = Integer.valueOf(themeIndex);
121            if (index >= 0 && index < KEYBOARD_THEMES.length) {
122                return KEYBOARD_THEMES[index];
123            }
124        } catch (NumberFormatException e) {
125            // Format error, keyboard theme is default to 0.
126        }
127        Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0");
128        return KEYBOARD_THEMES[0];
129    }
130
131    private void setContextThemeWrapper(final Context context, final KeyboardTheme keyboardTheme) {
132        if (mThemeContext == null || mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
133            mKeyboardTheme = keyboardTheme;
134            mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
135            KeyboardLayoutSet.clearKeyboardCache();
136        }
137    }
138
139    public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues) {
140        final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
141                mThemeContext, editorInfo);
142        final Resources res = mThemeContext.getResources();
143        final DisplayMetrics dm = res.getDisplayMetrics();
144        builder.setScreenGeometry(dm.widthPixels, dm.heightPixels);
145        builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
146        builder.setOptions(
147                settingsValues.isVoiceKeyEnabled(editorInfo),
148                settingsValues.isVoiceKeyOnMain(),
149                settingsValues.isLanguageSwitchKeyEnabled());
150        mKeyboardLayoutSet = builder.build();
151        try {
152            mState.onLoadKeyboard();
153        } catch (KeyboardLayoutSetException e) {
154            Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
155            LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
156            return;
157        }
158    }
159
160    public void saveKeyboardState() {
161        if (getKeyboard() != null) {
162            mState.onSaveKeyboardState();
163        }
164    }
165
166    public void onFinishInputView() {
167        mIsAutoCorrectionActive = false;
168    }
169
170    public void onHideWindow() {
171        mIsAutoCorrectionActive = false;
172    }
173
174    private void setKeyboard(final Keyboard keyboard) {
175        final MainKeyboardView keyboardView = mKeyboardView;
176        final Keyboard oldKeyboard = keyboardView.getKeyboard();
177        keyboardView.setKeyboard(keyboard);
178        mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
179        keyboardView.setKeyPreviewPopupEnabled(
180                Settings.readKeyPreviewPopupEnabled(mPrefs, mResources),
181                Settings.readKeyPreviewPopupDismissDelay(mPrefs, mResources));
182        keyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
183        keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
184        final boolean subtypeChanged = (oldKeyboard == null)
185                || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
186        final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
187                keyboard.mId.mLocale);
188        keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage,
189                RichInputMethodManager.getInstance().hasMultipleEnabledIMEsOrSubtypes(true));
190    }
191
192    public Keyboard getKeyboard() {
193        if (mKeyboardView != null) {
194            return mKeyboardView.getKeyboard();
195        }
196        return null;
197    }
198
199    /**
200     * Update keyboard shift state triggered by connected EditText status change.
201     */
202    public void updateShiftState() {
203        mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
204                mLatinIME.getCurrentRecapitalizeState());
205    }
206
207    // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
208    // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
209    public void resetKeyboardStateToAlphabet() {
210        mState.onResetKeyboardStateToAlphabet();
211    }
212
213    public void onPressKey(final int code, final boolean isSinglePointer) {
214        hapticAndAudioFeedback(code);
215        mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState());
216    }
217
218    public void onReleaseKey(final int code, final boolean withSliding) {
219        mState.onReleaseKey(code, withSliding);
220    }
221
222    public void onFinishSlidingInput() {
223        mState.onFinishSlidingInput();
224    }
225
226    // Implements {@link KeyboardState.SwitchActions}.
227    @Override
228    public void setAlphabetKeyboard() {
229        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
230    }
231
232    // Implements {@link KeyboardState.SwitchActions}.
233    @Override
234    public void setAlphabetManualShiftedKeyboard() {
235        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
236    }
237
238    // Implements {@link KeyboardState.SwitchActions}.
239    @Override
240    public void setAlphabetAutomaticShiftedKeyboard() {
241        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
242    }
243
244    // Implements {@link KeyboardState.SwitchActions}.
245    @Override
246    public void setAlphabetShiftLockedKeyboard() {
247        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
248    }
249
250    // Implements {@link KeyboardState.SwitchActions}.
251    @Override
252    public void setAlphabetShiftLockShiftedKeyboard() {
253        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
254    }
255
256    // Implements {@link KeyboardState.SwitchActions}.
257    @Override
258    public void setSymbolsKeyboard() {
259        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
260    }
261
262    // Implements {@link KeyboardState.SwitchActions}.
263    @Override
264    public void setSymbolsShiftedKeyboard() {
265        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
266    }
267
268    // Implements {@link KeyboardState.SwitchActions}.
269    @Override
270    public void requestUpdatingShiftState() {
271        mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
272                mLatinIME.getCurrentRecapitalizeState());
273    }
274
275    // Implements {@link KeyboardState.SwitchActions}.
276    @Override
277    public void startDoubleTapShiftKeyTimer() {
278        final MainKeyboardView keyboardView = getMainKeyboardView();
279        if (keyboardView != null) {
280            final TimerProxy timer = keyboardView.getTimerProxy();
281            timer.startDoubleTapShiftKeyTimer();
282        }
283    }
284
285    // Implements {@link KeyboardState.SwitchActions}.
286    @Override
287    public void cancelDoubleTapShiftKeyTimer() {
288        final MainKeyboardView keyboardView = getMainKeyboardView();
289        if (keyboardView != null) {
290            final TimerProxy timer = keyboardView.getTimerProxy();
291            timer.cancelDoubleTapShiftKeyTimer();
292        }
293    }
294
295    // Implements {@link KeyboardState.SwitchActions}.
296    @Override
297    public boolean isInDoubleTapShiftKeyTimeout() {
298        final MainKeyboardView keyboardView = getMainKeyboardView();
299        return (keyboardView != null)
300                ? keyboardView.getTimerProxy().isInDoubleTapShiftKeyTimeout() : false;
301    }
302
303    // Implements {@link KeyboardState.SwitchActions}.
304    @Override
305    public void startLongPressTimer(final int code) {
306        final MainKeyboardView keyboardView = getMainKeyboardView();
307        if (keyboardView != null) {
308            final TimerProxy timer = keyboardView.getTimerProxy();
309            timer.startLongPressTimer(code);
310        }
311    }
312
313    // Implements {@link KeyboardState.SwitchActions}.
314    @Override
315    public void cancelLongPressTimer() {
316        final MainKeyboardView keyboardView = getMainKeyboardView();
317        if (keyboardView != null) {
318            final TimerProxy timer = keyboardView.getTimerProxy();
319            timer.cancelLongPressTimer();
320        }
321    }
322
323    private void hapticAndAudioFeedback(final int code) {
324        if (mKeyboardView == null || mKeyboardView.isInSlidingKeyInput()) {
325            return;
326        }
327        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, mKeyboardView);
328    }
329
330    public void onLongPressTimeout(final int code) {
331        mState.onLongPressTimeout(code);
332        final Keyboard keyboard = getKeyboard();
333        if (keyboard != null && keyboard.mId.isAlphabetKeyboard() && code == Constants.CODE_SHIFT) {
334            hapticAndAudioFeedback(code);
335        }
336    }
337
338    /**
339     * Updates state machine to figure out when to automatically switch back to the previous mode.
340     */
341    public void onCodeInput(final int code) {
342        mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState());
343    }
344
345    public MainKeyboardView getMainKeyboardView() {
346        return mKeyboardView;
347    }
348
349    public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) {
350        if (mKeyboardView != null) {
351            mKeyboardView.closing();
352        }
353
354        setContextThemeWrapper(mLatinIME, mKeyboardTheme);
355        mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
356                R.layout.input_view, null);
357
358        mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
359        if (isHardwareAcceleratedDrawingEnabled) {
360            mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
361            // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
362        }
363        mKeyboardView.setKeyboardActionListener(mLatinIME);
364
365        // This always needs to be set since the accessibility state can
366        // potentially change without the input view being re-created.
367        AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView);
368
369        return mCurrentInputView;
370    }
371
372    public void onNetworkStateChanged() {
373        if (mKeyboardView != null) {
374            mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
375        }
376    }
377
378    public void onAutoCorrectionStateChanged(final boolean isAutoCorrection) {
379        if (mIsAutoCorrectionActive != isAutoCorrection) {
380            mIsAutoCorrectionActive = isAutoCorrection;
381            if (mKeyboardView != null) {
382                mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
383            }
384        }
385    }
386
387    public int getKeyboardShiftMode() {
388        final Keyboard keyboard = getKeyboard();
389        if (keyboard == null) {
390            return WordComposer.CAPS_MODE_OFF;
391        }
392        switch (keyboard.mId.mElementId) {
393        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
394        case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
395            return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED;
396        case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
397            return WordComposer.CAPS_MODE_MANUAL_SHIFTED;
398        case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
399            return WordComposer.CAPS_MODE_AUTO_SHIFTED;
400        default:
401            return WordComposer.CAPS_MODE_OFF;
402        }
403    }
404}
405