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