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