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