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.res.Resources; 21 import android.util.Log; 22 import android.view.ContextThemeWrapper; 23 import android.view.LayoutInflater; 24 import android.view.View; 25 import android.view.inputmethod.EditorInfo; 26 27 import com.android.inputmethod.compat.InputMethodServiceCompatUtils; 28 import com.android.inputmethod.event.Event; 29 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; 30 import com.android.inputmethod.keyboard.emoji.EmojiPalettesView; 31 import com.android.inputmethod.keyboard.internal.KeyboardState; 32 import com.android.inputmethod.keyboard.internal.KeyboardTextsSet; 33 import com.android.inputmethod.latin.InputView; 34 import com.android.inputmethod.latin.LatinIME; 35 import com.android.inputmethod.latin.R; 36 import com.android.inputmethod.latin.RichInputMethodManager; 37 import com.android.inputmethod.latin.WordComposer; 38 import com.android.inputmethod.latin.define.ProductionFlags; 39 import com.android.inputmethod.latin.settings.Settings; 40 import com.android.inputmethod.latin.settings.SettingsValues; 41 import com.android.inputmethod.latin.utils.CapsModeUtils; 42 import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils; 43 import com.android.inputmethod.latin.utils.RecapitalizeStatus; 44 import com.android.inputmethod.latin.utils.ResourceUtils; 45 import com.android.inputmethod.latin.utils.ScriptUtils; 46 47 import javax.annotation.Nonnull; 48 49 public final class KeyboardSwitcher implements KeyboardState.SwitchActions { 50 private static final String TAG = KeyboardSwitcher.class.getSimpleName(); 51 52 private InputView mCurrentInputView; 53 private View mMainKeyboardFrame; 54 private MainKeyboardView mKeyboardView; 55 private EmojiPalettesView mEmojiPalettesView; 56 private LatinIME mLatinIME; 57 private RichInputMethodManager mRichImm; 58 private boolean mIsHardwareAcceleratedDrawingEnabled; 59 60 private KeyboardState mState; 61 62 private KeyboardLayoutSet mKeyboardLayoutSet; 63 // TODO: The following {@link KeyboardTextsSet} should be in {@link KeyboardLayoutSet}. 64 private final KeyboardTextsSet mKeyboardTextsSet = new KeyboardTextsSet(); 65 66 private KeyboardTheme mKeyboardTheme; 67 private Context mThemeContext; 68 69 private static final KeyboardSwitcher sInstance = new KeyboardSwitcher(); 70 71 public static KeyboardSwitcher getInstance() { 72 return sInstance; 73 } 74 75 private KeyboardSwitcher() { 76 // Intentional empty constructor for singleton. 77 } 78 79 public static void init(final LatinIME latinIme) { 80 sInstance.initInternal(latinIme); 81 } 82 83 private void initInternal(final LatinIME latinIme) { 84 mLatinIME = latinIme; 85 mRichImm = RichInputMethodManager.getInstance(); 86 mState = new KeyboardState(this); 87 mIsHardwareAcceleratedDrawingEnabled = 88 InputMethodServiceCompatUtils.enableHardwareAcceleration(mLatinIME); 89 } 90 91 public void updateKeyboardTheme() { 92 final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper( 93 mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */)); 94 if (themeUpdated && mKeyboardView != null) { 95 mLatinIME.setInputView(onCreateInputView(mIsHardwareAcceleratedDrawingEnabled)); 96 } 97 } 98 99 private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context, 100 final KeyboardTheme keyboardTheme) { 101 if (mThemeContext == null || !keyboardTheme.equals(mKeyboardTheme)) { 102 mKeyboardTheme = keyboardTheme; 103 mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId); 104 KeyboardLayoutSet.onKeyboardThemeChanged(); 105 return true; 106 } 107 return false; 108 } 109 110 public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues, 111 final int currentAutoCapsState, final int currentRecapitalizeState) { 112 final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( 113 mThemeContext, editorInfo); 114 final Resources res = mThemeContext.getResources(); 115 final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res); 116 final int keyboardHeight = ResourceUtils.getKeyboardHeight(res, settingsValues); 117 builder.setKeyboardGeometry(keyboardWidth, keyboardHeight); 118 builder.setSubtype(mRichImm.getCurrentSubtype()); 119 builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey); 120 builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey()); 121 builder.setSplitLayoutEnabledByUser(ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED 122 && settingsValues.mIsSplitKeyboardEnabled); 123 mKeyboardLayoutSet = builder.build(); 124 try { 125 mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState); 126 mKeyboardTextsSet.setLocale(mRichImm.getCurrentSubtypeLocale(), mThemeContext); 127 } catch (KeyboardLayoutSetException e) { 128 Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); 129 } 130 } 131 132 public void saveKeyboardState() { 133 if (getKeyboard() != null || isShowingEmojiPalettes()) { 134 mState.onSaveKeyboardState(); 135 } 136 } 137 138 public void onHideWindow() { 139 if (mKeyboardView != null) { 140 mKeyboardView.onHideWindow(); 141 } 142 } 143 144 private void setKeyboard( 145 @Nonnull final int keyboardId, 146 @Nonnull final KeyboardSwitchState toggleState) { 147 // Make {@link MainKeyboardView} visible and hide {@link EmojiPalettesView}. 148 final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent(); 149 setMainKeyboardFrame(currentSettingsValues, toggleState); 150 // TODO: pass this object to setKeyboard instead of getting the current values. 151 final MainKeyboardView keyboardView = mKeyboardView; 152 final Keyboard oldKeyboard = keyboardView.getKeyboard(); 153 final Keyboard newKeyboard = mKeyboardLayoutSet.getKeyboard(keyboardId); 154 keyboardView.setKeyboard(newKeyboard); 155 mCurrentInputView.setKeyboardTopPadding(newKeyboard.mTopPadding); 156 keyboardView.setKeyPreviewPopupEnabled( 157 currentSettingsValues.mKeyPreviewPopupOn, 158 currentSettingsValues.mKeyPreviewPopupDismissDelay); 159 keyboardView.setKeyPreviewAnimationParams( 160 currentSettingsValues.mHasCustomKeyPreviewAnimationParams, 161 currentSettingsValues.mKeyPreviewShowUpStartXScale, 162 currentSettingsValues.mKeyPreviewShowUpStartYScale, 163 currentSettingsValues.mKeyPreviewShowUpDuration, 164 currentSettingsValues.mKeyPreviewDismissEndXScale, 165 currentSettingsValues.mKeyPreviewDismissEndYScale, 166 currentSettingsValues.mKeyPreviewDismissDuration); 167 keyboardView.updateShortcutKey(mRichImm.isShortcutImeReady()); 168 final boolean subtypeChanged = (oldKeyboard == null) 169 || !newKeyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype); 170 final int languageOnSpacebarFormatType = LanguageOnSpacebarUtils 171 .getLanguageOnSpacebarFormatType(newKeyboard.mId.mSubtype); 172 final boolean hasMultipleEnabledIMEsOrSubtypes = mRichImm 173 .hasMultipleEnabledIMEsOrSubtypes(true /* shouldIncludeAuxiliarySubtypes */); 174 keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType, 175 hasMultipleEnabledIMEsOrSubtypes); 176 } 177 178 public Keyboard getKeyboard() { 179 if (mKeyboardView != null) { 180 return mKeyboardView.getKeyboard(); 181 } 182 return null; 183 } 184 185 // TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout 186 // when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal(). 187 public void resetKeyboardStateToAlphabet(final int currentAutoCapsState, 188 final int currentRecapitalizeState) { 189 mState.onResetKeyboardStateToAlphabet(currentAutoCapsState, currentRecapitalizeState); 190 } 191 192 public void onPressKey(final int code, final boolean isSinglePointer, 193 final int currentAutoCapsState, final int currentRecapitalizeState) { 194 mState.onPressKey(code, isSinglePointer, currentAutoCapsState, currentRecapitalizeState); 195 } 196 197 public void onReleaseKey(final int code, final boolean withSliding, 198 final int currentAutoCapsState, final int currentRecapitalizeState) { 199 mState.onReleaseKey(code, withSliding, currentAutoCapsState, currentRecapitalizeState); 200 } 201 202 public void onFinishSlidingInput(final int currentAutoCapsState, 203 final int currentRecapitalizeState) { 204 mState.onFinishSlidingInput(currentAutoCapsState, currentRecapitalizeState); 205 } 206 207 // Implements {@link KeyboardState.SwitchActions}. 208 @Override 209 public void setAlphabetKeyboard() { 210 if (DEBUG_ACTION) { 211 Log.d(TAG, "setAlphabetKeyboard"); 212 } 213 setKeyboard(KeyboardId.ELEMENT_ALPHABET, KeyboardSwitchState.OTHER); 214 } 215 216 // Implements {@link KeyboardState.SwitchActions}. 217 @Override 218 public void setAlphabetManualShiftedKeyboard() { 219 if (DEBUG_ACTION) { 220 Log.d(TAG, "setAlphabetManualShiftedKeyboard"); 221 } 222 setKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardSwitchState.OTHER); 223 } 224 225 // Implements {@link KeyboardState.SwitchActions}. 226 @Override 227 public void setAlphabetAutomaticShiftedKeyboard() { 228 if (DEBUG_ACTION) { 229 Log.d(TAG, "setAlphabetAutomaticShiftedKeyboard"); 230 } 231 setKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardSwitchState.OTHER); 232 } 233 234 // Implements {@link KeyboardState.SwitchActions}. 235 @Override 236 public void setAlphabetShiftLockedKeyboard() { 237 if (DEBUG_ACTION) { 238 Log.d(TAG, "setAlphabetShiftLockedKeyboard"); 239 } 240 setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardSwitchState.OTHER); 241 } 242 243 // Implements {@link KeyboardState.SwitchActions}. 244 @Override 245 public void setAlphabetShiftLockShiftedKeyboard() { 246 if (DEBUG_ACTION) { 247 Log.d(TAG, "setAlphabetShiftLockShiftedKeyboard"); 248 } 249 setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardSwitchState.OTHER); 250 } 251 252 // Implements {@link KeyboardState.SwitchActions}. 253 @Override 254 public void setSymbolsKeyboard() { 255 if (DEBUG_ACTION) { 256 Log.d(TAG, "setSymbolsKeyboard"); 257 } 258 setKeyboard(KeyboardId.ELEMENT_SYMBOLS, KeyboardSwitchState.OTHER); 259 } 260 261 // Implements {@link KeyboardState.SwitchActions}. 262 @Override 263 public void setSymbolsShiftedKeyboard() { 264 if (DEBUG_ACTION) { 265 Log.d(TAG, "setSymbolsShiftedKeyboard"); 266 } 267 setKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED, KeyboardSwitchState.SYMBOLS_SHIFTED); 268 } 269 270 public boolean isImeSuppressedByHardwareKeyboard( 271 @Nonnull final SettingsValues settingsValues, 272 @Nonnull final KeyboardSwitchState toggleState) { 273 return settingsValues.mHasHardwareKeyboard && toggleState == KeyboardSwitchState.HIDDEN; 274 } 275 276 private void setMainKeyboardFrame( 277 @Nonnull final SettingsValues settingsValues, 278 @Nonnull final KeyboardSwitchState toggleState) { 279 final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState) 280 ? View.GONE : View.VISIBLE; 281 mKeyboardView.setVisibility(visibility); 282 // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. 283 // @see #getVisibleKeyboardView() and 284 // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets) 285 mMainKeyboardFrame.setVisibility(visibility); 286 mEmojiPalettesView.setVisibility(View.GONE); 287 mEmojiPalettesView.stopEmojiPalettes(); 288 } 289 290 // Implements {@link KeyboardState.SwitchActions}. 291 @Override 292 public void setEmojiKeyboard() { 293 if (DEBUG_ACTION) { 294 Log.d(TAG, "setEmojiKeyboard"); 295 } 296 final Keyboard keyboard = mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); 297 mMainKeyboardFrame.setVisibility(View.GONE); 298 // The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}. 299 // @see #getVisibleKeyboardView() and 300 // @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets) 301 mKeyboardView.setVisibility(View.GONE); 302 mEmojiPalettesView.startEmojiPalettes( 303 mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL), 304 mKeyboardView.getKeyVisualAttribute(), keyboard.mIconsSet); 305 mEmojiPalettesView.setVisibility(View.VISIBLE); 306 } 307 308 public enum KeyboardSwitchState { 309 HIDDEN(-1), 310 SYMBOLS_SHIFTED(KeyboardId.ELEMENT_SYMBOLS_SHIFTED), 311 EMOJI(KeyboardId.ELEMENT_EMOJI_RECENTS), 312 OTHER(-1); 313 314 final int mKeyboardId; 315 316 KeyboardSwitchState(int keyboardId) { 317 mKeyboardId = keyboardId; 318 } 319 } 320 321 public KeyboardSwitchState getKeyboardSwitchState() { 322 boolean hidden = !isShowingEmojiPalettes() 323 && (mKeyboardLayoutSet == null 324 || mKeyboardView == null 325 || !mKeyboardView.isShown()); 326 KeyboardSwitchState state; 327 if (hidden) { 328 return KeyboardSwitchState.HIDDEN; 329 } else if (isShowingEmojiPalettes()) { 330 return KeyboardSwitchState.EMOJI; 331 } else if (isShowingKeyboardId(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)) { 332 return KeyboardSwitchState.SYMBOLS_SHIFTED; 333 } 334 return KeyboardSwitchState.OTHER; 335 } 336 337 public void onToggleKeyboard(@Nonnull final KeyboardSwitchState toggleState) { 338 KeyboardSwitchState currentState = getKeyboardSwitchState(); 339 Log.w(TAG, "onToggleKeyboard() : Current = " + currentState + " : Toggle = " + toggleState); 340 if (currentState == toggleState) { 341 mLatinIME.stopShowingInputView(); 342 mLatinIME.hideWindow(); 343 setAlphabetKeyboard(); 344 } else { 345 mLatinIME.startShowingInputView(true); 346 if (toggleState == KeyboardSwitchState.EMOJI) { 347 setEmojiKeyboard(); 348 } else { 349 mEmojiPalettesView.stopEmojiPalettes(); 350 mEmojiPalettesView.setVisibility(View.GONE); 351 352 mMainKeyboardFrame.setVisibility(View.VISIBLE); 353 mKeyboardView.setVisibility(View.VISIBLE); 354 setKeyboard(toggleState.mKeyboardId, toggleState); 355 } 356 } 357 } 358 359 // Future method for requesting an updating to the shift state. 360 @Override 361 public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode) { 362 if (DEBUG_ACTION) { 363 Log.d(TAG, "requestUpdatingShiftState: " 364 + " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags) 365 + " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode)); 366 } 367 mState.onUpdateShiftState(autoCapsFlags, recapitalizeMode); 368 } 369 370 // Implements {@link KeyboardState.SwitchActions}. 371 @Override 372 public void startDoubleTapShiftKeyTimer() { 373 if (DEBUG_TIMER_ACTION) { 374 Log.d(TAG, "startDoubleTapShiftKeyTimer"); 375 } 376 final MainKeyboardView keyboardView = getMainKeyboardView(); 377 if (keyboardView != null) { 378 keyboardView.startDoubleTapShiftKeyTimer(); 379 } 380 } 381 382 // Implements {@link KeyboardState.SwitchActions}. 383 @Override 384 public void cancelDoubleTapShiftKeyTimer() { 385 if (DEBUG_TIMER_ACTION) { 386 Log.d(TAG, "setAlphabetKeyboard"); 387 } 388 final MainKeyboardView keyboardView = getMainKeyboardView(); 389 if (keyboardView != null) { 390 keyboardView.cancelDoubleTapShiftKeyTimer(); 391 } 392 } 393 394 // Implements {@link KeyboardState.SwitchActions}. 395 @Override 396 public boolean isInDoubleTapShiftKeyTimeout() { 397 if (DEBUG_TIMER_ACTION) { 398 Log.d(TAG, "isInDoubleTapShiftKeyTimeout"); 399 } 400 final MainKeyboardView keyboardView = getMainKeyboardView(); 401 return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout(); 402 } 403 404 /** 405 * Updates state machine to figure out when to automatically switch back to the previous mode. 406 */ 407 public void onEvent(final Event event, final int currentAutoCapsState, 408 final int currentRecapitalizeState) { 409 mState.onEvent(event, currentAutoCapsState, currentRecapitalizeState); 410 } 411 412 public boolean isShowingKeyboardId(@Nonnull int... keyboardIds) { 413 if (mKeyboardView == null || !mKeyboardView.isShown()) { 414 return false; 415 } 416 int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId; 417 for (int keyboardId : keyboardIds) { 418 if (activeKeyboardId == keyboardId) { 419 return true; 420 } 421 } 422 return false; 423 } 424 425 public boolean isShowingEmojiPalettes() { 426 return mEmojiPalettesView != null && mEmojiPalettesView.isShown(); 427 } 428 429 public boolean isShowingMoreKeysPanel() { 430 if (isShowingEmojiPalettes()) { 431 return false; 432 } 433 return mKeyboardView.isShowingMoreKeysPanel(); 434 } 435 436 public View getVisibleKeyboardView() { 437 if (isShowingEmojiPalettes()) { 438 return mEmojiPalettesView; 439 } 440 return mKeyboardView; 441 } 442 443 public MainKeyboardView getMainKeyboardView() { 444 return mKeyboardView; 445 } 446 447 public void deallocateMemory() { 448 if (mKeyboardView != null) { 449 mKeyboardView.cancelAllOngoingEvents(); 450 mKeyboardView.deallocateMemory(); 451 } 452 if (mEmojiPalettesView != null) { 453 mEmojiPalettesView.stopEmojiPalettes(); 454 } 455 } 456 457 public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) { 458 if (mKeyboardView != null) { 459 mKeyboardView.closing(); 460 } 461 462 updateKeyboardThemeAndContextThemeWrapper( 463 mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */)); 464 mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate( 465 R.layout.input_view, null); 466 mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame); 467 mEmojiPalettesView = (EmojiPalettesView)mCurrentInputView.findViewById( 468 R.id.emoji_palettes_view); 469 470 mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); 471 mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled); 472 mKeyboardView.setKeyboardActionListener(mLatinIME); 473 mEmojiPalettesView.setHardwareAcceleratedDrawingEnabled( 474 isHardwareAcceleratedDrawingEnabled); 475 mEmojiPalettesView.setKeyboardActionListener(mLatinIME); 476 return mCurrentInputView; 477 } 478 479 public int getKeyboardShiftMode() { 480 final Keyboard keyboard = getKeyboard(); 481 if (keyboard == null) { 482 return WordComposer.CAPS_MODE_OFF; 483 } 484 switch (keyboard.mId.mElementId) { 485 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: 486 case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: 487 return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED; 488 case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: 489 return WordComposer.CAPS_MODE_MANUAL_SHIFTED; 490 case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED: 491 return WordComposer.CAPS_MODE_AUTO_SHIFTED; 492 default: 493 return WordComposer.CAPS_MODE_OFF; 494 } 495 } 496 497 public int getCurrentKeyboardScriptId() { 498 if (null == mKeyboardLayoutSet) { 499 return ScriptUtils.SCRIPT_UNKNOWN; 500 } 501 return mKeyboardLayoutSet.getScriptId(); 502 } 503 } 504