diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
4 files changed, 238 insertions, 99 deletions
diff --git a/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java index 2529424c0..8924e0a3d 100644 --- a/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java +++ b/java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java @@ -18,14 +18,15 @@ package com.android.inputmethod.latin; import android.content.res.Resources; import android.util.Log; +import android.util.Pair; import android.view.KeyEvent; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.settings.Settings; -import java.util.HashMap; +import java.util.ArrayList; import java.util.HashSet; -import java.util.Map; +import java.util.List; import java.util.Set; import javax.annotation.Nonnull; @@ -35,121 +36,172 @@ import javax.annotation.Nonnull; */ final class EmojiAltPhysicalKeyDetector { private static final String TAG = "EmojiAltPhysicalKeyDetector"; + private static final boolean DEBUG = false; - private final Map<Integer, Integer> mEmojiSwitcherMap; - private final Map<Integer, Integer> mSymbolsShiftedSwitcherMap; - private final Map<Integer, Integer> mCombinedSwitcherMap; + private List<EmojiHotKeys> mHotKeysList; - // Set of keys codes that have been used as modifiers. - private Set<Integer> mActiveModifiers; + private static class HotKeySet extends HashSet<Pair<Integer, Integer>> { }; + + private abstract class EmojiHotKeys { + private final String mName; + private final HotKeySet mKeySet; + + boolean mCanFire; + int mMetaState; + + public EmojiHotKeys(final String name, HotKeySet keySet) { + mName = name; + mKeySet = keySet; + mCanFire = false; + } + + public void onKeyDown(@Nonnull final KeyEvent keyEvent) { + if (DEBUG) { + Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - considering " + keyEvent); + } + + final Pair<Integer, Integer> key = + Pair.create(keyEvent.getKeyCode(), keyEvent.getMetaState()); + if (mKeySet.contains(key)) { + if (DEBUG) { + Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - enabling action"); + } + mCanFire = true; + mMetaState = keyEvent.getMetaState(); + } else if (mCanFire) { + if (DEBUG) { + Log.d(TAG, "EmojiHotKeys.onKeyDown() - " + mName + " - disabling action"); + } + mCanFire = false; + } + } + + public void onKeyUp(@Nonnull final KeyEvent keyEvent) { + if (DEBUG) { + Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - considering " + keyEvent); + } + + final int keyCode = keyEvent.getKeyCode(); + int metaState = keyEvent.getMetaState(); + if (KeyEvent.isModifierKey(keyCode)) { + // Try restoring meta stat in case the released key was a modifier. + // I am sure one can come up with scenarios to break this, but it + // seems to work well in practice. + metaState |= mMetaState; + } + + final Pair<Integer, Integer> key = Pair.create(keyCode, metaState); + if (mKeySet.contains(key)) { + if (mCanFire) { + if (!keyEvent.isCanceled()) { + if (DEBUG) { + Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - firing action"); + } + action(); + } else { + // This key up event was a part of key combinations and + // should be ignored. + if (DEBUG) { + Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - canceled, ignoring action"); + } + } + mCanFire = false; + } + } + + if (mCanFire) { + if (DEBUG) { + Log.d(TAG, "EmojiHotKeys.onKeyUp() - " + mName + " - disabling action"); + } + mCanFire = false; + } + } + + protected abstract void action(); + } public EmojiAltPhysicalKeyDetector(@Nonnull final Resources resources) { - mEmojiSwitcherMap = parseSwitchDefinition(resources, R.array.keyboard_switcher_emoji); - mSymbolsShiftedSwitcherMap = parseSwitchDefinition( + mHotKeysList = new ArrayList<EmojiHotKeys>(); + + final HotKeySet emojiSwitchSet = parseHotKeys( + resources, R.array.keyboard_switcher_emoji); + final EmojiHotKeys emojiHotKeys = new EmojiHotKeys("emoji", emojiSwitchSet) { + @Override + protected void action() { + final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); + switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.EMOJI); + } + }; + mHotKeysList.add(emojiHotKeys); + + final HotKeySet symbolsSwitchSet = parseHotKeys( resources, R.array.keyboard_switcher_symbols_shifted); - mCombinedSwitcherMap = new HashMap<>(); - mCombinedSwitcherMap.putAll(mEmojiSwitcherMap); - mCombinedSwitcherMap.putAll(mSymbolsShiftedSwitcherMap); - mActiveModifiers = new HashSet<>(); + final EmojiHotKeys symbolsHotKeys = new EmojiHotKeys("symbols", symbolsSwitchSet) { + @Override + protected void action() { + final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); + switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.SYMBOLS_SHIFTED); + } + }; + mHotKeysList.add(symbolsHotKeys); } - private static Map<Integer, Integer> parseSwitchDefinition( - @Nonnull final Resources resources, - final int resourceId) { - final Map<Integer, Integer> definition = new HashMap<>(); - final String name = resources.getResourceEntryName(resourceId); - final String[] values = resources.getStringArray(resourceId); - for (int i = 0; values != null && i < values.length; i++) { - String[] valuePair = values[i].split(","); - if (valuePair.length != 2) { - Log.w(TAG, "Expected 2 integers in " + name + "[" + i + "] : " + values[i]); - } - try { - definition.put(Integer.parseInt(valuePair[0]), Integer.parseInt(valuePair[1])); - } catch (NumberFormatException e) { - Log.w(TAG, "Failed to parse " + name + "[" + i + "] : " + values[i], e); + public void onKeyDown(@Nonnull final KeyEvent keyEvent) { + if (DEBUG) { + Log.d(TAG, "onKeyDown(): " + keyEvent); + } + + if (shouldProcessEvent(keyEvent)) { + for (EmojiHotKeys hotKeys : mHotKeysList) { + hotKeys.onKeyDown(keyEvent); } } - return definition; } - /** - * Determine whether an up key event came from a mapped modifier key. - * - * @param keyEvent an up key event. - */ public void onKeyUp(@Nonnull final KeyEvent keyEvent) { - Log.d(TAG, "onKeyUp() : " + keyEvent); - if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { - // The feature is disabled. - Log.d(TAG, "onKeyUp() : Disabled"); - return; + if (DEBUG) { + Log.d(TAG, "onKeyUp(): " + keyEvent); } - if (keyEvent.isCanceled()) { - // This key up event was a part of key combinations and should be ignored. - Log.d(TAG, "onKeyUp() : Canceled"); - return; - } - final Integer mappedModifier = getMappedModifier(keyEvent); - if (mappedModifier != null) { - // If the key was modified by a mapped key, then ignore the next time - // the same modifier key comes up. - Log.d(TAG, "onKeyUp() : Using Modifier: " + mappedModifier); - mActiveModifiers.add(mappedModifier); - return; - } - final int keyCode = keyEvent.getKeyCode(); - if (mActiveModifiers.contains(keyCode)) { - // Used as a modifier, not a standalone key press. - Log.d(TAG, "onKeyUp() : Used as Modifier: " + keyCode); - mActiveModifiers.remove(keyCode); - return; - } - if (!isMappedKeyCode(keyEvent)) { - // Nothing special about this key. - Log.d(TAG, "onKeyUp() : Not Mapped: " + keyCode); - return; - } - final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); - if (mEmojiSwitcherMap.keySet().contains(keyCode)) { - switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.EMOJI); - } else if (mSymbolsShiftedSwitcherMap.keySet().contains(keyCode)) { - switcher.onToggleKeyboard(KeyboardSwitcher.KeyboardSwitchState.SYMBOLS_SHIFTED); - } else { - Log.w(TAG, "Cannot toggle on keyCode: " + keyCode); + + if (shouldProcessEvent(keyEvent)) { + for (EmojiHotKeys hotKeys : mHotKeysList) { + hotKeys.onKeyUp(keyEvent); + } } } - /** - * @param keyEvent pressed key event - * @return true iff the user pressed a mapped modifier key. - */ - private boolean isMappedKeyCode(@Nonnull final KeyEvent keyEvent) { - return mCombinedSwitcherMap.get(keyEvent.getKeyCode()) != null; + private static boolean shouldProcessEvent(@Nonnull final KeyEvent keyEvent) { + if (!Settings.getInstance().getCurrent().mEnableEmojiAltPhysicalKey) { + // The feature is disabled. + if (DEBUG) { + Log.d(TAG, "shouldProcessEvent(): Disabled"); + } + return false; + } + + return true; } - /** - * @param keyEvent pressed key event - * @return the mapped modifier used with this key opress, if any. - */ - private Integer getMappedModifier(@Nonnull final KeyEvent keyEvent) { - final int keyCode = keyEvent.getKeyCode(); - final int metaState = keyEvent.getMetaState(); - for (int mappedKeyCode : mCombinedSwitcherMap.keySet()) { - if (keyCode == mappedKeyCode) { - Log.d(TAG, "getMappedModifier() : KeyCode = MappedKeyCode = " + mappedKeyCode); - continue; - } - final Integer mappedMeta = mCombinedSwitcherMap.get(mappedKeyCode); - if (mappedMeta == null || mappedMeta.intValue() == -1) { - continue; + private static HotKeySet parseHotKeys( + @Nonnull final Resources resources, final int resourceId) { + final HotKeySet keySet = new HotKeySet(); + final String name = resources.getResourceEntryName(resourceId); + final String[] values = resources.getStringArray(resourceId); + for (int i = 0; values != null && i < values.length; i++) { + String[] valuePair = values[i].split(","); + if (valuePair.length != 2) { + Log.w(TAG, "Expected 2 integers in " + name + "[" + i + "] : " + values[i]); } - if ((metaState & mappedMeta) != 0) { - Log.d(TAG, "getMappedModifier() : MetaState(" + metaState - + ") contains MappedMeta(" + mappedMeta + ")"); - return mappedKeyCode; + try { + final Integer keyCode = Integer.parseInt(valuePair[0]); + final Integer metaState = Integer.parseInt(valuePair[1]); + final Pair<Integer, Integer> key = Pair.create( + keyCode, KeyEvent.normalizeMetaState(metaState)); + keySet.add(key); + } catch (NumberFormatException e) { + Log.w(TAG, "Failed to parse " + name + "[" + i + "] : " + values[i], e); } } - return null; + return keySet; } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 25a5de250..c2d9f965f 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Color; import android.inputmethodservice.InputMethodService; import android.media.AudioManager; import android.os.Debug; @@ -53,7 +54,9 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.compat.InputMethodServiceCompatUtils; +import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils; import com.android.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater; import com.android.inputmethod.dictionarypack.DictionaryPackConstants; @@ -175,8 +178,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8; private static final int MSG_DEALLOCATE_MEMORY = 9; private static final int MSG_RESUME_SUGGESTIONS_FOR_START_INPUT = 10; + private static final int MSG_SWITCH_LANGUAGE_AUTOMATICALLY = 11; // Update this when adding new messages - private static final int MSG_LAST = MSG_RESUME_SUGGESTIONS_FOR_START_INPUT; + private static final int MSG_LAST = MSG_SWITCH_LANGUAGE_AUTOMATICALLY; private static final int ARG1_NOT_GESTURE_INPUT = 0; private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; @@ -270,6 +274,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case MSG_DEALLOCATE_MEMORY: latinIme.deallocateMemory(); break; + case MSG_SWITCH_LANGUAGE_AUTOMATICALLY: + latinIme.switchLanguage((InputMethodSubtype)msg.obj); + break; } } @@ -388,6 +395,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget(); } + public void postSwitchLanguage(final InputMethodSubtype subtype) { + obtainMessage(MSG_SWITCH_LANGUAGE_AUTOMATICALLY, subtype).sendToTarget(); + } + // Working variables for the following methods. private boolean mIsOrientationChanging; private boolean mPendingSuccessiveImsCallback; @@ -794,6 +805,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen void onStartInputInternal(final EditorInfo editorInfo, final boolean restarting) { super.onStartInput(editorInfo, restarting); + + // If the primary hint language does not match the current subtype language, then try + // to switch to the primary hint language. + // TODO: Support all the locales in EditorInfo#hintLocales. + final Locale primaryHintLocale = EditorInfoCompatUtils.getPrimaryHintLocale(editorInfo); + if (primaryHintLocale == null) { + return; + } + final InputMethodSubtype newSubtype = mRichImm.findSubtypeByLocale(primaryHintLocale); + if (newSubtype == null || newSubtype.equals(mRichImm.getCurrentSubtype().getRawSubtype())) { + return; + } + mHandler.postSwitchLanguage(newSubtype); } @SuppressWarnings("deprecation") @@ -968,12 +992,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } @Override + public void onWindowShown() { + super.onWindowShown(); + setNavigationBarVisibility(isInputViewShown()); + } + + @Override public void onWindowHidden() { super.onWindowHidden(); final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (mainKeyboardView != null) { mainKeyboardView.closing(); } + setNavigationBarVisibility(false); } void onFinishInputInternal() { @@ -1293,6 +1324,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return mOptionsDialog != null && mOptionsDialog.isShowing(); } + public void switchLanguage(final InputMethodSubtype subtype) { + final IBinder token = getWindow().getWindow().getAttributes().token; + mRichImm.setInputMethodAndSubtype(token, subtype); + } + // TODO: Revise the language switch key behavior to make it much smarter and more reasonable. public void switchToNextSubtype() { final IBinder token = getWindow().getWindow().getAttributes().token; @@ -1650,6 +1686,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Hooks for hardware keyboard @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { + if (mEmojiAltPhysicalKeyDetector == null) { + mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( + getApplicationContext().getResources()); + } + mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent); if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { return super.onKeyDown(keyCode, keyEvent); } @@ -1865,4 +1906,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } return mRichImm.shouldOfferSwitchingToNextInputMethod(token, fallbackValue); } + + private void setNavigationBarVisibility(final boolean visible) { + // Color.BLACK is ignored and default IME navigation bar color is used. + getWindow().getWindow().setNavigationBarColor(visible ? Color.BLACK : Color.TRANSPARENT); + } } diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index ef946c8bc..3beb51d68 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -32,6 +32,7 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; import com.android.inputmethod.latin.utils.LanguageOnSpacebarUtils; @@ -428,6 +429,46 @@ public class RichInputMethodManager { return null; } + public InputMethodSubtype findSubtypeByLocale(final Locale locale) { + // Find the best subtype based on a straightforward matching algorithm. + // TODO: Use LocaleList#getFirstMatch() instead. + final List<InputMethodSubtype> subtypes = + getMyEnabledInputMethodSubtypeList(true /* allowsImplicitlySelectedSubtypes */); + final int count = subtypes.size(); + for (int i = 0; i < count; ++i) { + final InputMethodSubtype subtype = subtypes.get(i); + final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); + if (subtypeLocale.equals(locale)) { + return subtype; + } + } + for (int i = 0; i < count; ++i) { + final InputMethodSubtype subtype = subtypes.get(i); + final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); + if (subtypeLocale.getLanguage().equals(locale.getLanguage()) && + subtypeLocale.getCountry().equals(locale.getCountry()) && + subtypeLocale.getVariant().equals(locale.getVariant())) { + return subtype; + } + } + for (int i = 0; i < count; ++i) { + final InputMethodSubtype subtype = subtypes.get(i); + final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); + if (subtypeLocale.getLanguage().equals(locale.getLanguage()) && + subtypeLocale.getCountry().equals(locale.getCountry())) { + return subtype; + } + } + for (int i = 0; i < count; ++i) { + final InputMethodSubtype subtype = subtypes.get(i); + final Locale subtypeLocale = InputMethodSubtypeCompatUtils.getLocaleObject(subtype); + if (subtypeLocale.getLanguage().equals(locale.getLanguage())) { + return subtype; + } + } + return null; + } + public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) { mImmWrapper.mImm.setInputMethodAndSubtype( token, getInputMethodIdOfThisIme(), subtype); diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java index 9d7849ffc..71aaf5e01 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java @@ -47,7 +47,7 @@ public class RichInputMethodSubtype { public RichInputMethodSubtype(@Nonnull final InputMethodSubtype subtype) { mSubtype = subtype; - mLocale = LocaleUtils.constructLocaleFromString(mSubtype.getLocale()); + mLocale = InputMethodSubtypeCompatUtils.getLocaleObject(mSubtype); } // Extra values are determined by the primary subtype. This is probably right, but |