KeyboardSwitcher.java revision cb389ef0d6e6eec737c249e1729c2a2cdc30f341
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(mSubtypeSwitcher.getCurrentSubtype());
141        builder.setOptions(
142                settingsValues.isVoiceKeyEnabled(editorInfo),
143                settingsValues.isVoiceKeyOnMain(),
144                settingsValues.isLanguageSwitchKeyEnabled(mThemeContext));
145        mKeyboardSet = builder.build();
146        try {
147            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
148        } catch (KeyboardSetException e) {
149            Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
150            LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
151            return;
152        }
153    }
154
155    public void saveKeyboardState() {
156        if (getKeyboard() != null) {
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        mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
178        final boolean subtypeChanged = (oldKeyboard == null)
179                || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
180        final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
181                keyboard.mId.mLocale);
182        mKeyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage);
183    }
184
185    public Keyboard getKeyboard() {
186        if (mKeyboardView != null) {
187            return mKeyboardView.getKeyboard();
188        }
189        return null;
190    }
191
192    /**
193     * Update keyboard shift state triggered by connected EditText status change.
194     */
195    public void updateShiftState() {
196        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
197    }
198
199    public void onPressKey(int code) {
200        if (isVibrateAndSoundFeedbackRequired()) {
201            mInputMethodService.hapticAndAudioFeedback(code);
202        }
203        mState.onPressKey(code);
204    }
205
206    public void onReleaseKey(int code, boolean withSliding) {
207        mState.onReleaseKey(code, withSliding);
208    }
209
210    public void onCancelInput() {
211        mState.onCancelInput(isSinglePointer());
212    }
213
214    // Implements {@link KeyboardState.SwitchActions}.
215    @Override
216    public void setAlphabetKeyboard() {
217        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
218    }
219
220    // Implements {@link KeyboardState.SwitchActions}.
221    @Override
222    public void setAlphabetManualShiftedKeyboard() {
223        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
224    }
225
226    // Implements {@link KeyboardState.SwitchActions}.
227    @Override
228    public void setAlphabetAutomaticShiftedKeyboard() {
229        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
230    }
231
232    // Implements {@link KeyboardState.SwitchActions}.
233    @Override
234    public void setAlphabetShiftLockedKeyboard() {
235        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
236    }
237
238    // Implements {@link KeyboardState.SwitchActions}.
239    @Override
240    public void setAlphabetShiftLockShiftedKeyboard() {
241        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
242    }
243
244    // Implements {@link KeyboardState.SwitchActions}.
245    @Override
246    public void setSymbolsKeyboard() {
247        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
248    }
249
250    // Implements {@link KeyboardState.SwitchActions}.
251    @Override
252    public void setSymbolsShiftedKeyboard() {
253        setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
254    }
255
256    // Implements {@link KeyboardState.SwitchActions}.
257    @Override
258    public void requestUpdatingShiftState() {
259        mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState());
260    }
261
262    // Implements {@link KeyboardState.SwitchActions}.
263    @Override
264    public void startDoubleTapTimer() {
265        final LatinKeyboardView keyboardView = getKeyboardView();
266        if (keyboardView != null) {
267            final TimerProxy timer = keyboardView.getTimerProxy();
268            timer.startDoubleTapTimer();
269        }
270    }
271
272    // Implements {@link KeyboardState.SwitchActions}.
273    @Override
274    public void cancelDoubleTapTimer() {
275        final LatinKeyboardView keyboardView = getKeyboardView();
276        if (keyboardView != null) {
277            final TimerProxy timer = keyboardView.getTimerProxy();
278            timer.cancelDoubleTapTimer();
279        }
280    }
281
282    // Implements {@link KeyboardState.SwitchActions}.
283    @Override
284    public boolean isInDoubleTapTimeout() {
285        final LatinKeyboardView keyboardView = getKeyboardView();
286        return (keyboardView != null)
287                ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false;
288    }
289
290    // Implements {@link KeyboardState.SwitchActions}.
291    @Override
292    public void startLongPressTimer(int code) {
293        final LatinKeyboardView keyboardView = getKeyboardView();
294        if (keyboardView != null) {
295            final TimerProxy timer = keyboardView.getTimerProxy();
296            timer.startLongPressTimer(code);
297        }
298    }
299
300    // Implements {@link KeyboardState.SwitchActions}.
301    @Override
302    public void cancelLongPressTimer() {
303        final LatinKeyboardView keyboardView = getKeyboardView();
304        if (keyboardView != null) {
305            final TimerProxy timer = keyboardView.getTimerProxy();
306            timer.cancelLongPressTimer();
307        }
308    }
309
310    // Implements {@link KeyboardState.SwitchActions}.
311    @Override
312    public void hapticAndAudioFeedback(int code) {
313        mInputMethodService.hapticAndAudioFeedback(code);
314    }
315
316    public void onLongPressTimeout(int code) {
317        mState.onLongPressTimeout(code);
318    }
319
320    public boolean isInMomentarySwitchState() {
321        return mState.isInMomentarySwitchState();
322    }
323
324    private boolean isVibrateAndSoundFeedbackRequired() {
325        return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
326    }
327
328    private boolean isSinglePointer() {
329        return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
330    }
331
332    public boolean hasDistinctMultitouch() {
333        return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch();
334    }
335
336    /**
337     * Updates state machine to figure out when to automatically switch back to the previous mode.
338     */
339    public void onCodeInput(int code) {
340        mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState());
341    }
342
343    public LatinKeyboardView getKeyboardView() {
344        return mKeyboardView;
345    }
346
347    public View onCreateInputView() {
348        if (mKeyboardView != null) {
349            mKeyboardView.closing();
350        }
351
352        Utils.GCUtils.getInstance().reset();
353        boolean tryGC = true;
354        for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
355            try {
356                setContextThemeWrapper(mInputMethodService, mKeyboardTheme);
357                mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
358                        R.layout.input_view, null);
359                tryGC = false;
360            } catch (OutOfMemoryError e) {
361                Log.w(TAG, "load keyboard failed: " + e);
362                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
363            } catch (InflateException e) {
364                Log.w(TAG, "load keyboard failed: " + e);
365                tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e);
366            }
367        }
368
369        mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
370        mKeyboardView.setKeyboardActionListener(mInputMethodService);
371        if (mForceNonDistinctMultitouch) {
372            mKeyboardView.setDistinctMultitouch(false);
373        }
374
375        // This always needs to be set since the accessibility state can
376        // potentially change without the input view being re-created.
377        AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView);
378
379        return mCurrentInputView;
380    }
381
382    public void onNetworkStateChanged() {
383        if (mKeyboardView != null) {
384            mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
385        }
386    }
387
388    public void onAutoCorrectionStateChanged(boolean isAutoCorrection) {
389        if (mIsAutoCorrectionActive != isAutoCorrection) {
390            mIsAutoCorrectionActive = isAutoCorrection;
391            if (mKeyboardView != null) {
392                mKeyboardView.updateAutoCorrectionState(isAutoCorrection);
393            }
394        }
395    }
396}
397