LatinIME/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
Dan Zivkovic 1fdb8f3156 Define shortcuts for toggling IME layouts.
Out of the box, we want Alt-Left to toggle Emojis, while Alt-Right
toggles the shifted symbols layout.

Bug: 23954008
Bug: 24369173

Change-Id: I93dd66fb469e5d0a831359ff3a786fe68e1d73ea
(cherry picked from commit 411841b374aa04e333ea5a438dfd539f49ec589a)
2015-10-15 12:47:32 -07:00

503 lines
20 KiB
Java

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.keyboard;
import android.content.Context;
import android.content.res.Resources;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
import com.android.inputmethod.event.Event;
import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
import com.android.inputmethod.keyboard.emoji.EmojiPalettesView;
import com.android.inputmethod.keyboard.internal.KeyboardState;
import com.android.inputmethod.keyboard.internal.KeyboardTextsSet;
import com.android.inputmethod.latin.InputView;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.RichInputMethodManager;
import com.android.inputmethod.latin.WordComposer;
import com.android.inputmethod.latin.define.ProductionFlags;
import com.android.inputmethod.latin.settings.Settings;
import com.android.inputmethod.latin.settings.SettingsValues;
import com.android.inputmethod.latin.utils.CapsModeUtils;
import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils;
import com.android.inputmethod.latin.utils.RecapitalizeStatus;
import com.android.inputmethod.latin.utils.ResourceUtils;
import com.android.inputmethod.latin.utils.ScriptUtils;
import javax.annotation.Nonnull;
public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private static final String TAG = KeyboardSwitcher.class.getSimpleName();
private InputView mCurrentInputView;
private View mMainKeyboardFrame;
private MainKeyboardView mKeyboardView;
private EmojiPalettesView mEmojiPalettesView;
private LatinIME mLatinIME;
private RichInputMethodManager mRichImm;
private boolean mIsHardwareAcceleratedDrawingEnabled;
private KeyboardState mState;
private KeyboardLayoutSet mKeyboardLayoutSet;
// TODO: The following {@link KeyboardTextsSet} should be in {@link KeyboardLayoutSet}.
private final KeyboardTextsSet mKeyboardTextsSet = new KeyboardTextsSet();
private KeyboardTheme mKeyboardTheme;
private Context mThemeContext;
private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
public static KeyboardSwitcher getInstance() {
return sInstance;
}
private KeyboardSwitcher() {
// Intentional empty constructor for singleton.
}
public static void init(final LatinIME latinIme) {
sInstance.initInternal(latinIme);
}
private void initInternal(final LatinIME latinIme) {
mLatinIME = latinIme;
mRichImm = RichInputMethodManager.getInstance();
mState = new KeyboardState(this);
mIsHardwareAcceleratedDrawingEnabled =
InputMethodServiceCompatUtils.enableHardwareAcceleration(mLatinIME);
}
public void updateKeyboardTheme() {
final boolean themeUpdated = updateKeyboardThemeAndContextThemeWrapper(
mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
if (themeUpdated && mKeyboardView != null) {
mLatinIME.setInputView(onCreateInputView(mIsHardwareAcceleratedDrawingEnabled));
}
}
private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context,
final KeyboardTheme keyboardTheme) {
if (mThemeContext == null || !keyboardTheme.equals(mKeyboardTheme)) {
mKeyboardTheme = keyboardTheme;
mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
KeyboardLayoutSet.onKeyboardThemeChanged();
return true;
}
return false;
}
public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues,
final int currentAutoCapsState, final int currentRecapitalizeState) {
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
mThemeContext, editorInfo);
final Resources res = mThemeContext.getResources();
final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res);
final int keyboardHeight = ResourceUtils.getKeyboardHeight(res, settingsValues);
builder.setKeyboardGeometry(keyboardWidth, keyboardHeight);
builder.setSubtype(mRichImm.getCurrentSubtype());
builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey);
builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey());
builder.setSplitLayoutEnabledByUser(ProductionFlags.IS_SPLIT_KEYBOARD_SUPPORTED
&& settingsValues.mIsSplitKeyboardEnabled);
mKeyboardLayoutSet = builder.build();
try {
mState.onLoadKeyboard(currentAutoCapsState, currentRecapitalizeState);
mKeyboardTextsSet.setLocale(mRichImm.getCurrentSubtypeLocale(), mThemeContext);
} catch (KeyboardLayoutSetException e) {
Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
}
}
public void saveKeyboardState() {
if (getKeyboard() != null || isShowingEmojiPalettes()) {
mState.onSaveKeyboardState();
}
}
public void onHideWindow() {
if (mKeyboardView != null) {
mKeyboardView.onHideWindow();
}
}
private void setKeyboard(
@Nonnull final int keyboardId,
@Nonnull final KeyboardSwitchState toggleState) {
// Make {@link MainKeyboardView} visible and hide {@link EmojiPalettesView}.
final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
setMainKeyboardFrame(currentSettingsValues, toggleState);
// TODO: pass this object to setKeyboard instead of getting the current values.
final MainKeyboardView keyboardView = mKeyboardView;
final Keyboard oldKeyboard = keyboardView.getKeyboard();
final Keyboard newKeyboard = mKeyboardLayoutSet.getKeyboard(keyboardId);
keyboardView.setKeyboard(newKeyboard);
mCurrentInputView.setKeyboardTopPadding(newKeyboard.mTopPadding);
keyboardView.setKeyPreviewPopupEnabled(
currentSettingsValues.mKeyPreviewPopupOn,
currentSettingsValues.mKeyPreviewPopupDismissDelay);
keyboardView.setKeyPreviewAnimationParams(
currentSettingsValues.mHasCustomKeyPreviewAnimationParams,
currentSettingsValues.mKeyPreviewShowUpStartXScale,
currentSettingsValues.mKeyPreviewShowUpStartYScale,
currentSettingsValues.mKeyPreviewShowUpDuration,
currentSettingsValues.mKeyPreviewDismissEndXScale,
currentSettingsValues.mKeyPreviewDismissEndYScale,
currentSettingsValues.mKeyPreviewDismissDuration);
keyboardView.updateShortcutKey(mRichImm.isShortcutImeReady());
final boolean subtypeChanged = (oldKeyboard == null)
|| !newKeyboard.mId.mSubtype.equals(oldKeyboard.mId.mSubtype);
final int languageOnSpacebarFormatType = LanguageOnSpacebarUtils
.getLanguageOnSpacebarFormatType(newKeyboard.mId.mSubtype);
final boolean hasMultipleEnabledIMEsOrSubtypes = mRichImm
.hasMultipleEnabledIMEsOrSubtypes(true /* shouldIncludeAuxiliarySubtypes */);
keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, languageOnSpacebarFormatType,
hasMultipleEnabledIMEsOrSubtypes);
}
public Keyboard getKeyboard() {
if (mKeyboardView != null) {
return mKeyboardView.getKeyboard();
}
return null;
}
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
// when a keyboard layout set doesn't get reloaded in LatinIME.onStartInputViewInternal().
public void resetKeyboardStateToAlphabet(final int currentAutoCapsState,
final int currentRecapitalizeState) {
mState.onResetKeyboardStateToAlphabet(currentAutoCapsState, currentRecapitalizeState);
}
public void onPressKey(final int code, final boolean isSinglePointer,
final int currentAutoCapsState, final int currentRecapitalizeState) {
mState.onPressKey(code, isSinglePointer, currentAutoCapsState, currentRecapitalizeState);
}
public void onReleaseKey(final int code, final boolean withSliding,
final int currentAutoCapsState, final int currentRecapitalizeState) {
mState.onReleaseKey(code, withSliding, currentAutoCapsState, currentRecapitalizeState);
}
public void onFinishSlidingInput(final int currentAutoCapsState,
final int currentRecapitalizeState) {
mState.onFinishSlidingInput(currentAutoCapsState, currentRecapitalizeState);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setAlphabetKeyboard");
}
setKeyboard(KeyboardId.ELEMENT_ALPHABET, KeyboardSwitchState.OTHER);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetManualShiftedKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setAlphabetManualShiftedKeyboard");
}
setKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED, KeyboardSwitchState.OTHER);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetAutomaticShiftedKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setAlphabetAutomaticShiftedKeyboard");
}
setKeyboard(KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED, KeyboardSwitchState.OTHER);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetShiftLockedKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setAlphabetShiftLockedKeyboard");
}
setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED, KeyboardSwitchState.OTHER);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setAlphabetShiftLockShiftedKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setAlphabetShiftLockShiftedKeyboard");
}
setKeyboard(KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED, KeyboardSwitchState.OTHER);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setSymbolsKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setSymbolsKeyboard");
}
setKeyboard(KeyboardId.ELEMENT_SYMBOLS, KeyboardSwitchState.OTHER);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setSymbolsShiftedKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setSymbolsShiftedKeyboard");
}
setKeyboard(KeyboardId.ELEMENT_SYMBOLS_SHIFTED, KeyboardSwitchState.SYMBOLS_SHIFTED);
}
public boolean isImeSuppressedByHardwareKeyboard(
@Nonnull final SettingsValues settingsValues,
@Nonnull final KeyboardSwitchState toggleState) {
return settingsValues.mHasHardwareKeyboard && toggleState == KeyboardSwitchState.HIDDEN;
}
private void setMainKeyboardFrame(
@Nonnull final SettingsValues settingsValues,
@Nonnull final KeyboardSwitchState toggleState) {
final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState)
? View.GONE : View.VISIBLE;
mKeyboardView.setVisibility(visibility);
// The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}.
// @see #getVisibleKeyboardView() and
// @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets)
mMainKeyboardFrame.setVisibility(visibility);
mEmojiPalettesView.setVisibility(View.GONE);
mEmojiPalettesView.stopEmojiPalettes();
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setEmojiKeyboard() {
if (DEBUG_ACTION) {
Log.d(TAG, "setEmojiKeyboard");
}
final Keyboard keyboard = mKeyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET);
mMainKeyboardFrame.setVisibility(View.GONE);
// The visibility of {@link #mKeyboardView} must be aligned with {@link #MainKeyboardFrame}.
// @see #getVisibleKeyboardView() and
// @see LatinIME#onComputeInset(android.inputmethodservice.InputMethodService.Insets)
mKeyboardView.setVisibility(View.GONE);
mEmojiPalettesView.startEmojiPalettes(
mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL),
mKeyboardView.getKeyVisualAttribute(), keyboard.mIconsSet);
mEmojiPalettesView.setVisibility(View.VISIBLE);
}
public enum KeyboardSwitchState {
HIDDEN(-1),
SYMBOLS_SHIFTED(KeyboardId.ELEMENT_SYMBOLS_SHIFTED),
EMOJI(KeyboardId.ELEMENT_EMOJI_RECENTS),
OTHER(-1);
final int mKeyboardId;
KeyboardSwitchState(int keyboardId) {
mKeyboardId = keyboardId;
}
}
public KeyboardSwitchState getKeyboardSwitchState() {
boolean hidden = !isShowingEmojiPalettes()
&& (mKeyboardLayoutSet == null
|| mKeyboardView == null
|| !mKeyboardView.isShown());
KeyboardSwitchState state;
if (hidden) {
return KeyboardSwitchState.HIDDEN;
} else if (isShowingEmojiPalettes()) {
return KeyboardSwitchState.EMOJI;
} else if (isShowingKeyboardId(KeyboardId.ELEMENT_SYMBOLS_SHIFTED)) {
return KeyboardSwitchState.SYMBOLS_SHIFTED;
}
return KeyboardSwitchState.OTHER;
}
public void onToggleKeyboard(@Nonnull final KeyboardSwitchState toggleState) {
KeyboardSwitchState currentState = getKeyboardSwitchState();
Log.w(TAG, "onToggleKeyboard() : Current = " + currentState + " : Toggle = " + toggleState);
if (currentState == toggleState) {
mLatinIME.stopShowingInputView();
mLatinIME.hideWindow();
setAlphabetKeyboard();
} else {
mLatinIME.startShowingInputView(true);
if (toggleState == KeyboardSwitchState.EMOJI) {
setEmojiKeyboard();
} else {
mEmojiPalettesView.stopEmojiPalettes();
mEmojiPalettesView.setVisibility(View.GONE);
mMainKeyboardFrame.setVisibility(View.VISIBLE);
mKeyboardView.setVisibility(View.VISIBLE);
setKeyboard(toggleState.mKeyboardId, toggleState);
}
}
}
// Future method for requesting an updating to the shift state.
@Override
public void requestUpdatingShiftState(final int autoCapsFlags, final int recapitalizeMode) {
if (DEBUG_ACTION) {
Log.d(TAG, "requestUpdatingShiftState: "
+ " autoCapsFlags=" + CapsModeUtils.flagsToString(autoCapsFlags)
+ " recapitalizeMode=" + RecapitalizeStatus.modeToString(recapitalizeMode));
}
mState.onUpdateShiftState(autoCapsFlags, recapitalizeMode);
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void startDoubleTapShiftKeyTimer() {
if (DEBUG_TIMER_ACTION) {
Log.d(TAG, "startDoubleTapShiftKeyTimer");
}
final MainKeyboardView keyboardView = getMainKeyboardView();
if (keyboardView != null) {
keyboardView.startDoubleTapShiftKeyTimer();
}
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void cancelDoubleTapShiftKeyTimer() {
if (DEBUG_TIMER_ACTION) {
Log.d(TAG, "setAlphabetKeyboard");
}
final MainKeyboardView keyboardView = getMainKeyboardView();
if (keyboardView != null) {
keyboardView.cancelDoubleTapShiftKeyTimer();
}
}
// Implements {@link KeyboardState.SwitchActions}.
@Override
public boolean isInDoubleTapShiftKeyTimeout() {
if (DEBUG_TIMER_ACTION) {
Log.d(TAG, "isInDoubleTapShiftKeyTimeout");
}
final MainKeyboardView keyboardView = getMainKeyboardView();
return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout();
}
/**
* Updates state machine to figure out when to automatically switch back to the previous mode.
*/
public void onEvent(final Event event, final int currentAutoCapsState,
final int currentRecapitalizeState) {
mState.onEvent(event, currentAutoCapsState, currentRecapitalizeState);
}
public boolean isShowingKeyboardId(@Nonnull int... keyboardIds) {
if (mKeyboardView == null || !mKeyboardView.isShown()) {
return false;
}
int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId;
for (int keyboardId : keyboardIds) {
if (activeKeyboardId == keyboardId) {
return true;
}
}
return false;
}
public boolean isShowingEmojiPalettes() {
return mEmojiPalettesView != null && mEmojiPalettesView.isShown();
}
public boolean isShowingMoreKeysPanel() {
if (isShowingEmojiPalettes()) {
return false;
}
return mKeyboardView.isShowingMoreKeysPanel();
}
public View getVisibleKeyboardView() {
if (isShowingEmojiPalettes()) {
return mEmojiPalettesView;
}
return mKeyboardView;
}
public MainKeyboardView getMainKeyboardView() {
return mKeyboardView;
}
public void deallocateMemory() {
if (mKeyboardView != null) {
mKeyboardView.cancelAllOngoingEvents();
mKeyboardView.deallocateMemory();
}
if (mEmojiPalettesView != null) {
mEmojiPalettesView.stopEmojiPalettes();
}
}
public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) {
if (mKeyboardView != null) {
mKeyboardView.closing();
}
updateKeyboardThemeAndContextThemeWrapper(
mLatinIME, KeyboardTheme.getKeyboardTheme(mLatinIME /* context */));
mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
R.layout.input_view, null);
mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
mEmojiPalettesView = (EmojiPalettesView)mCurrentInputView.findViewById(
R.id.emoji_palettes_view);
mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled);
mKeyboardView.setKeyboardActionListener(mLatinIME);
mEmojiPalettesView.setHardwareAcceleratedDrawingEnabled(
isHardwareAcceleratedDrawingEnabled);
mEmojiPalettesView.setKeyboardActionListener(mLatinIME);
return mCurrentInputView;
}
public int getKeyboardShiftMode() {
final Keyboard keyboard = getKeyboard();
if (keyboard == null) {
return WordComposer.CAPS_MODE_OFF;
}
switch (keyboard.mId.mElementId) {
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED:
case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED:
return WordComposer.CAPS_MODE_MANUAL_SHIFT_LOCKED;
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
return WordComposer.CAPS_MODE_MANUAL_SHIFTED;
case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
return WordComposer.CAPS_MODE_AUTO_SHIFTED;
default:
return WordComposer.CAPS_MODE_OFF;
}
}
public int getCurrentKeyboardScriptId() {
if (null == mKeyboardLayoutSet) {
return ScriptUtils.SCRIPT_UNKNOWN;
}
return mKeyboardLayoutSet.getScriptId();
}
}