KeyboardSwitcher.java revision 2a88440419f49d100c73e067a823390f64aba3b1
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 final KeyboardId mainKeyboardId = mKeyboardSet.getMainKeyboardId(); 139 try { 140 mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols), 141 hasDistinctMultitouch()); 142 } catch (RuntimeException e) { 143 Log.w(TAG, "loading keyboard failed: " + mainKeyboardId, e); 144 LatinImeLogger.logOnException(mainKeyboardId.toString(), e); 145 return; 146 } 147 // TODO: Should get rid of this special case handling for Phone Number layouts once we 148 // have separate layouts with unique KeyboardIds for alphabet and alphabet-shifted 149 // respectively. 150 if (mainKeyboardId.isPhoneKeyboard()) { 151 mState.setSymbolsKeyboard(); 152 } 153 updateShiftState(); 154 } 155 156 public void saveKeyboardState() { 157 if (isKeyboardAvailable()) { 158 mState.onSaveKeyboardState(); 159 } 160 } 161 162 public void onFinishInputView() { 163 mIsAutoCorrectionActive = false; 164 } 165 166 public void onHideWindow() { 167 mIsAutoCorrectionActive = false; 168 } 169 170 private void setKeyboard(final Keyboard keyboard) { 171 final Keyboard oldKeyboard = mKeyboardView.getKeyboard(); 172 mKeyboardView.setKeyboard(keyboard); 173 mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding); 174 mKeyboardView.setKeyPreviewPopupEnabled( 175 SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources), 176 SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources)); 177 mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive); 178 // If the cached keyboard had been switched to another keyboard while the language was 179 // displayed on its spacebar, it might have had arbitrary text fade factor. In such 180 // case, we should reset the text fade factor. It is also applicable to shortcut key. 181 mKeyboardView.updateSpacebar(0.0f, 182 mSubtypeSwitcher.needsToDisplayLanguage(keyboard.mId.mLocale)); 183 mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 184 final boolean localeChanged = (oldKeyboard == null) 185 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); 186 mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged); 187 } 188 189 public boolean isAlphabetMode() { 190 final Keyboard keyboard = getKeyboard(); 191 return keyboard != null && keyboard.mId.isAlphabetKeyboard(); 192 } 193 194 public boolean isInputViewShown() { 195 return mCurrentInputView != null && mCurrentInputView.isShown(); 196 } 197 198 public boolean isShiftedOrShiftLocked() { 199 final Keyboard keyboard = getKeyboard(); 200 return keyboard != null && keyboard.isShiftedOrShiftLocked(); 201 } 202 203 public boolean isManualTemporaryUpperCase() { 204 final Keyboard keyboard = getKeyboard(); 205 return keyboard != null && keyboard.isManualTemporaryUpperCase(); 206 } 207 208 public boolean isKeyboardAvailable() { 209 if (mKeyboardView != null) 210 return mKeyboardView.getKeyboard() != null; 211 return false; 212 } 213 214 public Keyboard getKeyboard() { 215 if (mKeyboardView != null) { 216 return mKeyboardView.getKeyboard(); 217 } 218 return null; 219 } 220 221 // Implements {@link KeyboardState.SwitchActions}. 222 @Override 223 public void setShifted(int shiftMode) { 224 mInputMethodService.mHandler.cancelUpdateShiftState(); 225 Keyboard keyboard = getKeyboard(); 226 if (keyboard == null) 227 return; 228 switch (shiftMode) { 229 case AUTOMATIC_SHIFT: 230 keyboard.setAutomaticTemporaryUpperCase(); 231 break; 232 case MANUAL_SHIFT: 233 keyboard.setShifted(true); 234 break; 235 case UNSHIFT: 236 keyboard.setShifted(false); 237 break; 238 } 239 mKeyboardView.invalidateAllKeys(); 240 } 241 242 // Implements {@link KeyboardState.SwitchActions}. 243 @Override 244 public void setShiftLocked(boolean shiftLocked) { 245 mInputMethodService.mHandler.cancelUpdateShiftState(); 246 Keyboard keyboard = getKeyboard(); 247 if (keyboard == null) 248 return; 249 keyboard.setShiftLocked(shiftLocked); 250 mKeyboardView.invalidateAllKeys(); 251 if (!shiftLocked) { 252 // To be able to turn off caps lock by "double tap" on shift key, we should ignore 253 // the second tap of the "double tap" from now for a while because we just have 254 // already turned off caps lock above. 255 mKeyboardView.startIgnoringDoubleTap(); 256 } 257 } 258 259 /** 260 * Toggle caps lock state triggered by user touch event. 261 */ 262 public void toggleCapsLock() { 263 mState.onToggleCapsLock(); 264 } 265 266 /** 267 * Update keyboard shift state triggered by connected EditText status change. 268 */ 269 public void updateShiftState() { 270 mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState()); 271 } 272 273 public void onPressKey(int code) { 274 mState.onPressKey(code); 275 } 276 277 public void onReleaseKey(int code, boolean withSliding) { 278 mState.onReleaseKey(code, withSliding); 279 } 280 281 public void onCancelInput() { 282 mState.onCancelInput(isSinglePointer()); 283 } 284 285 // Implements {@link KeyboardState.SwitchActions}. 286 @Override 287 public void setSymbolsKeyboard() { 288 setKeyboard(mKeyboardSet.getSymbolsKeyboard()); 289 } 290 291 // Implements {@link KeyboardState.SwitchActions}. 292 @Override 293 public void setAlphabetKeyboard() { 294 setKeyboard(mKeyboardSet.getMainKeyboard()); 295 } 296 297 // Implements {@link KeyboardState.SwitchActions}. 298 @Override 299 public void setSymbolsShiftedKeyboard() { 300 setKeyboard(mKeyboardSet.getSymbolsShiftedKeyboard()); 301 } 302 303 // Implements {@link KeyboardState.SwitchActions}. 304 @Override 305 public void requestUpdatingShiftState() { 306 mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState()); 307 } 308 309 public boolean isInMomentarySwitchState() { 310 return mState.isInMomentarySwitchState(); 311 } 312 313 public boolean isVibrateAndSoundFeedbackRequired() { 314 return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput(); 315 } 316 317 private boolean isSinglePointer() { 318 return mKeyboardView != null && mKeyboardView.getPointerCount() == 1; 319 } 320 321 public boolean hasDistinctMultitouch() { 322 return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); 323 } 324 325 /** 326 * Updates state machine to figure out when to automatically snap back to the previous mode. 327 */ 328 public void onCodeInput(int code) { 329 mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState()); 330 } 331 332 public LatinKeyboardView getKeyboardView() { 333 return mKeyboardView; 334 } 335 336 public View onCreateInputView() { 337 return createInputView(mThemeIndex, true); 338 } 339 340 private View createInputView(final int newThemeIndex, final boolean forceRecreate) { 341 if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate) 342 return mCurrentInputView; 343 344 if (mKeyboardView != null) { 345 mKeyboardView.closing(); 346 } 347 348 final int oldThemeIndex = mThemeIndex; 349 Utils.GCUtils.getInstance().reset(); 350 boolean tryGC = true; 351 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 352 try { 353 setContextThemeWrapper(mInputMethodService, newThemeIndex); 354 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( 355 R.layout.input_view, null); 356 tryGC = false; 357 } catch (OutOfMemoryError e) { 358 Log.w(TAG, "load keyboard failed: " + e); 359 tryGC = Utils.GCUtils.getInstance().tryGCOrWait( 360 oldThemeIndex + "," + newThemeIndex, e); 361 } catch (InflateException e) { 362 Log.w(TAG, "load keyboard failed: " + e); 363 tryGC = Utils.GCUtils.getInstance().tryGCOrWait( 364 oldThemeIndex + "," + newThemeIndex, e); 365 } 366 } 367 368 mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); 369 mKeyboardView.setKeyboardActionListener(mInputMethodService); 370 if (mForceNonDistinctMultitouch) { 371 mKeyboardView.setDistinctMultitouch(false); 372 } 373 374 // This always needs to be set since the accessibility state can 375 // potentially change without the input view being re-created. 376 AccessibleKeyboardViewProxy.setView(mKeyboardView); 377 378 return mCurrentInputView; 379 } 380 381 private void postSetInputView(final View newInputView) { 382 final LatinIME latinIme = mInputMethodService; 383 latinIme.mHandler.post(new Runnable() { 384 @Override 385 public void run() { 386 if (newInputView != null) { 387 latinIme.setInputView(newInputView); 388 } 389 latinIme.updateInputViewShown(); 390 } 391 }); 392 } 393 394 @Override 395 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 396 if (PREF_KEYBOARD_LAYOUT.equals(key)) { 397 final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences); 398 postSetInputView(createInputView(themeIndex, false)); 399 } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) { 400 postSetInputView(createInputView(mThemeIndex, true)); 401 } 402 } 403 404 public void onNetworkStateChanged() { 405 if (mKeyboardView != null) { 406 mKeyboardView.updateShortcutKey(SubtypeSwitcher.getInstance().isShortcutImeReady()); 407 } 408 } 409 410 public void onAutoCorrectionStateChanged(boolean isAutoCorrection) { 411 if (mIsAutoCorrectionActive != isAutoCorrection) { 412 mIsAutoCorrectionActive = isAutoCorrection; 413 if (mKeyboardView != null) { 414 mKeyboardView.updateAutoCorrectionState(isAutoCorrection); 415 } 416 } 417 } 418} 419