aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java14
-rw-r--r--java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java18
-rw-r--r--java/src/com/android/inputmethod/compat/LocaleListCompatUtils.java34
-rw-r--r--java/src/com/android/inputmethod/compat/UserManagerCompatUtils.java80
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java20
-rw-r--r--java/src/com/android/inputmethod/latin/EmojiAltPhysicalKeyDetector.java246
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java48
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodManager.java41
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputMethodSubtype.java2
10 files changed, 413 insertions, 101 deletions
diff --git a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
index 7eefa221a..43714829a 100644
--- a/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/EditorInfoCompatUtils.java
@@ -19,6 +19,7 @@ package com.android.inputmethod.compat;
import android.view.inputmethod.EditorInfo;
import java.lang.reflect.Field;
+import java.util.Locale;
public final class EditorInfoCompatUtils {
// Note that EditorInfo.IME_FLAG_FORCE_ASCII has been introduced
@@ -27,6 +28,8 @@ public final class EditorInfoCompatUtils {
EditorInfo.class, "IME_FLAG_FORCE_ASCII");
private static final Integer OBJ_IME_FLAG_FORCE_ASCII = (Integer) CompatUtils.getFieldValue(
null /* receiver */, null /* defaultValue */, FIELD_IME_FLAG_FORCE_ASCII);
+ private static final Field FIELD_HINT_LOCALES = CompatUtils.getField(
+ EditorInfo.class, "hintLocales");
private EditorInfoCompatUtils() {
// This utility class is not publicly instantiable.
@@ -78,4 +81,15 @@ public final class EditorInfoCompatUtils {
}
return (action != null) ? flags + action : flags.toString();
}
+
+ public static Locale getPrimaryHintLocale(final EditorInfo editorInfo) {
+ if (editorInfo == null) {
+ return null;
+ }
+ final Object localeList = CompatUtils.getFieldValue(editorInfo, null, FIELD_HINT_LOCALES);
+ if (localeList == null) {
+ return null;
+ }
+ return LocaleListCompatUtils.getPrimary(localeList);
+ }
}
diff --git a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
index 58ad4bd4c..d123a1799 100644
--- a/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/InputMethodSubtypeCompatUtils.java
@@ -17,14 +17,17 @@
package com.android.inputmethod.compat;
import android.os.Build;
+import android.text.TextUtils;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.RichInputMethodSubtype;
import com.android.inputmethod.latin.common.Constants;
+import com.android.inputmethod.latin.common.LocaleUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
+import java.util.Locale;
import javax.annotation.Nonnull;
@@ -78,6 +81,21 @@ public final class InputMethodSubtypeCompatUtils {
|| subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.ASCII_CAPABLE);
}
+ // Note that InputMethodSubtype.getLanguageTag() is expected to be available in Android N+.
+ private static final Method GET_LANGUAGE_TAG =
+ CompatUtils.getMethod(InputMethodSubtype.class, "getLanguageTag");
+
+ public static Locale getLocaleObject(final InputMethodSubtype subtype) {
+ // Locale.forLanguageTag() is available only in Android L and later.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ final String languageTag = (String) CompatUtils.invoke(subtype, null, GET_LANGUAGE_TAG);
+ if (!TextUtils.isEmpty(languageTag)) {
+ return Locale.forLanguageTag(languageTag);
+ }
+ }
+ return LocaleUtils.constructLocaleFromString(subtype.getLocale());
+ }
+
@UsedForTesting
public static boolean isAsciiCapableWithAPI(final InputMethodSubtype subtype) {
return (Boolean)CompatUtils.invoke(subtype, false, METHOD_isAsciiCapable);
diff --git a/java/src/com/android/inputmethod/compat/LocaleListCompatUtils.java b/java/src/com/android/inputmethod/compat/LocaleListCompatUtils.java
new file mode 100644
index 000000000..01030aedd
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/LocaleListCompatUtils.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 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.compat;
+
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+public final class LocaleListCompatUtils {
+ private static final Class CLASS_LocaleList = CompatUtils.getClass("android.util.LocaleList");
+ private static final Method METHOD_getPrimary =
+ CompatUtils.getMethod(CLASS_LocaleList, "getPrimary");
+
+ private LocaleListCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static Locale getPrimary(final Object localeList) {
+ return (Locale) CompatUtils.invoke(localeList, null, METHOD_getPrimary);
+ }
+}
diff --git a/java/src/com/android/inputmethod/compat/UserManagerCompatUtils.java b/java/src/com/android/inputmethod/compat/UserManagerCompatUtils.java
new file mode 100644
index 000000000..5dee31629
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/UserManagerCompatUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.compat;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.UserManager;
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.reflect.Method;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * A temporary solution until {@code UserManagerCompat.isUserUnlocked()} in the support-v4 library
+ * becomes publicly available.
+ */
+public final class UserManagerCompatUtils {
+ private static final Method METHOD_isUserUnlocked;
+
+ static {
+ // We do not try to search the method in Android M and prior.
+ if (BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.M) {
+ METHOD_isUserUnlocked = null;
+ } else {
+ METHOD_isUserUnlocked = CompatUtils.getMethod(UserManager.class, "isUserUnlocked");
+ }
+ }
+
+ private UserManagerCompatUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static final int LOCK_STATE_UNKNOWN = 0;
+ public static final int LOCK_STATE_UNLOCKED = 1;
+ public static final int LOCK_STATE_LOCKED = 2;
+
+ @Retention(SOURCE)
+ @IntDef({LOCK_STATE_UNKNOWN, LOCK_STATE_UNLOCKED, LOCK_STATE_LOCKED})
+ public @interface LockState {}
+
+ /**
+ * Check if the calling user is running in an "unlocked" state. A user is unlocked only after
+ * they've entered their credentials (such as a lock pattern or PIN), and credential-encrypted
+ * private app data storage is available.
+ * @param context context from which {@link UserManager} should be obtained.
+ * @return One of {@link LockState}.
+ */
+ @LockState
+ public static int getUserLockState(final Context context) {
+ if (METHOD_isUserUnlocked == null) {
+ return LOCK_STATE_UNKNOWN;
+ }
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ if (userManager == null) {
+ return LOCK_STATE_UNKNOWN;
+ }
+ final Boolean result =
+ (Boolean) CompatUtils.invoke(userManager, null, METHOD_isUserUnlocked);
+ if (result == null) {
+ return LOCK_STATE_UNKNOWN;
+ }
+ return result ? LOCK_STATE_UNLOCKED : LOCK_STATE_LOCKED;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 47013fe9e..26ff051bb 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -32,6 +32,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.compat.EditorInfoCompatUtils;
import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
+import com.android.inputmethod.compat.UserManagerCompatUtils;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.UniqueKeysCache;
@@ -275,6 +276,16 @@ public final class KeyboardLayoutSet {
params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType);
params.mNoSettingsKey = InputAttributes.inPrivateImeOptions(
mPackageName, NO_SETTINGS_KEY, editorInfo);
+
+ // When the device is still unlocked, features like showing the IME setting app need to
+ // be locked down.
+ // TODO: Switch to {@code UserManagerCompat.isUserUnlocked()} in the support-v4 library
+ // when it becomes publicly available.
+ @UserManagerCompatUtils.LockState
+ final int lockState = UserManagerCompatUtils.getUserLockState(context);
+ if (lockState == UserManagerCompatUtils.LOCK_STATE_LOCKED) {
+ params.mNoSettingsKey = true;
+ }
}
public Builder setKeyboardGeometry(final int keyboardWidth, final int keyboardHeight) {
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
index 75b7962cb..b57e483d1 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
@@ -196,6 +196,8 @@ final class EmojiCategory {
addShownCategoryId(EmojiCategory.ID_FLAGS);
}
}
+ } else {
+ addShownCategoryId(EmojiCategory.ID_SYMBOLS);
}
addShownCategoryId(EmojiCategory.ID_EMOTICONS);
@@ -204,9 +206,14 @@ final class EmojiCategory {
recentsKbd.loadRecentKeys(mCategoryKeyboardMap.values());
mCurrentCategoryId = Settings.readLastShownEmojiCategoryId(mPrefs, defaultCategoryId);
- if (mCurrentCategoryId == EmojiCategory.ID_RECENTS &&
+ Log.i(TAG, "Last Emoji category id is " + mCurrentCategoryId);
+ if (!isShownCategoryId(mCurrentCategoryId)) {
+ Log.i(TAG, "Last emoji category " + mCurrentCategoryId +
+ " is invalid, starting in " + defaultCategoryId);
+ mCurrentCategoryId = defaultCategoryId;
+ } else if (mCurrentCategoryId == EmojiCategory.ID_RECENTS &&
recentsKbd.getSortedKeys().isEmpty()) {
- Log.i(TAG, "No recent emojis found, starting in category " + mCurrentCategoryId);
+ Log.i(TAG, "No recent emojis found, starting in category " + defaultCategoryId);
mCurrentCategoryId = defaultCategoryId;
}
}
@@ -219,6 +226,15 @@ final class EmojiCategory {
mShownCategories.add(properties);
}
+ private boolean isShownCategoryId(final int categoryId) {
+ for (final CategoryProperties prop : mShownCategories) {
+ if (prop.mCategoryId == categoryId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static String getCategoryName(final int categoryId, final int categoryPageId) {
return sCategoryName[categoryId] + "-" + categoryPageId;
}
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