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