KeyboardSwitcher.java revision 645128af712961456a42cbcc34c0cdf5f0b40a83
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.InputView; 32import com.android.inputmethod.latin.LatinIME; 33import com.android.inputmethod.latin.LatinImeLogger; 34import com.android.inputmethod.latin.R; 35import com.android.inputmethod.latin.Settings; 36import com.android.inputmethod.latin.SettingsValues; 37import com.android.inputmethod.latin.SubtypeSwitcher; 38import com.android.inputmethod.latin.Utils; 39 40public class KeyboardSwitcher implements KeyboardState.SwitchActions, 41 SharedPreferences.OnSharedPreferenceChangeListener { 42 private static final String TAG = KeyboardSwitcher.class.getSimpleName(); 43 44 public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916"; 45 private static final int[] KEYBOARD_THEMES = { 46 R.style.KeyboardTheme, 47 R.style.KeyboardTheme_HighContrast, 48 R.style.KeyboardTheme_Stone, 49 R.style.KeyboardTheme_Stone_Bold, 50 R.style.KeyboardTheme_Gingerbread, 51 R.style.KeyboardTheme_IceCreamSandwich, 52 }; 53 54 private SubtypeSwitcher mSubtypeSwitcher; 55 private SharedPreferences mPrefs; 56 57 private InputView mCurrentInputView; 58 private LatinKeyboardView mKeyboardView; 59 private LatinIME mInputMethodService; 60 private Resources mResources; 61 62 private KeyboardState mState; 63 64 private KeyboardSet mKeyboardSet; 65 66 /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of 67 * what user actually typed. */ 68 private boolean mIsAutoCorrectionActive; 69 70 private int mThemeIndex = -1; 71 private Context mThemeContext; 72 73 private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); 74 75 public static KeyboardSwitcher getInstance() { 76 return sInstance; 77 } 78 79 private KeyboardSwitcher() { 80 // Intentional empty constructor for singleton. 81 } 82 83 public static void init(LatinIME ims, SharedPreferences prefs) { 84 sInstance.initInternal(ims, prefs); 85 } 86 87 private void initInternal(LatinIME ims, SharedPreferences prefs) { 88 mInputMethodService = ims; 89 mResources = ims.getResources(); 90 mPrefs = prefs; 91 mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 92 mState = new KeyboardState(this); 93 setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs)); 94 prefs.registerOnSharedPreferenceChangeListener(this); 95 } 96 97 private static int getKeyboardThemeIndex(Context context, SharedPreferences prefs) { 98 final String defaultThemeId = context.getString(R.string.config_default_keyboard_theme_id); 99 final String themeId = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultThemeId); 100 try { 101 final int themeIndex = Integer.valueOf(themeId); 102 if (themeIndex >= 0 && themeIndex < KEYBOARD_THEMES.length) 103 return themeIndex; 104 } catch (NumberFormatException e) { 105 // Format error, keyboard theme is default to 0. 106 } 107 Log.w(TAG, "Illegal keyboard theme in preference: " + themeId + ", default to 0"); 108 return 0; 109 } 110 111 private void setContextThemeWrapper(Context context, int themeIndex) { 112 if (mThemeIndex != themeIndex) { 113 mThemeIndex = themeIndex; 114 mThemeContext = new ContextThemeWrapper(context, KEYBOARD_THEMES[themeIndex]); 115 KeyboardSet.clearKeyboardCache(); 116 } 117 } 118 119 public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) { 120 mKeyboardSet = new KeyboardSet.Builder(mThemeContext, editorInfo, settingsValues) 121 .build(); 122 final KeyboardId mainKeyboardId = mKeyboardSet.getMainKeyboardId(); 123 try { 124 mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols), 125 hasDistinctMultitouch()); 126 } catch (RuntimeException e) { 127 Log.w(TAG, "loading keyboard failed: " + mainKeyboardId, e); 128 LatinImeLogger.logOnException(mainKeyboardId.toString(), e); 129 return; 130 } 131 // TODO: Should get rid of this special case handling for Phone Number layouts once we 132 // have separate layouts with unique KeyboardIds for alphabet and alphabet-shifted 133 // respectively. 134 if (mainKeyboardId.isPhoneKeyboard()) { 135 mState.onToggleAlphabetAndSymbols(); 136 } 137 } 138 139 public void saveKeyboardState() { 140 if (isKeyboardAvailable()) { 141 mState.onSaveKeyboardState(); 142 } 143 } 144 145 public void onFinishInputView() { 146 mIsAutoCorrectionActive = false; 147 } 148 149 public void onHideWindow() { 150 mIsAutoCorrectionActive = false; 151 } 152 153 private void setKeyboard(final Keyboard keyboard) { 154 final Keyboard oldKeyboard = mKeyboardView.getKeyboard(); 155 mKeyboardView.setKeyboard(keyboard); 156 mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding); 157 mKeyboardView.setKeyPreviewPopupEnabled( 158 SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources), 159 SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources)); 160 final boolean localeChanged = (oldKeyboard == null) 161 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); 162 if (keyboard instanceof LatinKeyboard) { 163 final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard; 164 latinKeyboard.updateAutoCorrectionState(mIsAutoCorrectionActive); 165 // If the cached keyboard had been switched to another keyboard while the language was 166 // displayed on its spacebar, it might have had arbitrary text fade factor. In such 167 // case, we should reset the text fade factor. It is also applicable to shortcut key. 168 latinKeyboard.updateSpacebarLanguage(0.0f, 169 Utils.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */), 170 mSubtypeSwitcher.needsToDisplayLanguage(latinKeyboard.mId.mLocale)); 171 latinKeyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 172 } 173 updateShiftState(); 174 mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged); 175 } 176 177 public boolean isAlphabetMode() { 178 final Keyboard keyboard = getLatinKeyboard(); 179 return keyboard != null && keyboard.mId.isAlphabetKeyboard(); 180 } 181 182 public boolean isInputViewShown() { 183 return mCurrentInputView != null && mCurrentInputView.isShown(); 184 } 185 186 public boolean isShiftedOrShiftLocked() { 187 final Keyboard keyboard = getLatinKeyboard(); 188 return keyboard != null && keyboard.isShiftedOrShiftLocked(); 189 } 190 191 public boolean isManualTemporaryUpperCase() { 192 final Keyboard keyboard = getLatinKeyboard(); 193 return keyboard != null && keyboard.isManualTemporaryUpperCase(); 194 } 195 196 public boolean isKeyboardAvailable() { 197 if (mKeyboardView != null) 198 return mKeyboardView.getKeyboard() != null; 199 return false; 200 } 201 202 public LatinKeyboard getLatinKeyboard() { 203 if (mKeyboardView != null) { 204 final Keyboard keyboard = mKeyboardView.getKeyboard(); 205 if (keyboard instanceof LatinKeyboard) 206 return (LatinKeyboard)keyboard; 207 } 208 return null; 209 } 210 211 // Implements {@link KeyboardState.SwitchActions}. 212 @Override 213 public void setShifted(int shiftMode) { 214 mInputMethodService.mHandler.cancelUpdateShiftState(); 215 LatinKeyboard latinKeyboard = getLatinKeyboard(); 216 if (latinKeyboard == null) 217 return; 218 if (shiftMode == AUTOMATIC_SHIFT) { 219 latinKeyboard.setAutomaticTemporaryUpperCase(); 220 } else { 221 final boolean shifted = (shiftMode == MANUAL_SHIFT); 222 // On non-distinct multi touch panel device, we should also turn off the shift locked 223 // state when shift key is pressed to go to normal mode. 224 // On the other hand, on distinct multi touch panel device, turning off the shift 225 // locked state with shift key pressing is handled by onReleaseShift(). 226 if (!hasDistinctMultitouch() && !shifted && mState.isShiftLocked()) { 227 latinKeyboard.setShiftLocked(false); 228 } 229 latinKeyboard.setShifted(shifted); 230 } 231 mKeyboardView.invalidateAllKeys(); 232 } 233 234 // Implements {@link KeyboardState.SwitchActions}. 235 @Override 236 public void setShiftLocked(boolean shiftLocked) { 237 mInputMethodService.mHandler.cancelUpdateShiftState(); 238 LatinKeyboard latinKeyboard = getLatinKeyboard(); 239 if (latinKeyboard == null) 240 return; 241 latinKeyboard.setShiftLocked(shiftLocked); 242 mKeyboardView.invalidateAllKeys(); 243 if (!shiftLocked) { 244 // To be able to turn off caps lock by "double tap" on shift key, we should ignore 245 // the second tap of the "double tap" from now for a while because we just have 246 // already turned off caps lock above. 247 mKeyboardView.startIgnoringDoubleTap(); 248 } 249 } 250 251 /** 252 * Toggle keyboard shift state triggered by user touch event. 253 */ 254 public void toggleShift() { 255 mState.onToggleShift(); 256 } 257 258 /** 259 * Toggle caps lock state triggered by user touch event. 260 */ 261 public void toggleCapsLock() { 262 mState.onToggleCapsLock(); 263 } 264 265 /** 266 * Toggle between alphabet and symbols modes triggered by user touch event. 267 */ 268 public void toggleAlphabetAndSymbols() { 269 mState.onToggleAlphabetAndSymbols(); 270 } 271 272 /** 273 * Update keyboard shift state triggered by connected EditText status change. 274 */ 275 public void updateShiftState() { 276 mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState()); 277 } 278 279 public void onPressShift(boolean withSliding) { 280 mState.onPressShift(withSliding); 281 } 282 283 public void onReleaseShift(boolean withSliding) { 284 mState.onReleaseShift(withSliding); 285 } 286 287 public void onPressSymbol() { 288 mState.onPressSymbol(); 289 } 290 291 public void onReleaseSymbol() { 292 mState.onReleaseSymbol(); 293 } 294 295 public void onOtherKeyPressed() { 296 mState.onOtherKeyPressed(); 297 } 298 299 public void onCancelInput() { 300 mState.onCancelInput(isSinglePointer()); 301 } 302 303 // Implements {@link KeyboardState.SwitchActions}. 304 @Override 305 public void setSymbolsKeyboard() { 306 setKeyboard(mKeyboardSet.getMainKeyboard()); 307 } 308 309 // Implements {@link KeyboardState.SwitchActions}. 310 @Override 311 public void setAlphabetKeyboard() { 312 setKeyboard(mKeyboardSet.getSymbolsKeyboard()); 313 } 314 315 // Implements {@link KeyboardState.SwitchActions}. 316 @Override 317 public void setSymbolsShiftedKeyboard() { 318 setKeyboard(mKeyboardSet.getSymbolsShiftedKeyboard()); 319 } 320 321 public boolean isInMomentarySwitchState() { 322 return mState.isInMomentarySwitchState(); 323 } 324 325 public boolean isVibrateAndSoundFeedbackRequired() { 326 return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput(); 327 } 328 329 private boolean isSinglePointer() { 330 return mKeyboardView != null && mKeyboardView.getPointerCount() == 1; 331 } 332 333 public boolean hasDistinctMultitouch() { 334 return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); 335 } 336 337 /** 338 * Updates state machine to figure out when to automatically snap back to the previous mode. 339 */ 340 public void onCodeInput(int code) { 341 mState.onCodeInput(code, isSinglePointer()); 342 } 343 344 public LatinKeyboardView getKeyboardView() { 345 return mKeyboardView; 346 } 347 348 public View onCreateInputView() { 349 return createInputView(mThemeIndex, true); 350 } 351 352 private View createInputView(final int newThemeIndex, final boolean forceRecreate) { 353 if (mCurrentInputView != null && mThemeIndex == newThemeIndex && !forceRecreate) 354 return mCurrentInputView; 355 356 if (mKeyboardView != null) { 357 mKeyboardView.closing(); 358 } 359 360 final int oldThemeIndex = mThemeIndex; 361 Utils.GCUtils.getInstance().reset(); 362 boolean tryGC = true; 363 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 364 try { 365 setContextThemeWrapper(mInputMethodService, newThemeIndex); 366 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( 367 R.layout.input_view, null); 368 tryGC = false; 369 } catch (OutOfMemoryError e) { 370 Log.w(TAG, "load keyboard failed: " + e); 371 tryGC = Utils.GCUtils.getInstance().tryGCOrWait( 372 oldThemeIndex + "," + newThemeIndex, e); 373 } catch (InflateException e) { 374 Log.w(TAG, "load keyboard failed: " + e); 375 tryGC = Utils.GCUtils.getInstance().tryGCOrWait( 376 oldThemeIndex + "," + newThemeIndex, e); 377 } 378 } 379 380 mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); 381 mKeyboardView.setKeyboardActionListener(mInputMethodService); 382 383 // This always needs to be set since the accessibility state can 384 // potentially change without the input view being re-created. 385 AccessibleKeyboardViewProxy.setView(mKeyboardView); 386 387 return mCurrentInputView; 388 } 389 390 private void postSetInputView(final View newInputView) { 391 final LatinIME latinIme = mInputMethodService; 392 latinIme.mHandler.post(new Runnable() { 393 @Override 394 public void run() { 395 if (newInputView != null) { 396 latinIme.setInputView(newInputView); 397 } 398 latinIme.updateInputViewShown(); 399 } 400 }); 401 } 402 403 @Override 404 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { 405 if (PREF_KEYBOARD_LAYOUT.equals(key)) { 406 final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences); 407 postSetInputView(createInputView(themeIndex, false)); 408 } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) { 409 postSetInputView(createInputView(mThemeIndex, true)); 410 } 411 } 412 413 public void onNetworkStateChanged() { 414 final LatinKeyboard keyboard = getLatinKeyboard(); 415 if (keyboard == null) return; 416 final Key updatedKey = keyboard.updateShortcutKey( 417 SubtypeSwitcher.getInstance().isShortcutImeReady()); 418 if (updatedKey != null && mKeyboardView != null) { 419 mKeyboardView.invalidateKey(updatedKey); 420 } 421 } 422 423 public void onAutoCorrectionStateChanged(boolean isAutoCorrection) { 424 if (mIsAutoCorrectionActive != isAutoCorrection) { 425 mIsAutoCorrectionActive = isAutoCorrection; 426 final LatinKeyboard keyboard = getLatinKeyboard(); 427 if (keyboard != null && keyboard.needsAutoCorrectionSpacebarLed()) { 428 final Key invalidatedKey = keyboard.updateAutoCorrectionState(isAutoCorrection); 429 final LatinKeyboardView keyboardView = getKeyboardView(); 430 if (keyboardView != null) 431 keyboardView.invalidateKey(invalidatedKey); 432 } 433 } 434 } 435 436 private static String themeName(int themeId) { 437 // This should be aligned with theme-*.xml resource files' themeId attribute. 438 switch (themeId) { 439 case 0: return "Basic"; 440 case 1: return "BasicHighContrast"; 441 case 5: return "IceCreamSandwich"; 442 case 6: return "Stone"; 443 case 7: return "StoneBold"; 444 case 8: return "GingerBread"; 445 default: return null; 446 } 447 } 448} 449