KeyboardSwitcher.java revision 433ca6a46db30a321715da0f457974916668dff5
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        try {
139            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
140        } catch (RuntimeException e) {
141            Log.w(TAG, "loading keyboard failed: " + mKeyboardSet.getKeyboardId(
142                    KeyboardId.ELEMENT_ALPHABET), e);
143            LatinImeLogger.logOnException(mKeyboardSet.getKeyboardId(
144                    KeyboardId.ELEMENT_ALPHABET).toString(), e);
145            return;
146        }
147    }
148
149    public void saveKeyboardState() {
150        if (getKeyboard() != null) {
151            mState.onSaveKeyboardState();
152        }
153    }
154
155    public void onFinishInputView() {
156        mIsAutoCorrectionActive = false;
157    }
158
159    public void onHideWindow() {
160        mIsAutoCorrectionActive = false;
161    }
162
163    private void setKeyboard(final Keyboard keyboard) {
164        final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
165        mKeyboardView.setKeyboard(keyboard);
166        mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
167        mKeyboardView.setKeyPreviewPopupEnabled(
168                SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
169                SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
170        mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
171        // If the cached keyboard had been switched to another keyboard while the language was
172        // displayed on its spacebar, it might have had arbitrary text fade factor. In such
173        // case, we should reset the text fade factor. It is also applicable to shortcut key.
174        mKeyboardView.updateSpacebar(0.0f,
175                mSubtypeSwitcher.needsToDisplayLanguage(keyboard.mId.mLocale));
176        mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
177        final boolean localeChanged = (oldKeyboard == null)
178                || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
179        mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
180    }
181
182    public Keyboard getKeyboard() {
183        if (mKeyboardView != null) {
184            return mKeyboardView.getKeyboard();
185        }
186        return null;
187    }
188
189    /**
190     * Update keyboard shift state triggered by connected EditText status change.
191     */
192    public void updateShiftState() {
193        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
194    }
195
196    public void onPressKey(int code) {
197        mState.onPressKey(code);
198    }
199
200    public void onReleaseKey(int code, boolean withSliding) {
201        mState.onReleaseKey(code, withSliding);
202    }
203
204    public void onCancelInput() {
205        mState.onCancelInput(isSinglePointer());
206    }
207
208    // Implements {@link KeyboardState.SwitchActions}.
209    @Override
210    public void setAlphabetKeyboard() {
211        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
212    }
213
214    // Implements {@link KeyboardState.SwitchActions}.
215    @Override
216    public void setAlphabetManualShiftedKeyboard() {
217        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
218    }
219
220    // Implements {@link KeyboardState.SwitchActions}.
221    @Override
222    public void setAlphabetAutomaticShiftedKeyboard() {
223        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
224    }
225
226    // Implements {@link KeyboardState.SwitchActions}.
227    @Override
228    public void setAlphabetShiftLockedKeyboard() {
229        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
230    }
231
232    // Implements {@link KeyboardState.SwitchActions}.
233    @Override
234    public void setAlphabetShiftLockShiftedKeyboard() {
235        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
236    }
237
238    // Implements {@link KeyboardState.SwitchActions}.
239    @Override
240    public void setSymbolsKeyboard() {
241        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
242    }
243
244    // Implements {@link KeyboardState.SwitchActions}.
245    @Override
246    public void setSymbolsShiftedKeyboard() {
247        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
248    }
249
250    // Implements {@link KeyboardState.SwitchActions}.
251    @Override
252    public void requestUpdatingShiftState() {
253        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
254    }
255
256    public boolean isInMomentarySwitchState() {
257        return mState.isInMomentarySwitchState();
258    }
259
260    public boolean isVibrateAndSoundFeedbackRequired() {
261        return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
262    }
263
264    private boolean isSinglePointer() {
265        return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
266    }
267
268    public boolean hasDistinctMultitouch() {
269        return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
270    }
271
272    /**
273     * Updates state machine to figure out when to automatically switch back to the previous mode.
274     */
275    public void onCodeInput(int code) {
276        mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState());
277    }
278
279    public LatinKeyboardView getKeyboardView() {
280        return mKeyboardView;
281    }
282
283    public View onCreateInputView() {
284        return createInputView(mThemeIndex, true);
285    }
286
287    private View createInputView(final int newThemeIndex, final boolean forceRecreate) {
288        if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate)
289            return mCurrentInputView;
290
291        if (mKeyboardView != null) {
292            mKeyboardView.closing();
293        }
294
295        final int oldThemeIndex = mThemeIndex;
296        Utils.GCUtils.getInstance().reset();
297        boolean tryGC = true;
298        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
299            try {
300                setContextThemeWrapper(mInputMethodService, newThemeIndex);
301                mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
302                        R.layout.input_view, null);
303                tryGC = false;
304            } catch (OutOfMemoryError e) {
305                Log.w(TAG, "load keyboard failed: " + e);
306                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
307                        oldThemeIndex + "," + newThemeIndex, e);
308            } catch (InflateException e) {
309                Log.w(TAG, "load keyboard failed: " + e);
310                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
311                        oldThemeIndex + "," + newThemeIndex, e);
312            }
313        }
314
315        mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
316        mKeyboardView.setKeyboardActionListener(mInputMethodService);
317        if (mForceNonDistinctMultitouch) {
318            mKeyboardView.setDistinctMultitouch(false);
319        }
320
321        // This always needs to be set since the accessibility state can
322        // potentially change without the input view being re-created.
323        AccessibleKeyboardViewProxy.setView(mKeyboardView);
324
325        return mCurrentInputView;
326    }
327
328    private void postSetInputView(final View newInputView) {
329        final LatinIME latinIme = mInputMethodService;
330        latinIme.mHandler.post(new Runnable() {
331            @Override
332            public void run() {
333                if (newInputView != null) {
334                    latinIme.setInputView(newInputView);
335                }
336                latinIme.updateInputViewShown();
337            }
338        });
339    }
340
341    @Override
342    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
343        if (PREF_KEYBOARD_LAYOUT.equals(key)) {
344            final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences);
345            postSetInputView(createInputView(themeIndex, false));
346        } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) {
347            postSetInputView(createInputView(mThemeIndex, true));
348        }
349    }
350
351    public void onNetworkStateChanged() {
352        if (mKeyboardView != null) {
353            mKeyboardView.updateShortcutKey(SubtypeSwitcher.getInstance().isShortcutImeReady());
354        }
355    }
356
357    public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
358        if (mIsAutoCorrectionActive != isAutoCorrection) {
359            mIsAutoCorrectionActive = isAutoCorrection;
360            if (mKeyboardView != null) {
361                mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
362            }
363        }
364    }
365}
366