• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.inputmethod.keyboard;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.content.res.Resources;
22 import android.util.Log;
23 import android.view.ContextThemeWrapper;
24 import android.view.InflateException;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.inputmethod.EditorInfo;
28 
29 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
30 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
31 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
32 import com.android.inputmethod.keyboard.internal.KeyboardState;
33 import com.android.inputmethod.latin.DebugSettings;
34 import com.android.inputmethod.latin.ImfUtils;
35 import com.android.inputmethod.latin.InputView;
36 import com.android.inputmethod.latin.LatinIME;
37 import com.android.inputmethod.latin.LatinImeLogger;
38 import com.android.inputmethod.latin.R;
39 import com.android.inputmethod.latin.SettingsValues;
40 import com.android.inputmethod.latin.SubtypeSwitcher;
41 import com.android.inputmethod.latin.Utils;
42 
43 public 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 
KeyboardTheme(String name, int themeId, int styleId)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 
getInstance()91     public static KeyboardSwitcher getInstance() {
92         return sInstance;
93     }
94 
KeyboardSwitcher()95     private KeyboardSwitcher() {
96         // Intentional empty constructor for singleton.
97     }
98 
init(LatinIME latinIme, SharedPreferences prefs)99     public static void init(LatinIME latinIme, SharedPreferences prefs) {
100         sInstance.initInternal(latinIme, prefs);
101     }
102 
initInternal(LatinIME latinIme, SharedPreferences prefs)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 
getKeyboardTheme(Context context, SharedPreferences prefs)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 
setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme)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 
loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues)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 
saveKeyboardState()157     public void saveKeyboardState() {
158         if (getKeyboard() != null) {
159             mState.onSaveKeyboardState();
160         }
161     }
162 
onFinishInputView()163     public void onFinishInputView() {
164         mIsAutoCorrectionActive = false;
165     }
166 
onHideWindow()167     public void onHideWindow() {
168         mIsAutoCorrectionActive = false;
169     }
170 
setKeyboard(final Keyboard keyboard)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 
getKeyboard()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      */
updateShiftState()198     public void updateShiftState() {
199         mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
200     }
201 
onPressKey(int code)202     public void onPressKey(int code) {
203         if (isVibrateAndSoundFeedbackRequired()) {
204             mLatinIME.hapticAndAudioFeedback(code);
205         }
206         mState.onPressKey(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState());
207     }
208 
onReleaseKey(int code, boolean withSliding)209     public void onReleaseKey(int code, boolean withSliding) {
210         mState.onReleaseKey(code, withSliding);
211     }
212 
onCancelInput()213     public void onCancelInput() {
214         mState.onCancelInput(isSinglePointer());
215     }
216 
217     // Implements {@link KeyboardState.SwitchActions}.
218     @Override
setAlphabetKeyboard()219     public void setAlphabetKeyboard() {
220         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET));
221     }
222 
223     // Implements {@link KeyboardState.SwitchActions}.
224     @Override
setAlphabetManualShiftedKeyboard()225     public void setAlphabetManualShiftedKeyboard() {
226         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED));
227     }
228 
229     // Implements {@link KeyboardState.SwitchActions}.
230     @Override
setAlphabetAutomaticShiftedKeyboard()231     public void setAlphabetAutomaticShiftedKeyboard() {
232         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED));
233     }
234 
235     // Implements {@link KeyboardState.SwitchActions}.
236     @Override
setAlphabetShiftLockedKeyboard()237     public void setAlphabetShiftLockedKeyboard() {
238         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED));
239     }
240 
241     // Implements {@link KeyboardState.SwitchActions}.
242     @Override
setAlphabetShiftLockShiftedKeyboard()243     public void setAlphabetShiftLockShiftedKeyboard() {
244         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED));
245     }
246 
247     // Implements {@link KeyboardState.SwitchActions}.
248     @Override
setSymbolsKeyboard()249     public void setSymbolsKeyboard() {
250         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS));
251     }
252 
253     // Implements {@link KeyboardState.SwitchActions}.
254     @Override
setSymbolsShiftedKeyboard()255     public void setSymbolsShiftedKeyboard() {
256         setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED));
257     }
258 
259     // Implements {@link KeyboardState.SwitchActions}.
260     @Override
requestUpdatingShiftState()261     public void requestUpdatingShiftState() {
262         mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
263     }
264 
265     // Implements {@link KeyboardState.SwitchActions}.
266     @Override
startDoubleTapTimer()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
cancelDoubleTapTimer()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
isInDoubleTapTimeout()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
startLongPressTimer(int code)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
cancelLongPressTimer()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
hapticAndAudioFeedback(int code)315     public void hapticAndAudioFeedback(int code) {
316         mLatinIME.hapticAndAudioFeedback(code);
317     }
318 
onLongPressTimeout(int code)319     public void onLongPressTimeout(int code) {
320         mState.onLongPressTimeout(code);
321     }
322 
isInMomentarySwitchState()323     public boolean isInMomentarySwitchState() {
324         return mState.isInMomentarySwitchState();
325     }
326 
isVibrateAndSoundFeedbackRequired()327     private boolean isVibrateAndSoundFeedbackRequired() {
328         return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
329     }
330 
isSinglePointer()331     private boolean isSinglePointer() {
332         return mKeyboardView != null && mKeyboardView.getPointerCount() == 1;
333     }
334 
hasDistinctMultitouch()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      */
onCodeInput(int code)342     public void onCodeInput(int code) {
343         mState.onCodeInput(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState());
344     }
345 
getKeyboardView()346     public LatinKeyboardView getKeyboardView() {
347         return mKeyboardView;
348     }
349 
onCreateInputView()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 
onNetworkStateChanged()385     public void onNetworkStateChanged() {
386         if (mKeyboardView != null) {
387             mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady());
388         }
389     }
390 
onAutoCorrectionStateChanged(boolean isAutoCorrection)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