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