KeyboardSwitcher.java revision fd60b2f97035382b14dce207b3613711982a613e
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.KeyboardLayoutSet.KeyboardLayoutSetException;
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.ImfUtils;
35import com.android.inputmethod.latin.InputView;
36import com.android.inputmethod.latin.LatinIME;
37import com.android.inputmethod.latin.LatinImeLogger;
38import com.android.inputmethod.latin.R;
39import com.android.inputmethod.latin.SettingsValues;
40import com.android.inputmethod.latin.SubtypeSwitcher;
41import com.android.inputmethod.latin.Utils;
42
43public class KeyboardSwitcher implements KeyboardState.SwitchActions {
44    private static final String TAG = KeyboardSwitcher.class.getSimpleName();
45
46    public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
47
48    static class KeyboardTheme {
49        public final String mName;
50        public final int mThemeId;
51        public final int mStyleId;
52
53        public KeyboardTheme(String name, int themeId, int styleId) {
54            mName = name;
55            mThemeId = themeId;
56            mStyleId = styleId;
57        }
58    }
59
60    private static final KeyboardTheme[] KEYBOARD_THEMES = {
61        new KeyboardTheme("Basic",            0, R.style.KeyboardTheme),
62        new KeyboardTheme("HighContrast",     1, R.style.KeyboardTheme_HighContrast),
63        new KeyboardTheme("Stone",            6, R.style.KeyboardTheme_Stone),
64        new KeyboardTheme("Stne.Bold",        7, R.style.KeyboardTheme_Stone_Bold),
65        new KeyboardTheme("GingerBread",      8, R.style.KeyboardTheme_Gingerbread),
66        new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich),
67    };
68
69    private SubtypeSwitcher mSubtypeSwitcher;
70    private SharedPreferences mPrefs;
71    private boolean mForceNonDistinctMultitouch;
72
73    private InputView mCurrentInputView;
74    private LatinKeyboardView mKeyboardView;
75    private LatinIME mLatinIME;
76    private Resources mResources;
77
78    private KeyboardState mState;
79
80    private KeyboardLayoutSet mKeyboardLayoutSet;
81
82    /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
83     * what user actually typed. */
84    private boolean mIsAutoCorrectionActive;
85
86    private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[0];
87    private Context mThemeContext;
88
89    private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
90
91    public static KeyboardSwitcher getInstance() {
92        return sInstance;
93    }
94
95    private KeyboardSwitcher() {
96        // Intentional empty constructor for singleton.
97    }
98
99    public static void init(LatinIME latinIme, SharedPreferences prefs) {
100        sInstance.initInternal(latinIme, prefs);
101    }
102
103    private void initInternal(LatinIME latinIme, SharedPreferences prefs) {
104        mLatinIME = latinIme;
105        mResources = latinIme.getResources();
106        mPrefs = prefs;
107        mSubtypeSwitcher = SubtypeSwitcher.getInstance();
108        mState = new KeyboardState(this);
109        setContextThemeWrapper(latinIme, getKeyboardTheme(latinIme, prefs));
110        mForceNonDistinctMultitouch = prefs.getBoolean(
111                DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false);
112    }
113
114    private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) {
115        final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index);
116        final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex);
117        try {
118            final int index = Integer.valueOf(themeIndex);
119            if (index >= 0 && index < KEYBOARD_THEMES.length) {
120                return KEYBOARD_THEMES[index];
121            }
122        } catch (NumberFormatException e) {
123            // Format error, keyboard theme is default to 0.
124        }
125        Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0");
126        return KEYBOARD_THEMES[0];
127    }
128
129    private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) {
130        if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
131            mKeyboardTheme = keyboardTheme;
132            mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
133            KeyboardLayoutSet.clearKeyboardCache();
134        }
135    }
136
137    public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) {
138        final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
139                mThemeContext, editorInfo);
140        builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation,
141                mThemeContext.getResources().getDisplayMetrics().widthPixels);
142        builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype());
143        builder.setOptions(
144                settingsValues.isVoiceKeyEnabled(editorInfo),
145                settingsValues.isVoiceKeyOnMain(),
146                settingsValues.isLanguageSwitchKeyEnabled(mThemeContext));
147        mKeyboardLayoutSet = builder.build();
148        try {
149            mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols));
150        } catch (KeyboardLayoutSetException e) {
151            Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
152            LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
153            return;
154        }
155    }
156
157    public void saveKeyboardState() {
158        if (getKeyboard() != null) {
159            mState.onSaveKeyboardState();
160        }
161    }
162
163    public void onFinishInputView() {
164        mIsAutoCorrectionActive = false;
165    }
166
167    public void onHideWindow() {
168        mIsAutoCorrectionActive = false;
169    }
170
171    private void setKeyboard(final Keyboard keyboard) {
172        final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
173        mKeyboardView.setKeyboard(keyboard);
174        mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding);
175        mKeyboardView.setKeyPreviewPopupEnabled(
176                SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources),
177                SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
178        mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive);
179        mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
180        final boolean subtypeChanged = (oldKeyboard == null)
181                || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
182        final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage(
183                keyboard.mId.mLocale);
184        mKeyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage,
185                ImfUtils.hasMultipleEnabledIMEsOrSubtypes(mLatinIME, true));
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(mLatinIME.getCurrentAutoCapsState());
200    }
201
202    public void onPressKey(int code) {
203        if (isVibrateAndSoundFeedbackRequired()) {
204            mLatinIME.hapticAndAudioFeedback(code);
205        }
206        mState.onPressKey(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState());
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(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
221    }
222
223    // Implements {@link KeyboardState.SwitchActions}.
224    @Override
225    public void setAlphabetManualShiftedKeyboard() {
226        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
227    }
228
229    // Implements {@link KeyboardState.SwitchActions}.
230    @Override
231    public void setAlphabetAutomaticShiftedKeyboard() {
232        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
233    }
234
235    // Implements {@link KeyboardState.SwitchActions}.
236    @Override
237    public void setAlphabetShiftLockedKeyboard() {
238        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
239    }
240
241    // Implements {@link KeyboardState.SwitchActions}.
242    @Override
243    public void setAlphabetShiftLockShiftedKeyboard() {
244        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
245    }
246
247    // Implements {@link KeyboardState.SwitchActions}.
248    @Override
249    public void setSymbolsKeyboard() {
250        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
251    }
252
253    // Implements {@link KeyboardState.SwitchActions}.
254    @Override
255    public void setSymbolsShiftedKeyboard() {
256        setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
257    }
258
259    // Implements {@link KeyboardState.SwitchActions}.
260    @Override
261    public void requestUpdatingShiftState() {
262        mState.onUpdateShiftState(mLatinIME.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        mLatinIME.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(), mLatinIME.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(mLatinIME, 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(mLatinIME);
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(mSubtypeSwitcher.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