KeyboardSwitcher.java revision cb389ef0d6e6eec737c249e1729c2a2cdc30f341
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(mSubtypeSwitcher.getCurrentSubtype()); 141 builder.setOptions( 142 settingsValues.isVoiceKeyEnabled(editorInfo), 143 settingsValues.isVoiceKeyOnMain(), 144 settingsValues.isLanguageSwitchKeyEnabled(mThemeContext)); 145 mKeyboardSet = builder.build(); 146 try { 147 mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols)); 148 } catch (KeyboardSetException e) { 149 Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); 150 LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); 151 return; 152 } 153 } 154 155 public void saveKeyboardState() { 156 if (getKeyboard() != null) { 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 mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 178 final boolean subtypeChanged = (oldKeyboard == null) 179 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); 180 final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage( 181 keyboard.mId.mLocale); 182 mKeyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage); 183 } 184 185 public Keyboard getKeyboard() { 186 if (mKeyboardView != null) { 187 return mKeyboardView.getKeyboard(); 188 } 189 return null; 190 } 191 192 /** 193 * Update keyboard shift state triggered by connected EditText status change. 194 */ 195 public void updateShiftState() { 196 mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState()); 197 } 198 199 public void onPressKey(int code) { 200 if (isVibrateAndSoundFeedbackRequired()) { 201 mInputMethodService.hapticAndAudioFeedback(code); 202 } 203 mState.onPressKey(code); 204 } 205 206 public void onReleaseKey(int code, boolean withSliding) { 207 mState.onReleaseKey(code, withSliding); 208 } 209 210 public void onCancelInput() { 211 mState.onCancelInput(isSinglePointer()); 212 } 213 214 // Implements {@link KeyboardState.SwitchActions}. 215 @Override 216 public void setAlphabetKeyboard() { 217 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET)); 218 } 219 220 // Implements {@link KeyboardState.SwitchActions}. 221 @Override 222 public void setAlphabetManualShiftedKeyboard() { 223 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED)); 224 } 225 226 // Implements {@link KeyboardState.SwitchActions}. 227 @Override 228 public void setAlphabetAutomaticShiftedKeyboard() { 229 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)); 230 } 231 232 // Implements {@link KeyboardState.SwitchActions}. 233 @Override 234 public void setAlphabetShiftLockedKeyboard() { 235 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED)); 236 } 237 238 // Implements {@link KeyboardState.SwitchActions}. 239 @Override 240 public void setAlphabetShiftLockShiftedKeyboard() { 241 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED)); 242 } 243 244 // Implements {@link KeyboardState.SwitchActions}. 245 @Override 246 public void setSymbolsKeyboard() { 247 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS)); 248 } 249 250 // Implements {@link KeyboardState.SwitchActions}. 251 @Override 252 public void setSymbolsShiftedKeyboard() { 253 setKeyboard(mKeyboardSet.getKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)); 254 } 255 256 // Implements {@link KeyboardState.SwitchActions}. 257 @Override 258 public void requestUpdatingShiftState() { 259 mState.onUpdateShiftState(mInputMethodService.getCurrentAutoCapsState()); 260 } 261 262 // Implements {@link KeyboardState.SwitchActions}. 263 @Override 264 public void startDoubleTapTimer() { 265 final LatinKeyboardView keyboardView = getKeyboardView(); 266 if (keyboardView != null) { 267 final TimerProxy timer = keyboardView.getTimerProxy(); 268 timer.startDoubleTapTimer(); 269 } 270 } 271 272 // Implements {@link KeyboardState.SwitchActions}. 273 @Override 274 public void cancelDoubleTapTimer() { 275 final LatinKeyboardView keyboardView = getKeyboardView(); 276 if (keyboardView != null) { 277 final TimerProxy timer = keyboardView.getTimerProxy(); 278 timer.cancelDoubleTapTimer(); 279 } 280 } 281 282 // Implements {@link KeyboardState.SwitchActions}. 283 @Override 284 public boolean isInDoubleTapTimeout() { 285 final LatinKeyboardView keyboardView = getKeyboardView(); 286 return (keyboardView != null) 287 ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false; 288 } 289 290 // Implements {@link KeyboardState.SwitchActions}. 291 @Override 292 public void startLongPressTimer(int code) { 293 final LatinKeyboardView keyboardView = getKeyboardView(); 294 if (keyboardView != null) { 295 final TimerProxy timer = keyboardView.getTimerProxy(); 296 timer.startLongPressTimer(code); 297 } 298 } 299 300 // Implements {@link KeyboardState.SwitchActions}. 301 @Override 302 public void cancelLongPressTimer() { 303 final LatinKeyboardView keyboardView = getKeyboardView(); 304 if (keyboardView != null) { 305 final TimerProxy timer = keyboardView.getTimerProxy(); 306 timer.cancelLongPressTimer(); 307 } 308 } 309 310 // Implements {@link KeyboardState.SwitchActions}. 311 @Override 312 public void hapticAndAudioFeedback(int code) { 313 mInputMethodService.hapticAndAudioFeedback(code); 314 } 315 316 public void onLongPressTimeout(int code) { 317 mState.onLongPressTimeout(code); 318 } 319 320 public boolean isInMomentarySwitchState() { 321 return mState.isInMomentarySwitchState(); 322 } 323 324 private boolean isVibrateAndSoundFeedbackRequired() { 325 return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput(); 326 } 327 328 private boolean isSinglePointer() { 329 return mKeyboardView != null && mKeyboardView.getPointerCount() == 1; 330 } 331 332 public boolean hasDistinctMultitouch() { 333 return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); 334 } 335 336 /** 337 * Updates state machine to figure out when to automatically switch back to the previous mode. 338 */ 339 public void onCodeInput(int code) { 340 mState.onCodeInput(code, isSinglePointer(), mInputMethodService.getCurrentAutoCapsState()); 341 } 342 343 public LatinKeyboardView getKeyboardView() { 344 return mKeyboardView; 345 } 346 347 public View onCreateInputView() { 348 if (mKeyboardView != null) { 349 mKeyboardView.closing(); 350 } 351 352 Utils.GCUtils.getInstance().reset(); 353 boolean tryGC = true; 354 for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { 355 try { 356 setContextThemeWrapper(mInputMethodService, mKeyboardTheme); 357 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( 358 R.layout.input_view, null); 359 tryGC = false; 360 } catch (OutOfMemoryError e) { 361 Log.w(TAG, "load keyboard failed: " + e); 362 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e); 363 } catch (InflateException e) { 364 Log.w(TAG, "load keyboard failed: " + e); 365 tryGC = Utils.GCUtils.getInstance().tryGCOrWait(mKeyboardTheme.mName, e); 366 } 367 } 368 369 mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); 370 mKeyboardView.setKeyboardActionListener(mInputMethodService); 371 if (mForceNonDistinctMultitouch) { 372 mKeyboardView.setDistinctMultitouch(false); 373 } 374 375 // This always needs to be set since the accessibility state can 376 // potentially change without the input view being re-created. 377 AccessibleKeyboardViewProxy.getInstance().setView(mKeyboardView); 378 379 return mCurrentInputView; 380 } 381 382 public void onNetworkStateChanged() { 383 if (mKeyboardView != null) { 384 mKeyboardView.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady()); 385 } 386 } 387 388 public void onAutoCorrectionStateChanged(boolean isAutoCorrection) { 389 if (mIsAutoCorrectionActive != isAutoCorrection) { 390 mIsAutoCorrectionActive = isAutoCorrection; 391 if (mKeyboardView != null) { 392 mKeyboardView.updateAutoCorrectionState(isAutoCorrection); 393 } 394 } 395 } 396} 397