KeyboardSwitcher.java revision 2a9882a433e2372ac32fbc0def578d4d9a97a676
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under 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.preference.PreferenceManager; 23import android.util.DisplayMetrics; 24import android.util.Log; 25import android.view.ContextThemeWrapper; 26import android.view.LayoutInflater; 27import android.view.View; 28import android.view.inputmethod.EditorInfo; 29 30import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; 31import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; 32import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; 33import com.android.inputmethod.keyboard.internal.KeyboardState; 34import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; 35import com.android.inputmethod.latin.Constants; 36import com.android.inputmethod.latin.InputView; 37import com.android.inputmethod.latin.LatinIME; 38import com.android.inputmethod.latin.LatinImeLogger; 39import com.android.inputmethod.latin.R; 40import com.android.inputmethod.latin.RichInputMethodManager; 41import com.android.inputmethod.latin.Settings; 42import com.android.inputmethod.latin.SettingsValues; 43import com.android.inputmethod.latin.SubtypeSwitcher; 44import com.android.inputmethod.latin.WordComposer; 45 46public final class KeyboardSwitcher implements KeyboardState.SwitchActions { 47 private static final String TAG = KeyboardSwitcher.class.getSimpleName(); 48 49 public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916"; 50 51 static final class KeyboardTheme { 52 public final int mThemeId; 53 public final int mStyleId; 54 55 // Note: The themeId should be aligned with "themeId" attribute of Keyboard style 56 // in values/style.xml. 57 public KeyboardTheme(final int themeId, final int styleId) { 58 mThemeId = themeId; 59 mStyleId = styleId; 60 } 61 } 62 63 private static final KeyboardTheme[] KEYBOARD_THEMES = { 64 new KeyboardTheme(0, R.style.KeyboardTheme), 65 new KeyboardTheme(1, R.style.KeyboardTheme_HighContrast), 66 new KeyboardTheme(6, R.style.KeyboardTheme_Stone), 67 new KeyboardTheme(7, R.style.KeyboardTheme_Stone_Bold), 68 new KeyboardTheme(8, R.style.KeyboardTheme_Gingerbread), 69 new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich), 70 }; 71 72 private SubtypeSwitcher mSubtypeSwitcher; 73 private SharedPreferences mPrefs; 74 75 private InputView mCurrentInputView; 76 private MainKeyboardView mKeyboardView; 77 private LatinIME mLatinIME; 78 private Resources mResources; 79 80 private KeyboardState mState; 81 82 private KeyboardLayoutSet mKeyboardLayoutSet; 83 84 /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of 85 * what user actually typed. */ 86 private boolean mIsAutoCorrectionActive; 87 88 private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[0]; 89 private Context mThemeContext; 90 91 private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); 92 93 public static KeyboardSwitcher getInstance() { 94 return sInstance; 95 } 96 97 private KeyboardSwitcher() { 98 // Intentional empty constructor for singleton. 99 } 100 101 public static void init(final LatinIME latinIme) { 102 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(latinIme); 103 sInstance.initInternal(latinIme, prefs); 104 } 105 106 private void initInternal(final LatinIME latinIme, final SharedPreferences prefs) { 107 mLatinIME = latinIme; 108 mResources = latinIme.getResources(); 109 mPrefs = prefs; 110 mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 111 mState = new KeyboardState(this); 112 setContextThemeWrapper(latinIme, getKeyboardTheme(latinIme, prefs)); 113 } 114 115 private static KeyboardTheme getKeyboardTheme(final Context context, 116 final SharedPreferences prefs) { 117 final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index); 118 final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex); 119 try { 120 final int index = Integer.valueOf(themeIndex); 121 if (index >= 0 && index < KEYBOARD_THEMES.length) { 122 return KEYBOARD_THEMES[index]; 123 } 124 } catch (NumberFormatException e) { 125 // Format error, keyboard theme is default to 0. 126 } 127 Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0"); 128 return KEYBOARD_THEMES[0]; 129 } 130 131 private void setContextThemeWrapper(final Context context, final KeyboardTheme keyboardTheme) { 132 if (mThemeContext == null || mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) { 133 mKeyboardTheme = keyboardTheme; 134 mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); 135 KeyboardLayoutSet.clearKeyboardCache(); 136 } 137 } 138 139 public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues) { 140 final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( 141 mThemeContext, editorInfo); 142 final Resources res = mThemeContext.getResources(); 143 final DisplayMetrics dm = res.getDisplayMetrics(); 144 builder.setScreenGeometry(dm.widthPixels, dm.heightPixels); 145 builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype()); 146 builder.setOptions( 147 settingsValues.isVoiceKeyEnabled(editorInfo), 148 settingsValues.isVoiceKeyOnMain(), 149 settingsValues.isLanguageSwitchKeyEnabled()); 150 mKeyboardLayoutSet = builder.build(); 151 try { 152 mState.onLoadKeyboard(); 153 } catch (KeyboardLayoutSetException e) { 154 Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); 155 LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); 156 return; 157 } 158 } 159 160 public void saveKeyboardState() { 161 if (getKeyboard() != null) { 162 mState.onSaveKeyboardState(); 163 } 164 } 165 166 public void onFinishInputView() { 167 mIsAutoCorrectionActive = false; 168 } 169 170 public void onHideWindow() { 171 mIsAutoCorrectionActive = false; 172 } 173 174 private void setKeyboard(final Keyboard keyboard) { 175 final MainKeyboardView keyboardView = mKeyboardView; 176 final Keyboard oldKeyboard = keyboardView.getKeyboard(); 177 keyboardView.setKeyboard(keyboard); 178 mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding); 179 keyboardView.setKeyPreviewPopupEnabled( 180 Settings.readKeyPreviewPopupEnabled(mPrefs, mResources), 181 Settings.readKeyPreviewPopupDismissDelay(mPrefs, mResources)); 182 keyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive); 183 keyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 184 final boolean subtypeChanged = (oldKeyboard == null) 185 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); 186 final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage( 187 keyboard.mId.mLocale); 188 keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage, 189 RichInputMethodManager.getInstance().hasMultipleEnabledIMEsOrSubtypes(true)); 190 } 191 192 public Keyboard getKeyboard() { 193 if (mKeyboardView != null) { 194 return mKeyboardView.getKeyboard(); 195 } 196 return null; 197 } 198 199 /** 200 * Update keyboard shift state triggered by connected EditText status change. 201 */ 202 public void updateShiftState() { 203 mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(), 204 mLatinIME.getCurrentRecapitalizeState()); 205 } 206 207 // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout 208 // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). 209 public void resetKeyboardStateToAlphabet() { 210 mState.onResetKeyboardStateToAlphabet(); 211 } 212 213 public void onPressKey(final int code, final boolean isSinglePointer) { 214 hapticAndAudioFeedback(code); 215 mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState()); 216 } 217 218 public void onReleaseKey(final int code, final boolean withSliding) { 219 mState.onReleaseKey(code, withSliding); 220 } 221 222 public void onFinishSlidingInput() { 223 mState.onFinishSlidingInput(); 224 } 225 226 // Implements {@link KeyboardState.SwitchActions}. 227 @Override 228 public void setAlphabetKeyboard() { 229 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); 230 } 231 232 // Implements {@link KeyboardState.SwitchActions}. 233 @Override 234 public void setAlphabetManualShiftedKeyboard() { 235 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); 236 } 237 238 // Implements {@link KeyboardState.SwitchActions}. 239 @Override 240 public void setAlphabetAutomaticShiftedKeyboard() { 241 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); 242 } 243 244 // Implements {@link KeyboardState.SwitchActions}. 245 @Override 246 public void setAlphabetShiftLockedKeyboard() { 247 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); 248 } 249 250 // Implements {@link KeyboardState.SwitchActions}. 251 @Override 252 public void setAlphabetShiftLockShiftedKeyboard() { 253 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); 254 } 255 256 // Implements {@link KeyboardState.SwitchActions}. 257 @Override 258 public void setSymbolsKeyboard() { 259 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); 260 } 261 262 // Implements {@link KeyboardState.SwitchActions}. 263 @Override 264 public void setSymbolsShiftedKeyboard() { 265 setKeyboard(mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); 266 } 267 268 // Implements {@link KeyboardState.SwitchActions}. 269 @Override 270 public void requestUpdatingShiftState() { 271 mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(), 272 mLatinIME.getCurrentRecapitalizeState()); 273 } 274 275 // Implements {@link KeyboardState.SwitchActions}. 276 @Override 277 public void startDoubleTapShiftKeyTimer() { 278 final MainKeyboardView keyboardView = getMainKeyboardView(); 279 if (keyboardView != null) { 280 final TimerProxy timer = keyboardView.getTimerProxy(); 281 timer.startDoubleTapShiftKeyTimer(); 282 } 283 } 284 285 // Implements {@link KeyboardState.SwitchActions}. 286 @Override 287 public void cancelDoubleTapShiftKeyTimer() { 288 final MainKeyboardView keyboardView = getMainKeyboardView(); 289 if (keyboardView != null) { 290 final TimerProxy timer = keyboardView.getTimerProxy(); 291 timer.cancelDoubleTapShiftKeyTimer(); 292 } 293 } 294 295 // Implements {@link KeyboardState.SwitchActions}. 296 @Override 297 public boolean isInDoubleTapShiftKeyTimeout() { 298 final MainKeyboardView keyboardView = getMainKeyboardView(); 299 return (keyboardView != null) 300 ? keyboardView.getTimerProxy().isInDoubleTapShiftKeyTimeout() : false; 301 } 302 303 // Implements {@link KeyboardState.SwitchActions}. 304 @Override 305 public void startLongPressTimer(final int code) { 306 final MainKeyboardView keyboardView = getMainKeyboardView(); 307 if (keyboardView != null) { 308 final TimerProxy timer = keyboardView.getTimerProxy(); 309 timer.startLongPressTimer(code); 310 } 311 } 312 313 // Implements {@link KeyboardState.SwitchActions}. 314 @Override 315 public void cancelLongPressTimer() { 316 final MainKeyboardView keyboardView = getMainKeyboardView(); 317 if (keyboardView != null) { 318 final TimerProxy timer = keyboardView.getTimerProxy(); 319 timer.cancelLongPressTimer(); 320 } 321 } 322 323 private void hapticAndAudioFeedback(final int code) { 324 if (mKeyboardView == null || mKeyboardView.isInSlidingKeyInput()) { 325 return; 326 } 327 AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, mKeyboardView); 328 } 329 330 public void onLongPressTimeout(final int code) { 331 mState.onLongPressTimeout(code); 332 final Keyboard keyboard = getKeyboard(); 333 if (keyboard != null && keyboard.mId.isAlphabetKeyboard() && code == Constants.CODE_SHIFT) { 334 hapticAndAudioFeedback(code); 335 } 336 } 337 338 /** 339 * Updates state machine to figure out when to automatically switch back to the previous mode. 340 */ 341 public void onCodeInput(final int code) { 342 mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState()); 343 } 344 345 public MainKeyboardView getMainKeyboardView() { 346 return mKeyboardView; 347 } 348 349 public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) { 350 if (mKeyboardView != null) { 351 mKeyboardView.closing(); 352 } 353 354 setContextThemeWrapper(mLatinIME, mKeyboardTheme); 355 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( 356 R.layout.input_view, null); 357 358 mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); 359 if (isHardwareAcceleratedDrawingEnabled) { 360 mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 361 // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? 362 } 363 mKeyboardView.setKeyboardActionListener(mLatinIME); 364 365 // This always needs to be set since the accessibility state can 366 // potentially change without the input view being re-created. 367 AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView); 368 369 return mCurrentInputView; 370 } 371 372 public void onNetworkStateChanged() { 373 if (mKeyboardView != null) { 374 mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 375 } 376 } 377 378 public void onAutoCorrectionStateChanged(final boolean isAutoCorrection) { 379 if (mIsAutoCorrectionActive != isAutoCorrection) { 380 mIsAutoCorrectionActive = isAutoCorrection; 381 if (mKeyboardView != null) { 382 mKeyboardView.updateAutoCorrectionState(isAutoCorrection); 383 } 384 } 385 } 386 387 public int getKeyboardShiftMode() { 388 final Keyboard keyboard = getKeyboard(); 389 if (keyboard == null) { 390 return WordComposer.CAPS_MODE_OFF; 391 } 392 switch (keyboard.mId.mElementId) { 393 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 394 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 395 return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED; 396 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 397 return WordComposer.CAPS_MODE_MANUAL_SHIFTED; 398 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 399 return WordComposer.CAPS_MODE_AUTO_SHIFTED; 400 default: 401 return WordComposer.CAPS_MODE_OFF; 402 } 403 } 404} 405