KeyboardSwitcher.java revision e07db71d26fd9cb2c0141d76bfd3cd01bb001899
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.KeyboardSet.KeyboardSetException; 31import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; 32import com.android.inputmethod.keyboard.internal.KeyboardState; 33import com.android.inputmethod.latin.DebugSettings; 34import com.android.inputmethod.latin.InputView; 35import com.android.inputmethod.latin.LatinIME; 36import com.android.inputmethod.latin.LatinImeLogger; 37import com.android.inputmethod.latin.R; 38import com.android.inputmethod.latin.SettingsValues; 39import com.android.inputmethod.latin.SubtypeSwitcher; 40import com.android.inputmethod.latin.Utils; 41 42public class KeyboardSwitcher implements KeyboardState.SwitchActions { 43 private static final String TAG = KeyboardSwitcher.class.getSimpleName(); 44 45 public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916"; 46 47 static class KeyboardTheme { 48 public final String mName; 49 public final int mThemeId; 50 public final int mStyleId; 51 52 public KeyboardTheme(String name, int themeId, int styleId) { 53 mName = name; 54 mThemeId = themeId; 55 mStyleId = styleId; 56 } 57 } 58 59 private static final KeyboardTheme[] KEYBOARD_THEMES = { 60 new KeyboardTheme("Basic", 0, R.style.KeyboardTheme), 61 new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast), 62 new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone), 63 new KeyboardTheme("Stne.Bold", 7, R.style.KeyboardTheme_Stone_Bold), 64 new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread), 65 new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich), 66 }; 67 68 private SubtypeSwitcher mSubtypeSwitcher; 69 private SharedPreferences mPrefs; 70 private boolean mForceNonDistinctMultitouch; 71 72 private InputView mCurrentInputView; 73 private LatinKeyboardView mKeyboardView; 74 private LatinIME mInputMethodService; 75 private Resources mResources; 76 77 private KeyboardState mState; 78 79 private KeyboardSet mKeyboardSet; 80 81 /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of 82 * what user actually typed. */ 83 private boolean mIsAutoCorrectionActive; 84 85 private KeyboardTheme mKeyboardTheme = KEYBOARD_THEMES[0]; 86 private Context mThemeContext; 87 88 private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); 89 90 public static KeyboardSwitcher getInstance() { 91 return sInstance; 92 } 93 94 private KeyboardSwitcher() { 95 // Intentional empty constructor for singleton. 96 } 97 98 public static void init(LatinIME ims, SharedPreferences prefs) { 99 sInstance.initInternal(ims, prefs); 100 } 101 102 private void initInternal(LatinIME ims, SharedPreferences prefs) { 103 mInputMethodService = ims; 104 mResources = ims.getResources(); 105 mPrefs = prefs; 106 mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 107 mState = new KeyboardState(this); 108 setContextThemeWrapper(ims, getKeyboardTheme(ims, prefs)); 109 mForceNonDistinctMultitouch = prefs.getBoolean( 110 DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false); 111 } 112 113 private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) { 114 final String defaultIndex = context.getString(R.string.config_default_keyboard_theme_index); 115 final String themeIndex = prefs.getString(PREF_KEYBOARD_LAYOUT, defaultIndex); 116 try { 117 final int index = Integer.valueOf(themeIndex); 118 if (index >= 0 && index < KEYBOARD_THEMES.length) { 119 return KEYBOARD_THEMES[index]; 120 } 121 } catch (NumberFormatException e) { 122 // Format error, keyboard theme is default to 0. 123 } 124 Log.w(TAG, "Illegal keyboard theme in preference: " + themeIndex + ", default to 0"); 125 return KEYBOARD_THEMES[0]; 126 } 127 128 private void setContextThemeWrapper(Context context, KeyboardTheme keyboardTheme) { 129 if (mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) { 130 mKeyboardTheme = keyboardTheme; 131 mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); 132 KeyboardSet.clearKeyboardCache(); 133 } 134 } 135 136 public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) { 137 final KeyboardSet.Builder builder = new KeyboardSet.Builder(mThemeContext, editorInfo); 138 builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation, 139 mThemeContext.getResources().getDisplayMetrics().widthPixels); 140 builder.setSubtype( 141 mSubtypeSwitcher.getInputLocale(), 142 mSubtypeSwitcher.currentSubtypeContainsExtraValueKey( 143 LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE)); 144 builder.setOptions( 145 settingsValues.isVoiceKeyEnabled(editorInfo), 146 settingsValues.isVoiceKeyOnMain(), 147 settingsValues.isLanguageSwitchKeyEnabled(mThemeContext)); 148 mKeyboardSet = builder.build(); 149 try { 150 mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols)); 151 } catch (KeyboardSetException e) { 152 Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); 153 LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); 154 return; 155 } 156 } 157 158 public void saveKeyboardState() { 159 if (getKeyboard() != null) { 160 mState.onSaveKeyboardState(); 161 } 162 } 163 164 public void onFinishInputView() { 165 mIsAutoCorrectionActive = false; 166 } 167 168 public void onHideWindow() { 169 mIsAutoCorrectionActive = false; 170 } 171 172 private void setKeyboard(final Keyboard keyboard) { 173 final Keyboard oldKeyboard = mKeyboardView.getKeyboard(); 174 mKeyboardView.setKeyboard(keyboard); 175 mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding); 176 mKeyboardView.setKeyPreviewPopupEnabled( 177 SettingsValues.isKeyPreviewPopupEnabled(mPrefs, mResources), 178 SettingsValues.getKeyPreviewPopupDismissDelay(mPrefs, mResources)); 179 mKeyboardView.updateAutoCorrectionState(mIsAutoCorrectionActive); 180 mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 181 final boolean subtypeChanged = (oldKeyboard == null) 182 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); 183 final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage( 184 keyboard.mId.mLocale); 185 mKeyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage); 186 } 187 188 public Keyboard getKeyboard() { 189 if (mKeyboardView != null) { 190 return mKeyboardView.getKeyboard(); 191 } 192 return null; 193 } 194 195 /** 196 * Update keyboard shift state triggered by connected EditText status change. 197 */ 198 public void updateShiftState() { 199 mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState()); 200 } 201 202 public void onPressKey(int code) { 203 if (isVibrateAndSoundFeedbackRequired()) { 204 mInputMethodService.hapticAndAudioFeedback(code); 205 } 206 mState.onPressKey(code); 207 } 208 209 public void onReleaseKey(int code, boolean withSliding) { 210 mState.onReleaseKey(code, withSliding); 211 } 212 213 public void onCancelInput() { 214 mState.onCancelInput(isSinglePointer()); 215 } 216 217 // Implements {@link KeyboardState.SwitchActions}. 218 @Override 219 public void setAlphabetKeyboard() { 220 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); 221 } 222 223 // Implements {@link KeyboardState.SwitchActions}. 224 @Override 225 public void setAlphabetManualShiftedKeyboard() { 226 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); 227 } 228 229 // Implements {@link KeyboardState.SwitchActions}. 230 @Override 231 public void setAlphabetAutomaticShiftedKeyboard() { 232 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); 233 } 234 235 // Implements {@link KeyboardState.SwitchActions}. 236 @Override 237 public void setAlphabetShiftLockedKeyboard() { 238 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); 239 } 240 241 // Implements {@link KeyboardState.SwitchActions}. 242 @Override 243 public void setAlphabetShiftLockShiftedKeyboard() { 244 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); 245 } 246 247 // Implements {@link KeyboardState.SwitchActions}. 248 @Override 249 public void setSymbolsKeyboard() { 250 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); 251 } 252 253 // Implements {@link KeyboardState.SwitchActions}. 254 @Override 255 public void setSymbolsShiftedKeyboard() { 256 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); 257 } 258 259 // Implements {@link KeyboardState.SwitchActions}. 260 @Override 261 public void requestUpdatingShiftState() { 262 mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState()); 263 } 264 265 // Implements {@link KeyboardState.SwitchActions}. 266 @Override 267 public void startDoubleTapTimer() { 268 final LatinKeyboardView keyboardView = getKeyboardView(); 269 if (keyboardView != null) { 270 final TimerProxy timer = keyboardView.getTimerProxy(); 271 timer.startDoubleTapTimer(); 272 } 273 } 274 275 // Implements {@link KeyboardState.SwitchActions}. 276 @Override 277 public void cancelDoubleTapTimer() { 278 final LatinKeyboardView keyboardView = getKeyboardView(); 279 if (keyboardView != null) { 280 final TimerProxy timer = keyboardView.getTimerProxy(); 281 timer.cancelDoubleTapTimer(); 282 } 283 } 284 285 // Implements {@link KeyboardState.SwitchActions}. 286 @Override 287 public boolean isInDoubleTapTimeout() { 288 final LatinKeyboardView keyboardView = getKeyboardView(); 289 return (keyboardView != null) 290 ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false; 291 } 292 293 // Implements {@link KeyboardState.SwitchActions}. 294 @Override 295 public void startLongPressTimer(int code) { 296 final LatinKeyboardView keyboardView = getKeyboardView(); 297 if (keyboardView != null) { 298 final TimerProxy timer = keyboardView.getTimerProxy(); 299 timer.startLongPressTimer(code); 300 } 301 } 302 303 // Implements {@link KeyboardState.SwitchActions}. 304 @Override 305 public void cancelLongPressTimer() { 306 final LatinKeyboardView keyboardView = getKeyboardView(); 307 if (keyboardView != null) { 308 final TimerProxy timer = keyboardView.getTimerProxy(); 309 timer.cancelLongPressTimer(); 310 } 311 } 312 313 // Implements {@link KeyboardState.SwitchActions}. 314 @Override 315 public void hapticAndAudioFeedback(int code) { 316 mInputMethodService.hapticAndAudioFeedback(code); 317 } 318 319 public void onLongPressTimeout(int code) { 320 mState.onLongPressTimeout(code); 321 } 322 323 public boolean isInMomentarySwitchState() { 324 return mState.isInMomentarySwitchState(); 325 } 326 327 private boolean isVibrateAndSoundFeedbackRequired() { 328 return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput(); 329 } 330 331 private boolean isSinglePointer() { 332 return mKeyboardView != null && mKeyboardView.getPointerCount() == 1; 333 } 334 335 public boolean hasDistinctMultitouch() { 336 return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); 337 } 338 339 /** 340 * Updates state machine to figure out when to automatically switch back to the previous mode. 341 */ 342 public void onCodeInput(int code) { 343 mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState()); 344 } 345 346 public LatinKeyboardView getKeyboardView() { 347 return mKeyboardView; 348 } 349 350 public View onCreateInputView() { 351 if (mKeyboardView != null) { 352 mKeyboardView.closing(); 353 } 354 355 Utils.GCUtils.getInstance().reset(); 356 boolean tryGC = true; 357 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 358 try { 359 setContextThemeWrapper(mInputMethodService, mKeyboardTheme); 360 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( 361 R.layout.input_view, null); 362 tryGC = false; 363 } catch (OutOfMemoryError e) { 364 Log.w(TAG, "load keyboard failed: " + e); 365 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e); 366 } catch (InflateException e) { 367 Log.w(TAG, "load keyboard failed: " + e); 368 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e); 369 } 370 } 371 372 mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); 373 mKeyboardView.setKeyboardActionListener(mInputMethodService); 374 if (mForceNonDistinctMultitouch) { 375 mKeyboardView.setDistinctMultitouch(false); 376 } 377 378 // This always needs to be set since the accessibility state can 379 // potentially change without the input view being re-created. 380 AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView); 381 382 return mCurrentInputView; 383 } 384 385 public void onNetworkStateChanged() { 386 if (mKeyboardView != null) { 387 mKeyboardView.updateShortcutKey(SubtypeSwitcher.getInstance().isShortcutImeReady()); 388 } 389 } 390 391 public void onAutoCorrectionStateChanged(boolean isAutoCorrection) { 392 if (mIsAutoCorrectionActive != isAutoCorrection) { 393 mIsAutoCorrectionActive = isAutoCorrection; 394 if (mKeyboardView != null) { 395 mKeyboardView.updateAutoCorrectionState(isAutoCorrection); 396 } 397 } 398 } 399} 400