KeyboardSwitcher.java revision 2a88440419f49d100c73e067a823390f64aba3b1
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * 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.util.Log;
23import android.view.ContextThemeWrapper;
24import android.view.InflateException;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.inputmethod.EditorInfo;
28
29import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
30import com.android.inputmethod.keyboard.internal.KeyboardState;
31import com.android.inputmethod.latin.DebugSettings;
32import com.android.inputmethod.latin.InputView;
33import com.android.inputmethod.latin.LatinIME;
34import com.android.inputmethod.latin.LatinImeLogger;
35import com.android.inputmethod.latin.R;
36import com.android.inputmethod.latin.Settings;
37import com.android.inputmethod.latin.SettingsValues;
38import com.android.inputmethod.latin.SubtypeSwitcher;
39import com.android.inputmethod.latin.Utils;
40
41public class KeyboardSwitcher implements KeyboardState.SwitchActions,
42        SharedPreferences.OnSharedPreferenceChangeListener {
43    private static final String TAG = KeyboardSwitcher.class.getSimpleName();
44
45    public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
46    private static final int[] KEYBOARD_THEMES = {
47        R.style.KeyboardTheme,
48        R.style.KeyboardTheme_HighContrast,
49        R.style.KeyboardTheme_Stone,
50        R.style.KeyboardTheme_Stone_Bold,
51        R.style.KeyboardTheme_Gingerbread,
52        R.style.KeyboardTheme_IceCreamSandwich,
53    };
54
55    private SubtypeSwitcher mSubtypeSwitcher;
56    private SharedPreferences mPrefs;
57    private boolean mForceNonDistinctMultitouch;
58
59    private InputView mCurrentInputView;
60    private LatinKeyboardView mKeyboardView;
61    private LatinIME mInputMethodService;
62    private Resources mResources;
63
64    private KeyboardState mState;
65
66    private KeyboardSet mKeyboardSet;
67
68    /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
69     * what user actually typed. */
70    private boolean mIsAutoCorrectionActive;
71
72    private int mThemeIndex = -1;
73    private Context mThemeContext;
74
75    private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
76
77    public static KeyboardSwitcher getInstance() {
78        return sInstance;
79    }
80
81    private KeyboardSwitcher() {
82        // Intentional empty constructor for singleton.
83    }
84
85    public static void init(LatinIME ims, SharedPreferences prefs) {
86        sInstance.initInternal(ims, prefs);
87    }
88
89    private void initInternal(LatinIME ims, SharedPreferences prefs) {
90        mInputMethodService = ims;
91        mResources = ims.getResources();
92        mPrefs = prefs;
93        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
94        mState = new KeyboardState(this);
95        setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
96        prefs.registerOnSharedPreferenceChangeListener(this);
97        mForceNonDistinctMultitouch = prefs.getBoolean(
98                DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false);
99    }
100
101    private static int getKeyboardThemeIndex(Context context, SharedPreferences prefs) {
102        final String defaultThemeId = context.getString(R.string.config_default_keyboard_theme_id);
103        final String themeId = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultThemeId);
104        try {
105            final int themeIndex = Integer.valueOf(themeId);
106            if (themeIndex >= 0 && themeIndex < KEYBOARD_THEMES.length)
107                return themeIndex;
108        } catch (NumberFormatException e) {
109            // Format error, keyboard theme is default to 0.
110        }
111        Log.w(TAG, "Illegal keyboard theme in preference: " + themeId + ", default to 0");
112        return 0;
113    }
114
115    private void setContextThemeWrapper(Context context, int themeIndex) {
116        if (mThemeIndex != themeIndex) {
117            mThemeIndex = themeIndex;
118            mThemeContext = new ContextThemeWrapper(context, KEYBOARD_THEMES[themeIndex]);
119            KeyboardSet.clearKeyboardCache();
120        }
121    }
122
123    public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
124        final KeyboardSet.Builder builder = new KeyboardSet.Builder(mThemeContext, editorInfo);
125        builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation,
126                mThemeContext.getResources().getDisplayMetrics().widthPixels);
127        builder.setSubtype(
128                mSubtypeSwitcher.getInputLocale(),
129                mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
130                        LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE),
131                mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
132                        LatinIME.SUBTYPE_EXTRA_VALUE_SUPPORT_TOUCH_POSITION_CORRECTION));
133        builder.setOptions(
134                settingsValues.isSettingsKeyEnabled(),
135                settingsValues.isVoiceKeyEnabled(editorInfo),
136                settingsValues.isVoiceKeyOnMain());
137        mKeyboardSet = builder.build();
138        final KeyboardId mainKeyboardId = mKeyboardSet.getMainKeyboardId();
139        try {
140            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols),
141                    hasDistinctMultitouch());
142        } catch (RuntimeException e) {
143            Log.w(TAG, "loading keyboard failed: " + mainKeyboardId, e);
144            LatinImeLogger.logOnException(mainKeyboardId.toString(), e);
145            return;
146        }
147        // TODO: Should get rid of this special case handling for Phone Number layouts once we
148        // have separate layouts with unique KeyboardIds for alphabet and alphabet-shifted
149        // respectively.
150        if (mainKeyboardId.isPhoneKeyboard()) {
151            mState.setSymbolsKeyboard();
152        }
153        updateShiftState();
154    }
155
156    public void saveKeyboardState() {
157        if (isKeyboardAvailable()) {
158            mState.onSaveKeyboardState();
159        }
160    }
161
162    public void onFinishInputView() {
163        mIsAutoCorrectionActive = false;
164    }
165
166    public void onHideWindow() {
167        mIsAutoCorrectionActive = false;
168    }
169
170    private void setKeyboard(final Keyboard keyboard) {
171        final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
172        mKeyboardView.setKeyboard(keyboard);
173        mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
174        mKeyboardView.setKeyPreviewPopupEnabled(
175                SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
176                SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
177        mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
178        // If the cached keyboard had been switched to another keyboard while the language was
179        // displayed on its spacebar, it might have had arbitrary text fade factor. In such
180        // case, we should reset the text fade factor. It is also applicable to shortcut key.
181        mKeyboardView.updateSpacebar(0.0f,
182                mSubtypeSwitcher.needsToDisplayLanguage(keyboard.mId.mLocale));
183        mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
184        final boolean localeChanged = (oldKeyboard == null)
185                || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
186        mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
187    }
188
189    public boolean isAlphabetMode() {
190        final Keyboard keyboard = getKeyboard();
191        return keyboard != null && keyboard.mId.isAlphabetKeyboard();
192    }
193
194    public boolean isInputViewShown() {
195        return mCurrentInputView != null && mCurrentInputView.isShown();
196    }
197
198    public boolean isShiftedOrShiftLocked() {
199        final Keyboard keyboard = getKeyboard();
200        return keyboard != null && keyboard.isShiftedOrShiftLocked();
201    }
202
203    public boolean isManualTemporaryUpperCase() {
204        final Keyboard keyboard = getKeyboard();
205        return keyboard != null && keyboard.isManualTemporaryUpperCase();
206    }
207
208    public boolean isKeyboardAvailable() {
209        if (mKeyboardView != null)
210            return mKeyboardView.getKeyboard() != null;
211        return false;
212    }
213
214    public Keyboard getKeyboard() {
215        if (mKeyboardView != null) {
216            return mKeyboardView.getKeyboard();
217        }
218        return null;
219    }
220
221    // Implements {@link KeyboardState.SwitchActions}.
222    @Override
223    public void setShifted(int shiftMode) {
224        mInputMethodService.mHandler.cancelUpdateShiftState();
225        Keyboard keyboard = getKeyboard();
226        if (keyboard == null)
227            return;
228        switch (shiftMode) {
229        case AUTOMATIC_SHIFT:
230            keyboard.setAutomaticTemporaryUpperCase();
231            break;
232        case MANUAL_SHIFT:
233            keyboard.setShifted(true);
234            break;
235        case UNSHIFT:
236            keyboard.setShifted(false);
237            break;
238        }
239        mKeyboardView.invalidateAllKeys();
240    }
241
242    // Implements {@link KeyboardState.SwitchActions}.
243    @Override
244    public void setShiftLocked(boolean shiftLocked) {
245        mInputMethodService.mHandler.cancelUpdateShiftState();
246        Keyboard keyboard = getKeyboard();
247        if (keyboard == null)
248            return;
249        keyboard.setShiftLocked(shiftLocked);
250        mKeyboardView.invalidateAllKeys();
251        if (!shiftLocked) {
252            // To be able to turn off caps lock by "double tap" on shift key, we should ignore
253            // the second tap of the "double tap" from now for a while because we just have
254            // already turned off caps lock above.
255            mKeyboardView.startIgnoringDoubleTap();
256        }
257    }
258
259    /**
260     * Toggle caps lock state triggered by user touch event.
261     */
262    public void toggleCapsLock() {
263        mState.onToggleCapsLock();
264    }
265
266    /**
267     * Update keyboard shift state triggered by connected EditText status change.
268     */
269    public void updateShiftState() {
270        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
271    }
272
273    public void onPressKey(int code) {
274        mState.onPressKey(code);
275    }
276
277    public void onReleaseKey(int code, boolean withSliding) {
278        mState.onReleaseKey(code, withSliding);
279    }
280
281    public void onCancelInput() {
282        mState.onCancelInput(isSinglePointer());
283    }
284
285    // Implements {@link KeyboardState.SwitchActions}.
286    @Override
287    public void setSymbolsKeyboard() {
288        setKeyboard(mKeyboardSet.getSymbolsKeyboard());
289    }
290
291    // Implements {@link KeyboardState.SwitchActions}.
292    @Override
293    public void setAlphabetKeyboard() {
294        setKeyboard(mKeyboardSet.getMainKeyboard());
295    }
296
297    // Implements {@link KeyboardState.SwitchActions}.
298    @Override
299    public void setSymbolsShiftedKeyboard() {
300        setKeyboard(mKeyboardSet.getSymbolsShiftedKeyboard());
301    }
302
303    // Implements {@link KeyboardState.SwitchActions}.
304    @Override
305    public void requestUpdatingShiftState() {
306        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
307    }
308
309    public boolean isInMomentarySwitchState() {
310        return mState.isInMomentarySwitchState();
311    }
312
313    public boolean isVibrateAndSoundFeedbackRequired() {
314        return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
315    }
316
317    private boolean isSinglePointer() {
318        return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
319    }
320
321    public boolean hasDistinctMultitouch() {
322        return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
323    }
324
325    /**
326     * Updates state machine to figure out when to automatically snap back to the previous mode.
327     */
328    public void onCodeInput(int code) {
329        mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState());
330    }
331
332    public LatinKeyboardView getKeyboardView() {
333        return mKeyboardView;
334    }
335
336    public View onCreateInputView() {
337        return createInputView(mThemeIndex, true);
338    }
339
340    private View createInputView(final int newThemeIndex, final boolean forceRecreate) {
341        if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate)
342            return mCurrentInputView;
343
344        if (mKeyboardView != null) {
345            mKeyboardView.closing();
346        }
347
348        final int oldThemeIndex = mThemeIndex;
349        Utils.GCUtils.getInstance().reset();
350        boolean tryGC = true;
351        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
352            try {
353                setContextThemeWrapper(mInputMethodService, newThemeIndex);
354                mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
355                        R.layout.input_view, null);
356                tryGC = false;
357            } catch (OutOfMemoryError e) {
358                Log.w(TAG, "load keyboard failed: " + e);
359                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
360                        oldThemeIndex + "," + newThemeIndex, e);
361            } catch (InflateException e) {
362                Log.w(TAG, "load keyboard failed: " + e);
363                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
364                        oldThemeIndex + "," + newThemeIndex, e);
365            }
366        }
367
368        mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
369        mKeyboardView.setKeyboardActionListener(mInputMethodService);
370        if (mForceNonDistinctMultitouch) {
371            mKeyboardView.setDistinctMultitouch(false);
372        }
373
374        // This always needs to be set since the accessibility state can
375        // potentially change without the input view being re-created.
376        AccessibleKeyboardViewProxy.setView(mKeyboardView);
377
378        return mCurrentInputView;
379    }
380
381    private void postSetInputView(final View newInputView) {
382        final LatinIME latinIme = mInputMethodService;
383        latinIme.mHandler.post(new Runnable() {
384            @Override
385            public void run() {
386                if (newInputView != null) {
387                    latinIme.setInputView(newInputView);
388                }
389                latinIme.updateInputViewShown();
390            }
391        });
392    }
393
394    @Override
395    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
396        if (PREF_KEYBOARD_LAYOUT.equals(key)) {
397            final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences);
398            postSetInputView(createInputView(themeIndex, false));
399        } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) {
400            postSetInputView(createInputView(mThemeIndex, true));
401        }
402    }
403
404    public void onNetworkStateChanged() {
405        if (mKeyboardView != null) {
406            mKeyboardView.updateShortcutKey(SubtypeSwitcher.getInstance().isShortcutImeReady());
407        }
408    }
409
410    public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
411        if (mIsAutoCorrectionActive != isAutoCorrection) {
412            mIsAutoCorrectionActive = isAutoCorrection;
413            if (mKeyboardView != null) {
414                mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
415            }
416        }
417    }
418}
419