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