KeyboardSwitcher.java revision 06bc1db017b14614353137fde035d2eed666a74f
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        } catch (RuntimeException e) {
142            Log.w(TAG, "loading keyboard failed: " + mainKeyboardId, e);
143            LatinImeLogger.logOnException(mainKeyboardId.toString(), e);
144            return;
145        }
146        // TODO: Should get rid of this special case handling for Phone Number layouts once we
147        // have separate layouts with unique KeyboardIds for alphabet and alphabet-shifted
148        // respectively.
149        if (mainKeyboardId.isPhoneKeyboard()) {
150            mState.setSymbolsKeyboard();
151        }
152        updateShiftState();
153    }
154
155    public void saveKeyboardState() {
156        if (isKeyboardAvailable()) {
157            mState.onSaveKeyboardState();
158        }
159    }
160
161    public void onFinishInputView() {
162        mIsAutoCorrectionActive = false;
163    }
164
165    public void onHideWindow() {
166        mIsAutoCorrectionActive = false;
167    }
168
169    private void setKeyboard(final Keyboard keyboard) {
170        final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
171        mKeyboardView.setKeyboard(keyboard);
172        mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
173        mKeyboardView.setKeyPreviewPopupEnabled(
174                SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
175                SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
176        mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
177        // If the cached keyboard had been switched to another keyboard while the language was
178        // displayed on its spacebar, it might have had arbitrary text fade factor. In such
179        // case, we should reset the text fade factor. It is also applicable to shortcut key.
180        mKeyboardView.updateSpacebar(0.0f,
181                mSubtypeSwitcher.needsToDisplayLanguage(keyboard.mId.mLocale));
182        mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
183        final boolean localeChanged = (oldKeyboard == null)
184                || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
185        mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
186    }
187
188    public boolean isAlphabetMode() {
189        final Keyboard keyboard = getKeyboard();
190        return keyboard != null && keyboard.mId.isAlphabetKeyboard();
191    }
192
193    public boolean isInputViewShown() {
194        return mCurrentInputView != null && mCurrentInputView.isShown();
195    }
196
197    public boolean isShiftedOrShiftLocked() {
198        final Keyboard keyboard = getKeyboard();
199        return keyboard != null && keyboard.isShiftedOrShiftLocked();
200    }
201
202    public boolean isManualTemporaryUpperCase() {
203        final Keyboard keyboard = getKeyboard();
204        return keyboard != null && keyboard.isManualTemporaryUpperCase();
205    }
206
207    public boolean isKeyboardAvailable() {
208        if (mKeyboardView != null)
209            return mKeyboardView.getKeyboard() != null;
210        return false;
211    }
212
213    public Keyboard getKeyboard() {
214        if (mKeyboardView != null) {
215            return mKeyboardView.getKeyboard();
216        }
217        return null;
218    }
219
220    // Implements {@link KeyboardState.SwitchActions}.
221    @Override
222    public void setShifted(int shiftMode) {
223        mInputMethodService.mHandler.cancelUpdateShiftState();
224        Keyboard keyboard = getKeyboard();
225        if (keyboard == null)
226            return;
227        switch (shiftMode) {
228        case AUTOMATIC_SHIFT:
229            keyboard.setAutomaticTemporaryUpperCase();
230            break;
231        case MANUAL_SHIFT:
232            keyboard.setShifted(true);
233            break;
234        case UNSHIFT:
235            keyboard.setShifted(false);
236            break;
237        }
238        mKeyboardView.invalidateAllKeys();
239    }
240
241    // Implements {@link KeyboardState.SwitchActions}.
242    @Override
243    public void setShiftLocked(boolean shiftLocked) {
244        mInputMethodService.mHandler.cancelUpdateShiftState();
245        Keyboard keyboard = getKeyboard();
246        if (keyboard == null)
247            return;
248        keyboard.setShiftLocked(shiftLocked);
249        mKeyboardView.invalidateAllKeys();
250        if (!shiftLocked) {
251            // To be able to turn off caps lock by "double tap" on shift key, we should ignore
252            // the second tap of the "double tap" from now for a while because we just have
253            // already turned off caps lock above.
254            mKeyboardView.startIgnoringDoubleTap();
255        }
256    }
257
258    /**
259     * Update keyboard shift state triggered by connected EditText status change.
260     */
261    public void updateShiftState() {
262        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
263    }
264
265    public void onPressKey(int code) {
266        mState.onPressKey(code);
267    }
268
269    public void onReleaseKey(int code, boolean withSliding) {
270        mState.onReleaseKey(code, withSliding);
271    }
272
273    public void onCancelInput() {
274        mState.onCancelInput(isSinglePointer());
275    }
276
277    // Implements {@link KeyboardState.SwitchActions}.
278    @Override
279    public void setSymbolsKeyboard() {
280        setKeyboard(mKeyboardSet.getSymbolsKeyboard());
281    }
282
283    // Implements {@link KeyboardState.SwitchActions}.
284    @Override
285    public void setAlphabetKeyboard() {
286        setKeyboard(mKeyboardSet.getMainKeyboard());
287    }
288
289    // Implements {@link KeyboardState.SwitchActions}.
290    @Override
291    public void setSymbolsShiftedKeyboard() {
292        setKeyboard(mKeyboardSet.getSymbolsShiftedKeyboard());
293    }
294
295    // Implements {@link KeyboardState.SwitchActions}.
296    @Override
297    public void requestUpdatingShiftState() {
298        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
299    }
300
301    public boolean isInMomentarySwitchState() {
302        return mState.isInMomentarySwitchState();
303    }
304
305    public boolean isVibrateAndSoundFeedbackRequired() {
306        return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
307    }
308
309    private boolean isSinglePointer() {
310        return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
311    }
312
313    public boolean hasDistinctMultitouch() {
314        return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
315    }
316
317    /**
318     * Updates state machine to figure out when to automatically switch back to the previous mode.
319     */
320    public void onCodeInput(int code) {
321        mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState());
322    }
323
324    public LatinKeyboardView getKeyboardView() {
325        return mKeyboardView;
326    }
327
328    public View onCreateInputView() {
329        return createInputView(mThemeIndex, true);
330    }
331
332    private View createInputView(final int newThemeIndex, final boolean forceRecreate) {
333        if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate)
334            return mCurrentInputView;
335
336        if (mKeyboardView != null) {
337            mKeyboardView.closing();
338        }
339
340        final int oldThemeIndex = mThemeIndex;
341        Utils.GCUtils.getInstance().reset();
342        boolean tryGC = true;
343        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
344            try {
345                setContextThemeWrapper(mInputMethodService, newThemeIndex);
346                mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
347                        R.layout.input_view, null);
348                tryGC = false;
349            } catch (OutOfMemoryError e) {
350                Log.w(TAG, "load keyboard failed: " + e);
351                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
352                        oldThemeIndex + "," + newThemeIndex, e);
353            } catch (InflateException e) {
354                Log.w(TAG, "load keyboard failed: " + e);
355                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(
356                        oldThemeIndex + "," + newThemeIndex, e);
357            }
358        }
359
360        mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
361        mKeyboardView.setKeyboardActionListener(mInputMethodService);
362        if (mForceNonDistinctMultitouch) {
363            mKeyboardView.setDistinctMultitouch(false);
364        }
365
366        // This always needs to be set since the accessibility state can
367        // potentially change without the input view being re-created.
368        AccessibleKeyboardViewProxy.setView(mKeyboardView);
369
370        return mCurrentInputView;
371    }
372
373    private void postSetInputView(final View newInputView) {
374        final LatinIME latinIme = mInputMethodService;
375        latinIme.mHandler.post(new Runnable() {
376            @Override
377            public void run() {
378                if (newInputView != null) {
379                    latinIme.setInputView(newInputView);
380                }
381                latinIme.updateInputViewShown();
382            }
383        });
384    }
385
386    @Override
387    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
388        if (PREF_KEYBOARD_LAYOUT.equals(key)) {
389            final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences);
390            postSetInputView(createInputView(themeIndex, false));
391        } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) {
392            postSetInputView(createInputView(mThemeIndex, true));
393        }
394    }
395
396    public void onNetworkStateChanged() {
397        if (mKeyboardView != null) {
398            mKeyboardView.updateShortcutKey(SubtypeSwitcher.getInstance().isShortcutImeReady());
399        }
400    }
401
402    public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
403        if (mIsAutoCorrectionActive != isAutoCorrection) {
404            mIsAutoCorrectionActive = isAutoCorrection;
405            if (mKeyboardView != null) {
406                mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
407            }
408        }
409    }
410}
411