KeyboardSwitcher.java revision e07db71d26fd9cb2c0141d76bfd3cd01bb001899
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.KeyboardSet.KeyboardSetException;
31import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
32import com.android.inputmethod.keyboard.internal.KeyboardState;
33import com.android.inputmethod.latin.DebugSettings;
34import com.android.inputmethod.latin.InputView;
35import com.android.inputmethod.latin.LatinIME;
36import com.android.inputmethod.latin.LatinImeLogger;
37import com.android.inputmethod.latin.R;
38import com.android.inputmethod.latin.SettingsValues;
39import com.android.inputmethod.latin.SubtypeSwitcher;
40import com.android.inputmethod.latin.Utils;
41
42public class KeyboardSwitcher implements KeyboardState.SwitchActions {
43    private static final String TAG = KeyboardSwitcher.class.getSimpleName();
44
45    public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
46
47    static class KeyboardTheme {
48        public final String mName;
49        public final int mThemeId;
50        public final int mStyleId;
51
52        public KeyboardTheme(String name, int themeId, int styleId) {
53            mName = name;
54            mThemeId = themeId;
55            mStyleId = styleId;
56        }
57    }
58
59    private static final KeyboardTheme[] KEYBOARD_THEMES = {
60        new KeyboardTheme("Basic",            0, R.style.KeyboardTheme),
61        new KeyboardTheme("HighContrast",     1, R.style.KeyboardTheme_HighContrast),
62        new KeyboardTheme("Stone",            6, R.style.KeyboardTheme_Stone),
63        new KeyboardTheme("Stne.Bold",        7, R.style.KeyboardTheme_Stone_Bold),
64        new KeyboardTheme("GingerBread",      8, R.style.KeyboardTheme_Gingerbread),
65        new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
66    };
67
68    private SubtypeSwitcher mSubtypeSwitcher;
69    private SharedPreferences mPrefs;
70    private boolean mForceNonDistinctMultitouch;
71
72    private InputView mCurrentInputView;
73    private LatinKeyboardView mKeyboardView;
74    private LatinIME mInputMethodService;
75    private Resources mResources;
76
77    private KeyboardState mState;
78
79    private KeyboardSet mKeyboardSet;
80
81    /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
82     * what user actually typed. */
83    private boolean mIsAutoCorrectionActive;
84
85    private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[0];
86    private Context mThemeContext;
87
88    private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
89
90    public static KeyboardSwitcher getInstance() {
91        return sInstance;
92    }
93
94    private KeyboardSwitcher() {
95        // Intentional empty constructor for singleton.
96    }
97
98    public static void init(LatinIME ims, SharedPreferences prefs) {
99        sInstance.initInternal(ims, prefs);
100    }
101
102    private void initInternal(LatinIME ims, SharedPreferences prefs) {
103        mInputMethodService = ims;
104        mResources = ims.getResources();
105        mPrefs = prefs;
106        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
107        mState = new KeyboardState(this);
108        setContextThemeWrapper(ims, getKeyboardTheme(ims, prefs));
109        mForceNonDistinctMultitouch = prefs.getBoolean(
110                DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false);
111    }
112
113    private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) {
114        final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index);
115        final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex);
116        try {
117            final int index = Integer.valueOf(themeIndex);
118            if (index >= 0 && index < KEYBOARD_THEMES.length) {
119                return KEYBOARD_THEMES[index];
120            }
121        } catch (NumberFormatException e) {
122            // Format error, keyboard theme is default to 0.
123        }
124        Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0");
125        return KEYBOARD_THEMES[0];
126    }
127
128    private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) {
129        if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
130            mKeyboardTheme = keyboardTheme;
131            mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
132            KeyboardSet.clearKeyboardCache();
133        }
134    }
135
136    public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
137        final KeyboardSet.Builder builder = new KeyboardSet.Builder(mThemeContext, editorInfo);
138        builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation,
139                mThemeContext.getResources().getDisplayMetrics().widthPixels);
140        builder.setSubtype(
141                mSubtypeSwitcher.getInputLocale(),
142                mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(
143                        LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE));
144        builder.setOptions(
145                settingsValues.isVoiceKeyEnabled(editorInfo),
146                settingsValues.isVoiceKeyOnMain(),
147                settingsValues.isLanguageSwitchKeyEnabled(mThemeContext));
148        mKeyboardSet = builder.build();
149        try {
150            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
151        } catch (KeyboardSetException e) {
152            Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
153            LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
154            return;
155        }
156    }
157
158    public void saveKeyboardState() {
159        if (getKeyboard() != null) {
160            mState.onSaveKeyboardState();
161        }
162    }
163
164    public void onFinishInputView() {
165        mIsAutoCorrectionActive = false;
166    }
167
168    public void onHideWindow() {
169        mIsAutoCorrectionActive = false;
170    }
171
172    private void setKeyboard(final Keyboard keyboard) {
173        final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
174        mKeyboardView.setKeyboard(keyboard);
175        mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
176        mKeyboardView.setKeyPreviewPopupEnabled(
177                SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
178                SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
179        mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
180        mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
181        final boolean subtypeChanged = (oldKeyboard == null)
182                || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
183        final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
184                keyboard.mId.mLocale);
185        mKeyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage);
186    }
187
188    public Keyboard getKeyboard() {
189        if (mKeyboardView != null) {
190            return mKeyboardView.getKeyboard();
191        }
192        return null;
193    }
194
195    /**
196     * Update keyboard shift state triggered by connected EditText status change.
197     */
198    public void updateShiftState() {
199        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
200    }
201
202    public void onPressKey(int code) {
203        if (isVibrateAndSoundFeedbackRequired()) {
204            mInputMethodService.hapticAndAudioFeedback(code);
205        }
206        mState.onPressKey(code);
207    }
208
209    public void onReleaseKey(int code, boolean withSliding) {
210        mState.onReleaseKey(code, withSliding);
211    }
212
213    public void onCancelInput() {
214        mState.onCancelInput(isSinglePointer());
215    }
216
217    // Implements {@link KeyboardState.SwitchActions}.
218    @Override
219    public void setAlphabetKeyboard() {
220        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
221    }
222
223    // Implements {@link KeyboardState.SwitchActions}.
224    @Override
225    public void setAlphabetManualShiftedKeyboard() {
226        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
227    }
228
229    // Implements {@link KeyboardState.SwitchActions}.
230    @Override
231    public void setAlphabetAutomaticShiftedKeyboard() {
232        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
233    }
234
235    // Implements {@link KeyboardState.SwitchActions}.
236    @Override
237    public void setAlphabetShiftLockedKeyboard() {
238        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
239    }
240
241    // Implements {@link KeyboardState.SwitchActions}.
242    @Override
243    public void setAlphabetShiftLockShiftedKeyboard() {
244        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
245    }
246
247    // Implements {@link KeyboardState.SwitchActions}.
248    @Override
249    public void setSymbolsKeyboard() {
250        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
251    }
252
253    // Implements {@link KeyboardState.SwitchActions}.
254    @Override
255    public void setSymbolsShiftedKeyboard() {
256        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
257    }
258
259    // Implements {@link KeyboardState.SwitchActions}.
260    @Override
261    public void requestUpdatingShiftState() {
262        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
263    }
264
265    // Implements {@link KeyboardState.SwitchActions}.
266    @Override
267    public void startDoubleTapTimer() {
268        final LatinKeyboardView keyboardView = getKeyboardView();
269        if (keyboardView != null) {
270            final TimerProxy timer = keyboardView.getTimerProxy();
271            timer.startDoubleTapTimer();
272        }
273    }
274
275    // Implements {@link KeyboardState.SwitchActions}.
276    @Override
277    public void cancelDoubleTapTimer() {
278        final LatinKeyboardView keyboardView = getKeyboardView();
279        if (keyboardView != null) {
280            final TimerProxy timer = keyboardView.getTimerProxy();
281            timer.cancelDoubleTapTimer();
282        }
283    }
284
285    // Implements {@link KeyboardState.SwitchActions}.
286    @Override
287    public boolean isInDoubleTapTimeout() {
288        final LatinKeyboardView keyboardView = getKeyboardView();
289        return (keyboardView != null)
290                ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false;
291    }
292
293    // Implements {@link KeyboardState.SwitchActions}.
294    @Override
295    public void startLongPressTimer(int code) {
296        final LatinKeyboardView keyboardView = getKeyboardView();
297        if (keyboardView != null) {
298            final TimerProxy timer = keyboardView.getTimerProxy();
299            timer.startLongPressTimer(code);
300        }
301    }
302
303    // Implements {@link KeyboardState.SwitchActions}.
304    @Override
305    public void cancelLongPressTimer() {
306        final LatinKeyboardView keyboardView = getKeyboardView();
307        if (keyboardView != null) {
308            final TimerProxy timer = keyboardView.getTimerProxy();
309            timer.cancelLongPressTimer();
310        }
311    }
312
313    // Implements {@link KeyboardState.SwitchActions}.
314    @Override
315    public void hapticAndAudioFeedback(int code) {
316        mInputMethodService.hapticAndAudioFeedback(code);
317    }
318
319    public void onLongPressTimeout(int code) {
320        mState.onLongPressTimeout(code);
321    }
322
323    public boolean isInMomentarySwitchState() {
324        return mState.isInMomentarySwitchState();
325    }
326
327    private boolean isVibrateAndSoundFeedbackRequired() {
328        return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
329    }
330
331    private boolean isSinglePointer() {
332        return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
333    }
334
335    public boolean hasDistinctMultitouch() {
336        return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
337    }
338
339    /**
340     * Updates state machine to figure out when to automatically switch back to the previous mode.
341     */
342    public void onCodeInput(int code) {
343        mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState());
344    }
345
346    public LatinKeyboardView getKeyboardView() {
347        return mKeyboardView;
348    }
349
350    public View onCreateInputView() {
351        if (mKeyboardView != null) {
352            mKeyboardView.closing();
353        }
354
355        Utils.GCUtils.getInstance().reset();
356        boolean tryGC = true;
357        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
358            try {
359                setContextThemeWrapper(mInputMethodService, mKeyboardTheme);
360                mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
361                        R.layout.input_view, null);
362                tryGC = false;
363            } catch (OutOfMemoryError e) {
364                Log.w(TAG, "load keyboard failed: " + e);
365                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
366            } catch (InflateException e) {
367                Log.w(TAG, "load keyboard failed: " + e);
368                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
369            }
370        }
371
372        mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
373        mKeyboardView.setKeyboardActionListener(mInputMethodService);
374        if (mForceNonDistinctMultitouch) {
375            mKeyboardView.setDistinctMultitouch(false);
376        }
377
378        // This always needs to be set since the accessibility state can
379        // potentially change without the input view being re-created.
380        AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView);
381
382        return mCurrentInputView;
383    }
384
385    public void onNetworkStateChanged() {
386        if (mKeyboardView != null) {
387            mKeyboardView.updateShortcutKey(SubtypeSwitcher.getInstance().isShortcutImeReady());
388        }
389    }
390
391    public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
392        if (mIsAutoCorrectionActive != isAutoCorrection) {
393            mIsAutoCorrectionActive = isAutoCorrection;
394            if (mKeyboardView != null) {
395                mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
396            }
397        }
398    }
399}
400