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