aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java7
-rw-r--r--java/src/com/android/inputmethod/event/CombinerChain.java36
-rw-r--r--java/src/com/android/inputmethod/event/MyanmarReordering.java259
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java51
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardTheme.java125
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java34
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java41
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java (renamed from java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java)7
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java358
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java (renamed from java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java)6
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java (renamed from java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java)4
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java (renamed from java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java)7
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java146
-rw-r--r--java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java (renamed from java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java)524
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java19
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java14
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java7
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java2
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java46
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java1
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java23
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java9
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java16
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java33
-rw-r--r--java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java53
-rw-r--r--java/src/com/android/inputmethod/latin/settings/Settings.java2
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsFragment.java25
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java3
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java5
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java64
-rw-r--r--java/src/com/android/inputmethod/latin/utils/DistracterFilter.java94
-rw-r--r--java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java3
-rw-r--r--java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java4
34 files changed, 1407 insertions, 632 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 2e6649bf2..0499a3456 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -189,9 +189,14 @@ public final class KeyCodeDescriptionMapper {
break;
case KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED:
case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED:
- case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
resId = R.string.spoken_description_shift_shifted;
break;
+ case KeyboardId.ELEMENT_SYMBOLS:
+ resId = R.string.spoken_description_symbols_shift;
+ break;
+ case KeyboardId.ELEMENT_SYMBOLS_SHIFTED:
+ resId = R.string.spoken_description_symbols_shift_shifted;
+ break;
default:
resId = R.string.spoken_description_shift;
}
diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java
index 8b59dc52a..990f7deea 100644
--- a/java/src/com/android/inputmethod/event/CombinerChain.java
+++ b/java/src/com/android/inputmethod/event/CombinerChain.java
@@ -23,6 +23,7 @@ import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.CollectionUtils;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* This class implements the logic chain between receiving events and generating code points.
@@ -43,6 +44,13 @@ public class CombinerChain {
private SpannableStringBuilder mStateFeedback;
private final ArrayList<Combiner> mCombiners;
+ private static final HashMap<String, Class> IMPLEMENTED_COMBINERS
+ = new HashMap<String, Class>();
+ static {
+ IMPLEMENTED_COMBINERS.put("MyanmarReordering", MyanmarReordering.class);
+ }
+ private static final String COMBINER_SPEC_SEPARATOR = ";";
+
/**
* Create an combiner chain.
*
@@ -56,6 +64,9 @@ public class CombinerChain {
mCombiners = CollectionUtils.newArrayList();
// The dead key combiner is always active, and always first
mCombiners.add(new DeadKeyCombiner());
+ for (final Combiner combiner : combinerList) {
+ mCombiners.add(combiner);
+ }
mCombinedText = new StringBuilder();
mStateFeedback = new SpannableStringBuilder();
}
@@ -114,4 +125,29 @@ public class CombinerChain {
final SpannableStringBuilder s = new SpannableStringBuilder(mCombinedText);
return s.append(mStateFeedback);
}
+
+ public static Combiner[] createCombiners(final String spec) {
+ if (TextUtils.isEmpty(spec)) {
+ return new Combiner[0];
+ }
+ final String[] combinerDescriptors = spec.split(COMBINER_SPEC_SEPARATOR);
+ final Combiner[] combiners = new Combiner[combinerDescriptors.length];
+ int i = 0;
+ for (final String combinerDescriptor : combinerDescriptors) {
+ final Class combinerClass = IMPLEMENTED_COMBINERS.get(combinerDescriptor);
+ if (null == combinerClass) {
+ throw new RuntimeException("Unknown combiner descriptor: " + combinerDescriptor);
+ }
+ try {
+ combiners[i++] = (Combiner)combinerClass.newInstance();
+ } catch (InstantiationException e) {
+ throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
+ e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor,
+ e);
+ }
+ }
+ return combiners;
+ }
}
diff --git a/java/src/com/android/inputmethod/event/MyanmarReordering.java b/java/src/com/android/inputmethod/event/MyanmarReordering.java
new file mode 100644
index 000000000..da0228bd2
--- /dev/null
+++ b/java/src/com/android/inputmethod/event/MyanmarReordering.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 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.event;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * A combiner that reorders input for Myanmar.
+ */
+public class MyanmarReordering implements Combiner {
+ // U+1031 MYANMAR VOWEL SIGN E
+ private final static int VOWEL_E = 0x1031; // Code point for vowel E that we need to reorder
+ // U+200C ZERO WIDTH NON-JOINER
+ // U+200B ZERO WIDTH SPACE
+ private final static int ZERO_WIDTH_NON_JOINER = 0x200B; // should be 0x200C
+
+ private final ArrayList<Event> mCurrentEvents = CollectionUtils.newArrayList();
+
+ // List of consonants :
+ // U+1000 MYANMAR LETTER KA
+ // U+1001 MYANMAR LETTER KHA
+ // U+1002 MYANMAR LETTER GA
+ // U+1003 MYANMAR LETTER GHA
+ // U+1004 MYANMAR LETTER NGA
+ // U+1005 MYANMAR LETTER CA
+ // U+1006 MYANMAR LETTER CHA
+ // U+1007 MYANMAR LETTER JA
+ // U+1008 MYANMAR LETTER JHA
+ // U+1009 MYANMAR LETTER NYA
+ // U+100A MYANMAR LETTER NNYA
+ // U+100B MYANMAR LETTER TTA
+ // U+100C MYANMAR LETTER TTHA
+ // U+100D MYANMAR LETTER DDA
+ // U+100E MYANMAR LETTER DDHA
+ // U+100F MYANMAR LETTER NNA
+ // U+1010 MYANMAR LETTER TA
+ // U+1011 MYANMAR LETTER THA
+ // U+1012 MYANMAR LETTER DA
+ // U+1013 MYANMAR LETTER DHA
+ // U+1014 MYANMAR LETTER NA
+ // U+1015 MYANMAR LETTER PA
+ // U+1016 MYANMAR LETTER PHA
+ // U+1017 MYANMAR LETTER BA
+ // U+1018 MYANMAR LETTER BHA
+ // U+1019 MYANMAR LETTER MA
+ // U+101A MYANMAR LETTER YA
+ // U+101B MYANMAR LETTER RA
+ // U+101C MYANMAR LETTER LA
+ // U+101D MYANMAR LETTER WA
+ // U+101E MYANMAR LETTER SA
+ // U+101F MYANMAR LETTER HA
+ // U+1020 MYANMAR LETTER LLA
+ // U+103F MYANMAR LETTER GREAT SA
+ private static boolean isConsonant(final int codePoint) {
+ return (codePoint >= 0x1000 && codePoint <= 0x1020) || 0x103F == codePoint;
+ }
+
+ // List of medials :
+ // U+103B MYANMAR CONSONANT SIGN MEDIAL YA
+ // U+103C MYANMAR CONSONANT SIGN MEDIAL RA
+ // U+103D MYANMAR CONSONANT SIGN MEDIAL WA
+ // U+103E MYANMAR CONSONANT SIGN MEDIAL HA
+ // U+105E MYANMAR CONSONANT SIGN MON MEDIAL NA
+ // U+105F MYANMAR CONSONANT SIGN MON MEDIAL MA
+ // U+1060 MYANMAR CONSONANT SIGN MON MEDIAL LA
+ // U+1082 MYANMAR CONSONANT SIGN SHAN MEDIAL WA
+ private static int[] MEDIAL_LIST = { 0x103B, 0x103C, 0x103D, 0x103E,
+ 0x105E, 0x105F, 0x1060, 0x1082};
+ private static boolean isMedial(final int codePoint) {
+ return Arrays.binarySearch(MEDIAL_LIST, codePoint) >= 0;
+ }
+
+ private static boolean isConsonantOrMedial(final int codePoint) {
+ return isConsonant(codePoint) || isMedial(codePoint);
+ }
+
+ private Event getLastEvent() {
+ final int size = mCurrentEvents.size();
+ if (size <= 0) {
+ return null;
+ }
+ return mCurrentEvents.get(size - 1);
+ }
+
+ private CharSequence getCharSequence() {
+ final StringBuilder s = new StringBuilder();
+ for (final Event e : mCurrentEvents) {
+ s.appendCodePoint(e.mCodePoint);
+ }
+ return s;
+ }
+
+ /**
+ * Clears the currently combining stream of events and returns the resulting software text
+ * event corresponding to the stream. Optionally adds a new event to the cleared stream.
+ * @param newEvent the new event to add to the stream. null if none.
+ * @return the resulting software text event. Null if none.
+ */
+ private Event clearAndGetResultingEvent(final Event newEvent) {
+ final CharSequence combinedText;
+ if (mCurrentEvents.size() > 0) {
+ combinedText = getCharSequence();
+ mCurrentEvents.clear();
+ } else {
+ combinedText = null;
+ }
+ if (null != newEvent) {
+ mCurrentEvents.add(newEvent);
+ }
+ return null == combinedText ? null
+ : Event.createSoftwareTextEvent(combinedText, Event.NOT_A_KEY_CODE);
+ }
+
+ @Override
+ public Event processEvent(ArrayList<Event> previousEvents, Event newEvent) {
+ final int codePoint = newEvent.mCodePoint;
+ if (VOWEL_E == codePoint) {
+ final Event lastEvent = getLastEvent();
+ if (null == lastEvent) {
+ mCurrentEvents.add(newEvent);
+ return null;
+ } else if (isConsonantOrMedial(lastEvent.mCodePoint)) {
+ final Event resultingEvent = clearAndGetResultingEvent(null);
+ mCurrentEvents.add(Event.createSoftwareKeypressEvent(ZERO_WIDTH_NON_JOINER,
+ Event.NOT_A_KEY_CODE,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
+ false /* isKeyRepeat */));
+ mCurrentEvents.add(newEvent);
+ return resultingEvent;
+ } else { // VOWEL_E == lastCodePoint. But if that was anything else this is correct too.
+ return clearAndGetResultingEvent(newEvent);
+ }
+ } if (isConsonant(codePoint)) {
+ final Event lastEvent = getLastEvent();
+ if (null == lastEvent) {
+ mCurrentEvents.add(newEvent);
+ return null;
+ } else if (VOWEL_E == lastEvent.mCodePoint) {
+ final int eventSize = mCurrentEvents.size();
+ if (eventSize >= 2
+ && mCurrentEvents.get(eventSize - 2).mCodePoint == ZERO_WIDTH_NON_JOINER) {
+ // We have a ZWJN before a vowel E. We need to remove the ZWNJ and then
+ // reorder the vowel with respect to the consonant.
+ mCurrentEvents.remove(eventSize - 1);
+ mCurrentEvents.remove(eventSize - 2);
+ mCurrentEvents.add(newEvent);
+ mCurrentEvents.add(lastEvent);
+ return null;
+ }
+ // If there is already a consonant, then we are starting a new syllable.
+ for (int i = eventSize - 2; i >= 0; --i) {
+ if (isConsonant(mCurrentEvents.get(i).mCodePoint)) {
+ return clearAndGetResultingEvent(newEvent);
+ }
+ }
+ // If we come here, we didn't have a consonant so we reorder
+ mCurrentEvents.remove(eventSize - 1);
+ mCurrentEvents.add(newEvent);
+ mCurrentEvents.add(lastEvent);
+ return null;
+ } else { // lastCodePoint is a consonant/medial. But if it's something else it's fine
+ return clearAndGetResultingEvent(newEvent);
+ }
+ } else if (isMedial(codePoint)) {
+ final Event lastEvent = getLastEvent();
+ if (null == lastEvent) {
+ mCurrentEvents.add(newEvent);
+ return null;
+ } else if (VOWEL_E == lastEvent.mCodePoint) {
+ final int eventSize = mCurrentEvents.size();
+ // If there is already a consonant, then we are in the middle of a syllable, and we
+ // need to reorder.
+ boolean hasConsonant = false;
+ for (int i = eventSize - 2; i >= 0; --i) {
+ if (isConsonant(mCurrentEvents.get(i).mCodePoint)) {
+ hasConsonant = true;
+ break;
+ }
+ }
+ if (hasConsonant) {
+ mCurrentEvents.remove(eventSize - 1);
+ mCurrentEvents.add(newEvent);
+ mCurrentEvents.add(lastEvent);
+ return null;
+ }
+ // Otherwise, we just commit everything.
+ return clearAndGetResultingEvent(null);
+ } else { // lastCodePoint is a consonant/medial. But if it's something else it's fine
+ return clearAndGetResultingEvent(newEvent);
+ }
+ } else if (Constants.CODE_DELETE == newEvent.mKeyCode) {
+ final Event lastEvent = getLastEvent();
+ final int eventSize = mCurrentEvents.size();
+ if (null != lastEvent) {
+ if (VOWEL_E == lastEvent.mCodePoint) {
+ // We have a VOWEL_E at the end. There are four cases.
+ // - The vowel is the only code point in the buffer. Remove it.
+ // - The vowel is preceded by a ZWNJ. Remove both vowel E and ZWNJ.
+ // - The vowel is preceded by a consonant/medial, remove the consonant/medial.
+ // - In all other cases, it's strange, so just remove the last code point.
+ if (eventSize <= 1) {
+ mCurrentEvents.clear();
+ } else { // eventSize >= 2
+ final int previousCodePoint = mCurrentEvents.get(eventSize - 2).mCodePoint;
+ if (previousCodePoint == ZERO_WIDTH_NON_JOINER) {
+ mCurrentEvents.remove(eventSize - 1);
+ mCurrentEvents.remove(eventSize - 2);
+ } else if (isConsonantOrMedial(previousCodePoint)) {
+ mCurrentEvents.remove(eventSize - 2);
+ } else {
+ mCurrentEvents.remove(eventSize - 1);
+ }
+ }
+ return null;
+ } else if (eventSize > 0) {
+ mCurrentEvents.remove(eventSize - 1);
+ return null;
+ }
+ }
+ }
+ // This character is not part of the combining scheme, so we should reset everything.
+ if (mCurrentEvents.size() > 0) {
+ // If we have events in flight, then add the new event and return the resulting event.
+ mCurrentEvents.add(newEvent);
+ return clearAndGetResultingEvent(null);
+ } else {
+ // If we don't have any events in flight, then just pass this one through.
+ return newEvent;
+ }
+ }
+
+ @Override
+ public CharSequence getCombiningStateFeedback() {
+ return getCharSequence();
+ }
+
+ @Override
+ public void reset() {
+ mCurrentEvents.clear();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 816a94300..4c2250740 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -218,7 +218,7 @@ public class Key implements Comparable<Key> {
*
* @param keySpec the key specification.
* @param keyAttr the Key XML attributes array.
- * @param keyStyle the {@link KeyStyle} of this key.
+ * @param style the {@link KeyStyle} of this key.
* @param params the keyboard building parameters.
* @param row the row that this key belongs to. row's x-coordinate will be the right edge of
* this key.
@@ -857,17 +857,6 @@ public class Key implements Comparable<Key> {
android.R.attr.state_empty
};
- // functional normal state (with properties)
- private static final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
- android.R.attr.state_single
- };
-
- // functional pressed state (with properties)
- private static final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
- android.R.attr.state_single,
- android.R.attr.state_pressed
- };
-
// action normal state (with properties)
private static final int[] KEY_STATE_ACTIVE_NORMAL = {
android.R.attr.state_active
@@ -880,25 +869,43 @@ public class Key implements Comparable<Key> {
};
/**
- * Returns the drawable state for the key, based on the current state and type of the key.
- * @return the drawable state of the key.
+ * Returns the background drawable for the key, based on the current state and type of the key.
+ * @return the background drawable of the key.
* @see android.graphics.drawable.StateListDrawable#setState(int[])
*/
- public final int[] getCurrentDrawableState() {
+ public final Drawable selectBackgroundDrawable(final Drawable keyBackground,
+ final Drawable functionalKeyBackground, final Drawable spacebarBackground) {
+ final Drawable background;
+ if (mBackgroundType == BACKGROUND_TYPE_FUNCTIONAL) {
+ background = functionalKeyBackground;
+ } else if (getCode() == Constants.CODE_SPACE) {
+ background = spacebarBackground;
+ } else {
+ background = keyBackground;
+ }
+ final int[] stateSet;
switch (mBackgroundType) {
- case BACKGROUND_TYPE_FUNCTIONAL:
- return mPressed ? KEY_STATE_FUNCTIONAL_PRESSED : KEY_STATE_FUNCTIONAL_NORMAL;
case BACKGROUND_TYPE_ACTION:
- return mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
+ stateSet = mPressed ? KEY_STATE_ACTIVE_PRESSED : KEY_STATE_ACTIVE_NORMAL;
+ break;
case BACKGROUND_TYPE_STICKY_OFF:
- return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
+ stateSet = mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_OFF : KEY_STATE_NORMAL_HIGHLIGHT_OFF;
+ break;
case BACKGROUND_TYPE_STICKY_ON:
- return mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
+ stateSet = mPressed ? KEY_STATE_PRESSED_HIGHLIGHT_ON : KEY_STATE_NORMAL_HIGHLIGHT_ON;
+ break;
case BACKGROUND_TYPE_EMPTY:
- return mPressed ? KEY_STATE_PRESSED : KEY_STATE_EMPTY;
+ stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_EMPTY;
+ break;
+ case BACKGROUND_TYPE_FUNCTIONAL:
+ stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
+ break;
default: /* BACKGROUND_TYPE_NORMAL */
- return mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
+ stateSet = mPressed ? KEY_STATE_PRESSED : KEY_STATE_NORMAL;
+ break;
}
+ background.setState(stateSet);
+ return background;
}
public static class Spacer extends Key {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 1cd6ef249..fc9faa6e3 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -28,6 +28,7 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
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;
@@ -64,7 +65,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
* what user actually typed. */
private boolean mIsAutoCorrectionActive;
- private KeyboardTheme mKeyboardTheme = KeyboardTheme.getDefaultKeyboardTheme();
+ private KeyboardTheme mKeyboardTheme;
private Context mThemeContext;
private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
@@ -101,7 +102,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
private boolean updateKeyboardThemeAndContextThemeWrapper(final Context context,
final KeyboardTheme keyboardTheme) {
- if (mThemeContext == null || mKeyboardTheme.mThemeId != keyboardTheme.mThemeId) {
+ if (mThemeContext == null || !keyboardTheme.equals(mKeyboardTheme)) {
mKeyboardTheme = keyboardTheme;
mThemeContext = new ContextThemeWrapper(context, keyboardTheme.mStyleId);
KeyboardLayoutSet.clearKeyboardCache();
@@ -253,10 +254,11 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void setEmojiKeyboard() {
+ final Keyboard keyboard = mKeyboardView.getKeyboard();
mMainKeyboardFrame.setVisibility(View.GONE);
mEmojiPalettesView.startEmojiPalettes(
mKeyboardTextsSet.getText(KeyboardTextsSet.SWITCH_TO_ALPHA_KEY_LABEL),
- mKeyboardView.getKeyVisualAttribute());
+ mKeyboardView.getKeyVisualAttribute(), keyboard.mIconsSet);
mEmojiPalettesView.setVisibility(View.VISIBLE);
}
@@ -342,7 +344,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
mKeyboardView.closing();
}
- updateKeyboardThemeAndContextThemeWrapper(mLatinIME, mKeyboardTheme);
+ updateKeyboardThemeAndContextThemeWrapper(
+ mLatinIME, KeyboardTheme.getKeyboardTheme(mPrefs));
mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
R.layout.input_view, null);
mMainKeyboardFrame = mCurrentInputView.findViewById(R.id.main_keyboard_frame);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
index 4db72ad4d..e0b74fa14 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java
@@ -17,34 +17,85 @@
package com.android.inputmethod.keyboard;
import android.content.SharedPreferences;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
import android.util.Log;
import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.settings.Settings;
+
+import java.util.Arrays;
+import java.util.Comparator;
public final class KeyboardTheme {
private static final String TAG = KeyboardTheme.class.getSimpleName();
- public static final int THEME_ID_ICS = 0;
- public static final int THEME_ID_KLP = 2;
- private static final int DEFAULT_THEME_ID = THEME_ID_KLP;
+ static final String KITKAT_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916";
+ static final String KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509";
+
+ static final int THEME_ID_ICS = 0;
+ static final int THEME_ID_KLP = 2;
+ static final int THEME_ID_LXX = 3;
+ static final int DEFAULT_THEME_ID = THEME_ID_KLP;
private static final KeyboardTheme[] KEYBOARD_THEMES = {
- new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS),
- new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP),
+ new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS,
+ VERSION_CODES.ICE_CREAM_SANDWICH),
+ new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP,
+ VERSION_CODES.KITKAT),
+ new KeyboardTheme(THEME_ID_LXX, R.style.KeyboardTheme_LXX,
+ // TODO: Update this constant once the *next* version becomes available.
+ VERSION_CODES.CUR_DEVELOPMENT),
};
+ static {
+ // Sort {@link #KEYBOARD_THEME} by descending order of {@link #mMinApiVersion}.
+ Arrays.sort(KEYBOARD_THEMES, new Comparator<KeyboardTheme>() {
+ @Override
+ public int compare(final KeyboardTheme lhs, final KeyboardTheme rhs) {
+ if (lhs.mMinApiVersion > rhs.mMinApiVersion) return -1;
+ if (lhs.mMinApiVersion < rhs.mMinApiVersion) return 1;
+ return 0;
+ }
+ });
+ }
public final int mThemeId;
public final int mStyleId;
+ final int mMinApiVersion;
// Note: The themeId should be aligned with "themeId" attribute of Keyboard style
- // in values/style.xml.
- public KeyboardTheme(final int themeId, final int styleId) {
+ // in values/themes-<style>.xml.
+ private KeyboardTheme(final int themeId, final int styleId, final int minApiVersion) {
mThemeId = themeId;
mStyleId = styleId;
+ mMinApiVersion = minApiVersion;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (o == this) return true;
+ return (o instanceof KeyboardTheme) && ((KeyboardTheme)o).mThemeId == mThemeId;
}
- private static KeyboardTheme searchKeyboardTheme(final int themeId) {
+ @Override
+ public int hashCode() {
+ return mThemeId;
+ }
+
+ // TODO: This method should be removed when {@link LatinImeLogger} is removed.
+ public int getCompatibleThemeIdForLogging() {
+ switch (mThemeId) {
+ case THEME_ID_ICS:
+ return 5;
+ case THEME_ID_KLP:
+ return 9;
+ case THEME_ID_LXX:
+ return 10;
+ default: // Invalid theme
+ return -1;
+ }
+ }
+
+ private static KeyboardTheme searchKeyboardThemeById(final int themeId) {
// TODO: This search algorithm isn't optimal if there are many themes.
for (final KeyboardTheme theme : KEYBOARD_THEMES) {
if (theme.mThemeId == themeId) {
@@ -54,18 +105,57 @@ public final class KeyboardTheme {
return null;
}
- public static KeyboardTheme getDefaultKeyboardTheme() {
- return searchKeyboardTheme(DEFAULT_THEME_ID);
+ private static int getSdkVersion() {
+ final int sdkVersion = Build.VERSION.SDK_INT;
+ // TODO: Consider to remove this check once the *next* version becomes available.
+ if (sdkVersion == VERSION_CODES.KITKAT && Build.VERSION.CODENAME.startsWith("L")) {
+ return VERSION_CODES.CUR_DEVELOPMENT;
+ }
+ return sdkVersion;
+ }
+
+ static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
+ final int sdkVersion) {
+ final String obsoleteIdString = prefs.getString(KITKAT_KEYBOARD_THEME_KEY, null);
+ if (obsoleteIdString != null) {
+ // Remove old preference.
+ prefs.edit().remove(KITKAT_KEYBOARD_THEME_KEY).apply();
+ if (sdkVersion <= VERSION_CODES.KITKAT) {
+ try {
+ final int themeId = Integer.parseInt(obsoleteIdString);
+ final KeyboardTheme theme = searchKeyboardThemeById(themeId);
+ if (theme != null) {
+ return theme;
+ }
+ Log.w(TAG, "Unknown keyboard theme in preference: " + obsoleteIdString);
+ } catch (final NumberFormatException e) {
+ Log.w(TAG, "Illegal keyboard theme in preference: " + obsoleteIdString);
+ }
+ }
+ }
+ // TODO: This search algorithm isn't optimal if there are many themes.
+ for (final KeyboardTheme theme : KEYBOARD_THEMES) {
+ if (sdkVersion >= theme.mMinApiVersion) {
+ return theme;
+ }
+ }
+ return searchKeyboardThemeById(DEFAULT_THEME_ID);
+ }
+
+ public static void saveKeyboardThemeId(final String themeIdString,
+ final SharedPreferences prefs) {
+ prefs.edit().putString(KEYBOARD_THEME_KEY, themeIdString).apply();
}
public static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs) {
- final String themeIdString = prefs.getString(Settings.PREF_KEYBOARD_LAYOUT, null);
+ final int sdkVersion = getSdkVersion();
+ final String themeIdString = prefs.getString(KEYBOARD_THEME_KEY, null);
if (themeIdString == null) {
- return getDefaultKeyboardTheme();
+ return getDefaultKeyboardTheme(prefs, sdkVersion);
}
try {
final int themeId = Integer.parseInt(themeIdString);
- final KeyboardTheme theme = searchKeyboardTheme(themeId);
+ final KeyboardTheme theme = searchKeyboardThemeById(themeId);
if (theme != null) {
return theme;
}
@@ -73,9 +163,8 @@ public final class KeyboardTheme {
} catch (final NumberFormatException e) {
Log.w(TAG, "Illegal keyboard theme in preference: " + themeIdString);
}
- // Reset preference to default value.
- final String defaultThemeIdString = Integer.toString(DEFAULT_THEME_ID);
- prefs.edit().putString(Settings.PREF_KEYBOARD_LAYOUT, defaultThemeIdString).apply();
- return getDefaultKeyboardTheme();
+ // Remove preference.
+ prefs.edit().remove(KEYBOARD_THEME_KEY).apply();
+ return getDefaultKeyboardTheme(prefs, sdkVersion);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 8ca00b005..a6eac4cd7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -47,6 +47,8 @@ import java.util.HashSet;
* A view that renders a virtual {@link Keyboard}.
*
* @attr ref R.styleable#KeyboardView_keyBackground
+ * @attr ref R.styleable#KeyboardView_functionalKeyBackground
+ * @attr ref R.styleable#KeyboardView_spacebarBackground
* @attr ref R.styleable#KeyboardView_keyLabelHorizontalPadding
* @attr ref R.styleable#KeyboardView_keyHintLetterPadding
* @attr ref R.styleable#KeyboardView_keyPopupHintLetterPadding
@@ -81,7 +83,10 @@ public class KeyboardView extends View {
private final float mKeyTextShadowRadius;
private final float mVerticalCorrection;
private final Drawable mKeyBackground;
+ private final Drawable mFunctionalKeyBackground;
+ private final Drawable mSpacebarBackground;
private final Rect mKeyBackgroundPadding = new Rect();
+ private static final float KET_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
// HORIZONTAL ELLIPSIS "...", character for popup hint.
private static final String POPUP_HINT_CHAR = "\u2026";
@@ -124,6 +129,14 @@ public class KeyboardView extends View {
R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
mKeyBackground = keyboardViewAttr.getDrawable(R.styleable.KeyboardView_keyBackground);
mKeyBackground.getPadding(mKeyBackgroundPadding);
+ final Drawable functionalKeyBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_functionalKeyBackground);
+ mFunctionalKeyBackground = (functionalKeyBackground != null) ? functionalKeyBackground
+ : mKeyBackground;
+ final Drawable spacebarBackground = keyboardViewAttr.getDrawable(
+ R.styleable.KeyboardView_spacebarBackground);
+ mSpacebarBackground = (spacebarBackground != null) ? spacebarBackground
+ : mKeyBackground;
mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset(
R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
mKeyHintLetterPadding = keyboardViewAttr.getDimension(
@@ -133,7 +146,7 @@ public class KeyboardView extends View {
mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f);
mKeyTextShadowRadius = keyboardViewAttr.getFloat(
- R.styleable.KeyboardView_keyTextShadowRadius, 0.0f);
+ R.styleable.KeyboardView_keyTextShadowRadius, KET_TEXT_SHADOW_RADIUS_DISABLED);
mVerticalCorrection = keyboardViewAttr.getDimension(
R.styleable.KeyboardView_verticalCorrection, 0.0f);
keyboardViewAttr.recycle();
@@ -323,7 +336,9 @@ public class KeyboardView extends View {
params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE;
if (!key.isSpacer()) {
- onDrawKeyBackground(key, canvas, mKeyBackground);
+ final Drawable background = key.selectBackgroundDrawable(
+ mKeyBackground, mFunctionalKeyBackground, mSpacebarBackground);
+ onDrawKeyBackground(key, canvas, background);
}
onDrawKeyTopVisuals(key, canvas, paint, params);
@@ -338,8 +353,6 @@ public class KeyboardView extends View {
final int bgHeight = key.getHeight() + padding.top + padding.bottom;
final int bgX = -padding.left;
final int bgY = -padding.top;
- final int[] drawableState = key.getCurrentDrawableState();
- background.setState(drawableState);
final Rect bounds = background.getBounds();
if (bgWidth != bounds.right || bgHeight != bounds.bottom) {
background.setBounds(0, 0, bgWidth, bgHeight);
@@ -414,18 +427,23 @@ public class KeyboardView extends View {
}
}
- paint.setColor(key.selectTextColor(params));
if (key.isEnabled()) {
- // Set a drop shadow for the text
- paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
+ paint.setColor(key.selectTextColor(params));
+ // Set a drop shadow for the text if the shadow radius is positive value.
+ if (mKeyTextShadowRadius > 0.0f) {
+ paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
+ } else {
+ paint.clearShadowLayer();
+ }
} else {
// Make label invisible
paint.setColor(Color.TRANSPARENT);
+ paint.clearShadowLayer();
}
blendAlpha(paint, params.mAnimAlpha);
canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
// Turn off drop shadow and reset x-scale.
- paint.setShadowLayer(0.0f, 0.0f, 0.0f, Color.TRANSPARENT);
+ paint.clearShadowLayer();
paint.setTextScaleX(1.0f);
if (icon != null) {
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 8f79a9128..4a0976845 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -74,8 +74,8 @@ import java.util.WeakHashMap;
* @attr ref R.styleable#MainKeyboardView_autoCorrectionSpacebarLedIcon
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextRatio
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextColor
+ * @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowRadius
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarTextShadowColor
- * @attr ref R.styleable#MainKeyboardView_spacebarBackground
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFinalAlpha
* @attr ref R.styleable#MainKeyboardView_languageOnSpacebarFadeoutAnimator
* @attr ref R.styleable#MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator
@@ -119,7 +119,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
/* Space key and its icon and background. */
private Key mSpaceKey;
private Drawable mSpacebarIcon;
- private final Drawable mSpacebarBackground;
// Stuff to draw language name on spacebar.
private final int mLanguageOnSpacebarFinalAlpha;
private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
@@ -129,7 +128,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private final float mLanguageOnSpacebarTextRatio;
private float mLanguageOnSpacebarTextSize;
private final int mLanguageOnSpacebarTextColor;
+ private final float mLanguageOnSpacebarTextShadowRadius;
private final int mLanguageOnSpacebarTextShadowColor;
+ private static final float LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED = -1.0f;
// The minimum x-scale to fit the language name on spacebar.
private static final float MINIMUM_XSCALE_OF_LANGUAGE_NAME = 0.8f;
// Stuff to draw auto correction LED on spacebar.
@@ -151,7 +152,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
private final SlidingKeyInputDrawingPreview mSlidingKeyInputDrawingPreview;
// Key preview
- private static final boolean FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED = false;
private final KeyPreviewDrawParams mKeyPreviewDrawParams;
private final KeyPreviewChoreographer mKeyPreviewChoreographer;
@@ -221,8 +221,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
R.styleable.MainKeyboardView_backgroundDimAlpha, 0);
mBackgroundDimAlphaPaint.setColor(Color.BLACK);
mBackgroundDimAlphaPaint.setAlpha(backgroundDimAlpha);
- mSpacebarBackground = mainKeyboardViewAttr.getDrawable(
- R.styleable.MainKeyboardView_spacebarBackground);
mAutoCorrectionSpacebarLedEnabled = mainKeyboardViewAttr.getBoolean(
R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false);
mAutoCorrectionSpacebarLedIcon = mainKeyboardViewAttr.getDrawable(
@@ -231,6 +229,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
R.styleable.MainKeyboardView_languageOnSpacebarTextRatio, 1, 1, 1.0f);
mLanguageOnSpacebarTextColor = mainKeyboardViewAttr.getColor(
R.styleable.MainKeyboardView_languageOnSpacebarTextColor, 0);
+ mLanguageOnSpacebarTextShadowRadius = mainKeyboardViewAttr.getFloat(
+ R.styleable.MainKeyboardView_languageOnSpacebarTextShadowRadius,
+ LANGUAGE_ON_SPACEBAR_TEXT_SHADOW_RADIUS_DISABLED);
mLanguageOnSpacebarTextShadowColor = mainKeyboardViewAttr.getColor(
R.styleable.MainKeyboardView_languageOnSpacebarTextShadowColor, 0);
mLanguageOnSpacebarFinalAlpha = mainKeyboardViewAttr.getInt(
@@ -551,6 +552,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
// Note that this method is called from a non-UI thread.
+ @SuppressWarnings("static-method")
public void setMainDictionaryAvailability(final boolean mainDictionaryAvailable) {
PointerTracker.setMainDictionaryAvailability(mainDictionaryAvailable);
}
@@ -858,30 +860,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
}
- // Draw key background.
- @Override
- protected void onDrawKeyBackground(final Key key, final Canvas canvas,
- final Drawable background) {
- if (key.getCode() == Constants.CODE_SPACE) {
- super.onDrawKeyBackground(key, canvas, mSpacebarBackground);
- return;
- }
- super.onDrawKeyBackground(key, canvas, background);
- }
-
@Override
protected void onDrawKeyTopVisuals(final Key key, final Canvas canvas, final Paint paint,
final KeyDrawParams params) {
if (key.altCodeWhileTyping() && key.isEnabled()) {
params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha;
}
- // Don't draw key top letter when key preview is showing.
- if (FADE_OUT_KEY_TOP_LETTER_WHEN_KEY_IS_PRESSED
- && mKeyPreviewChoreographer.isShowingKeyPreview(key)) {
- // TODO: Fade out animation for the key top letter, and fade in animation for the key
- // background color when the user presses the key.
- return;
- }
final int code = key.getCode();
if (code == Constants.CODE_SPACE) {
drawSpacebar(key, canvas, paint);
@@ -948,12 +932,17 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
final float descent = paint.descent();
final float textHeight = -paint.ascent() + descent;
final float baseline = height / 2 + textHeight / 2;
- paint.setColor(mLanguageOnSpacebarTextShadowColor);
- paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
- canvas.drawText(language, width / 2, baseline - descent - 1, paint);
+ if (mLanguageOnSpacebarTextShadowRadius > 0.0f) {
+ paint.setShadowLayer(mLanguageOnSpacebarTextShadowRadius, 0, 0,
+ mLanguageOnSpacebarTextShadowColor);
+ } else {
+ paint.clearShadowLayer();
+ }
paint.setColor(mLanguageOnSpacebarTextColor);
paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
canvas.drawText(language, width / 2, baseline - descent, paint);
+ paint.clearShadowLayer();
+ paint.setTextScaleX(1.0f);
}
// Draw the spacebar icon at the bottom
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java
index 67a222732..c7a9025c0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard.internal;
+package com.android.inputmethod.keyboard.emoji;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
-import com.android.inputmethod.keyboard.EmojiPalettesView;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.settings.Settings;
@@ -36,7 +35,7 @@ import java.util.List;
/**
* This is a Keyboard class where you can add keys dynamically shown in a grid layout
*/
-public class DynamicGridKeyboard extends Keyboard {
+final class DynamicGridKeyboard extends Keyboard {
private static final String TAG = DynamicGridKeyboard.class.getSimpleName();
private static final int TEMPLATE_KEY_CODE_0 = 0x30;
private static final int TEMPLATE_KEY_CODE_1 = 0x31;
@@ -62,7 +61,7 @@ public class DynamicGridKeyboard extends Keyboard {
mVerticalStep = key0.getHeight() + mVerticalGap;
mColumnsNum = mBaseWidth / mHorizontalStep;
mMaxKeyCount = maxKeyCount;
- mIsRecents = categoryId == EmojiPalettesView.CATEGORY_ID_RECENTS;
+ mIsRecents = categoryId == EmojiCategory.ID_RECENTS;
mPrefs = prefs;
}
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
new file mode 100644
index 000000000..dd0e3e838
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2014 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.emoji;
+
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+final class EmojiCategory {
+ private final String TAG = EmojiCategory.class.getSimpleName();
+
+ private static final int ID_UNSPECIFIED = -1;
+ public static final int ID_RECENTS = 0;
+ private static final int ID_PEOPLE = 1;
+ private static final int ID_OBJECTS = 2;
+ private static final int ID_NATURE = 3;
+ private static final int ID_PLACES = 4;
+ private static final int ID_SYMBOLS = 5;
+ private static final int ID_EMOTICONS = 6;
+
+ public final class CategoryProperties {
+ public final int mCategoryId;
+ public final int mPageCount;
+ public CategoryProperties(final int categoryId, final int pageCount) {
+ mCategoryId = categoryId;
+ mPageCount = pageCount;
+ }
+ }
+
+ private static final String[] sCategoryName = {
+ "recents",
+ "people",
+ "objects",
+ "nature",
+ "places",
+ "symbols",
+ "emoticons" };
+
+ private static final int[] sCategoryIcon = {
+ R.drawable.ic_emoji_recent_light,
+ R.drawable.ic_emoji_people_light,
+ R.drawable.ic_emoji_objects_light,
+ R.drawable.ic_emoji_nature_light,
+ R.drawable.ic_emoji_places_light,
+ R.drawable.ic_emoji_symbols_light,
+ 0 };
+
+ private static final String[] sCategoryLabel =
+ { null, null, null, null, null, null, ":-)" };
+
+ private static final int[] sAccessibilityDescriptionResourceIdsForCategories = {
+ R.string.spoken_descrption_emoji_category_recents,
+ R.string.spoken_descrption_emoji_category_people,
+ R.string.spoken_descrption_emoji_category_objects,
+ R.string.spoken_descrption_emoji_category_nature,
+ R.string.spoken_descrption_emoji_category_places,
+ R.string.spoken_descrption_emoji_category_symbols,
+ R.string.spoken_descrption_emoji_category_emoticons };
+
+ private static final int[] sCategoryElementId = {
+ KeyboardId.ELEMENT_EMOJI_RECENTS,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY1,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY2,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY3,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY4,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY5,
+ KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
+
+ private final SharedPreferences mPrefs;
+ private final Resources mRes;
+ private final int mMaxPageKeyCount;
+ private final KeyboardLayoutSet mLayoutSet;
+ private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
+ private final ArrayList<CategoryProperties> mShownCategories =
+ CollectionUtils.newArrayList();
+ private final ConcurrentHashMap<Long, DynamicGridKeyboard>
+ mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
+
+ private int mCurrentCategoryId = EmojiCategory.ID_UNSPECIFIED;
+ private int mCurrentCategoryPageId = 0;
+
+ public EmojiCategory(final SharedPreferences prefs, final Resources res,
+ final KeyboardLayoutSet layoutSet) {
+ mPrefs = prefs;
+ mRes = res;
+ mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count);
+ mLayoutSet = layoutSet;
+ for (int i = 0; i < sCategoryName.length; ++i) {
+ mCategoryNameToIdMap.put(sCategoryName[i], i);
+ }
+ addShownCategoryId(EmojiCategory.ID_RECENTS);
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
+ || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KeyLimePie")
+ || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KitKat")) {
+ addShownCategoryId(EmojiCategory.ID_PEOPLE);
+ addShownCategoryId(EmojiCategory.ID_OBJECTS);
+ addShownCategoryId(EmojiCategory.ID_NATURE);
+ addShownCategoryId(EmojiCategory.ID_PLACES);
+ mCurrentCategoryId =
+ Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_PEOPLE);
+ } else {
+ mCurrentCategoryId =
+ Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_SYMBOLS);
+ }
+ addShownCategoryId(EmojiCategory.ID_SYMBOLS);
+ addShownCategoryId(EmojiCategory.ID_EMOTICONS);
+ getKeyboard(EmojiCategory.ID_RECENTS, 0 /* cagetoryPageId */)
+ .loadRecentKeys(mCategoryKeyboardMap.values());
+ }
+
+ private void addShownCategoryId(final int categoryId) {
+ // Load a keyboard of categoryId
+ getKeyboard(categoryId, 0 /* cagetoryPageId */);
+ final CategoryProperties properties =
+ new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
+ mShownCategories.add(properties);
+ }
+
+ public String getCategoryName(final int categoryId, final int categoryPageId) {
+ return sCategoryName[categoryId] + "-" + categoryPageId;
+ }
+
+ public int getCategoryId(final String name) {
+ final String[] strings = name.split("-");
+ return mCategoryNameToIdMap.get(strings[0]);
+ }
+
+ public int getCategoryIcon(final int categoryId) {
+ return sCategoryIcon[categoryId];
+ }
+
+ public String getCategoryLabel(final int categoryId) {
+ return sCategoryLabel[categoryId];
+ }
+
+ public String getAccessibilityDescription(final int categoryId) {
+ return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]);
+ }
+
+ public ArrayList<CategoryProperties> getShownCategories() {
+ return mShownCategories;
+ }
+
+ public int getCurrentCategoryId() {
+ return mCurrentCategoryId;
+ }
+
+ public int getCurrentCategoryPageSize() {
+ return getCategoryPageSize(mCurrentCategoryId);
+ }
+
+ public int getCategoryPageSize(final int categoryId) {
+ for (final CategoryProperties prop : mShownCategories) {
+ if (prop.mCategoryId == categoryId) {
+ return prop.mPageCount;
+ }
+ }
+ Log.w(TAG, "Invalid category id: " + categoryId);
+ // Should not reach here.
+ return 0;
+ }
+
+ public void setCurrentCategoryId(final int categoryId) {
+ mCurrentCategoryId = categoryId;
+ Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId);
+ }
+
+ public void setCurrentCategoryPageId(final int id) {
+ mCurrentCategoryPageId = id;
+ }
+
+ public int getCurrentCategoryPageId() {
+ return mCurrentCategoryPageId;
+ }
+
+ public void saveLastTypedCategoryPage() {
+ Settings.writeLastTypedEmojiCategoryPageId(
+ mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
+ }
+
+ public boolean isInRecentTab() {
+ return mCurrentCategoryId == EmojiCategory.ID_RECENTS;
+ }
+
+ public int getTabIdFromCategoryId(final int categoryId) {
+ for (int i = 0; i < mShownCategories.size(); ++i) {
+ if (mShownCategories.get(i).mCategoryId == categoryId) {
+ return i;
+ }
+ }
+ Log.w(TAG, "categoryId not found: " + categoryId);
+ return 0;
+ }
+
+ // Returns the view pager's page position for the categoryId
+ public int getPageIdFromCategoryId(final int categoryId) {
+ final int lastSavedCategoryPageId =
+ Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId);
+ int sum = 0;
+ for (int i = 0; i < mShownCategories.size(); ++i) {
+ final CategoryProperties props = mShownCategories.get(i);
+ if (props.mCategoryId == categoryId) {
+ return sum + lastSavedCategoryPageId;
+ }
+ sum += props.mPageCount;
+ }
+ Log.w(TAG, "categoryId not found: " + categoryId);
+ return 0;
+ }
+
+ public int getRecentTabId() {
+ return getTabIdFromCategoryId(EmojiCategory.ID_RECENTS);
+ }
+
+ private int getCategoryPageCount(final int categoryId) {
+ final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+ return (keyboard.getSortedKeys().size() - 1) / mMaxPageKeyCount + 1;
+ }
+
+ // Returns a pair of the category id and the category page id from the view pager's page
+ // position. The category page id is numbered in each category. And the view page position
+ // is the position of the current shown page in the view pager which contains all pages of
+ // all categories.
+ public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(final int position) {
+ int sum = 0;
+ for (final CategoryProperties properties : mShownCategories) {
+ final int temp = sum;
+ sum += properties.mPageCount;
+ if (sum > position) {
+ return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
+ }
+ }
+ return null;
+ }
+
+ // Returns a keyboard from the view pager's page position.
+ public DynamicGridKeyboard getKeyboardFromPagePosition(final int position) {
+ final Pair<Integer, Integer> categoryAndId =
+ getCategoryIdAndPageIdFromPagePosition(position);
+ if (categoryAndId != null) {
+ return getKeyboard(categoryAndId.first, categoryAndId.second);
+ }
+ return null;
+ }
+
+ private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) {
+ return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
+ }
+
+ public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
+ synchronized (mCategoryKeyboardMap) {
+ final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id);
+ if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) {
+ return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
+ }
+
+ if (categoryId == EmojiCategory.ID_RECENTS) {
+ final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs,
+ mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+ mMaxPageKeyCount, categoryId);
+ mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd);
+ return kbd;
+ }
+
+ final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
+ final Key[][] sortedKeys = sortKeysIntoPages(
+ keyboard.getSortedKeys(), mMaxPageKeyCount);
+ for (int pageId = 0; pageId < sortedKeys.length; ++pageId) {
+ final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
+ mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
+ mMaxPageKeyCount, categoryId);
+ for (final Key emojiKey : sortedKeys[pageId]) {
+ if (emojiKey == null) {
+ break;
+ }
+ tempKeyboard.addKeyLast(emojiKey);
+ }
+ mCategoryKeyboardMap.put(
+ getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard);
+ }
+ return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
+ }
+ }
+
+ public int getTotalPageCountOfAllCategories() {
+ int sum = 0;
+ for (CategoryProperties properties : mShownCategories) {
+ sum += properties.mPageCount;
+ }
+ return sum;
+ }
+
+ private static Comparator<Key> EMOJI_KEY_COMPARATOR = new Comparator<Key>() {
+ @Override
+ public int compare(final Key lhs, final Key rhs) {
+ final Rect lHitBox = lhs.getHitBox();
+ final Rect rHitBox = rhs.getHitBox();
+ if (lHitBox.top < rHitBox.top) {
+ return -1;
+ } else if (lHitBox.top > rHitBox.top) {
+ return 1;
+ }
+ if (lHitBox.left < rHitBox.left) {
+ return -1;
+ } else if (lHitBox.left > rHitBox.left) {
+ return 1;
+ }
+ if (lhs.getCode() == rhs.getCode()) {
+ return 0;
+ }
+ return lhs.getCode() < rhs.getCode() ? -1 : 1;
+ }
+ };
+
+ private static Key[][] sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount) {
+ final ArrayList<Key> keys = CollectionUtils.newArrayList(inKeys);
+ Collections.sort(keys, EMOJI_KEY_COMPARATOR);
+ final int pageCount = (keys.size() - 1) / maxPageCount + 1;
+ final Key[][] retval = new Key[pageCount][maxPageCount];
+ for (int i = 0; i < keys.size(); ++i) {
+ retval[i / maxPageCount][i % maxPageCount] = keys.get(i);
+ }
+ return retval;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java
index d56a3cf25..74cfd9b4b 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiCategoryPageIndicatorView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.keyboard.emoji;
import com.android.inputmethod.latin.R;
@@ -24,8 +24,8 @@ import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.LinearLayout;
-public class EmojiCategoryPageIndicatorView extends LinearLayout {
- private static final float BOTTOM_MARGIN_RATIO = 0.66f;
+public final class EmojiCategoryPageIndicatorView extends LinearLayout {
+ private static final float BOTTOM_MARGIN_RATIO = 1.0f;
private final Paint mPaint = new Paint();
private int mCategoryPageSize = 0;
private int mCurrentCategoryPageId = 0;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java
index d57ea5a94..77c183a99 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/EmojiLayoutParams.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiLayoutParams.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard.internal;
+package com.android.inputmethod.keyboard.emoji;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.utils.ResourceUtils;
@@ -24,7 +24,7 @@ import android.support.v4.view.ViewPager;
import android.widget.ImageView;
import android.widget.LinearLayout;
-public class EmojiLayoutParams {
+final class EmojiLayoutParams {
private static final int DEFAULT_KEYBOARD_ROWS = 4;
public final int mEmojiPagerHeight;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
index e175a051e..d14ffeef9 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/EmojiPageKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard.internal;
+package com.android.inputmethod.keyboard.emoji;
import android.content.Context;
import android.os.Handler;
@@ -26,15 +26,14 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.R;
/**
* This is an extended {@link KeyboardView} class that hosts an emoji page keyboard.
- * Multi-touch unsupported. No {@link PointerTracker}s. No gesture support.
+ * Multi-touch unsupported. No gesture support.
*/
// TODO: Implement key popup preview.
-public final class EmojiPageKeyboardView extends KeyboardView implements
+final class EmojiPageKeyboardView extends KeyboardView implements
GestureDetector.OnGestureListener {
private static final long KEY_PRESS_DELAY_TIME = 250; // msec
private static final long KEY_RELEASE_DELAY_TIME = 30; // msec
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java
new file mode 100644
index 000000000..52a4dde97
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2014 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.emoji;
+
+import android.support.v4.view.PagerAdapter;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardView;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+final class EmojiPalettesAdapter extends PagerAdapter {
+ private static final String TAG = EmojiPalettesAdapter.class.getSimpleName();
+ private static final boolean DEBUG_PAGER = false;
+
+ private final EmojiPageKeyboardView.OnKeyEventListener mListener;
+ private final DynamicGridKeyboard mRecentsKeyboard;
+ private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews =
+ CollectionUtils.newSparseArray();
+ private final EmojiCategory mEmojiCategory;
+ private int mActivePosition = 0;
+
+ public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
+ final EmojiPageKeyboardView.OnKeyEventListener listener) {
+ mEmojiCategory = emojiCategory;
+ mListener = listener;
+ mRecentsKeyboard = mEmojiCategory.getKeyboard(EmojiCategory.ID_RECENTS, 0);
+ }
+
+ public void flushPendingRecentKeys() {
+ mRecentsKeyboard.flushPendingRecentKeys();
+ final KeyboardView recentKeyboardView =
+ mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
+ if (recentKeyboardView != null) {
+ recentKeyboardView.invalidateAllKeys();
+ }
+ }
+
+ public void addRecentKey(final Key key) {
+ if (mEmojiCategory.isInRecentTab()) {
+ mRecentsKeyboard.addPendingKey(key);
+ return;
+ }
+ mRecentsKeyboard.addKeyFirst(key);
+ final KeyboardView recentKeyboardView =
+ mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
+ if (recentKeyboardView != null) {
+ recentKeyboardView.invalidateAllKeys();
+ }
+ }
+
+ public void onPageScrolled() {
+ // Make sure the delayed key-down event (highlight effect and haptic feedback) will be
+ // canceled.
+ final EmojiPageKeyboardView currentKeyboardView =
+ mActiveKeyboardViews.get(mActivePosition);
+ if (currentKeyboardView != null) {
+ currentKeyboardView.releaseCurrentKey();
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mEmojiCategory.getTotalPageCountOfAllCategories();
+ }
+
+ @Override
+ public void setPrimaryItem(final ViewGroup container, final int position,
+ final Object object) {
+ if (mActivePosition == position) {
+ return;
+ }
+ final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
+ if (oldKeyboardView != null) {
+ oldKeyboardView.releaseCurrentKey();
+ oldKeyboardView.deallocateMemory();
+ }
+ mActivePosition = position;
+ }
+
+ @Override
+ public Object instantiateItem(final ViewGroup container, final int position) {
+ if (DEBUG_PAGER) {
+ Log.d(TAG, "instantiate item: " + position);
+ }
+ final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
+ if (oldKeyboardView != null) {
+ oldKeyboardView.deallocateMemory();
+ // This may be redundant but wanted to be safer..
+ mActiveKeyboardViews.remove(position);
+ }
+ final Keyboard keyboard =
+ mEmojiCategory.getKeyboardFromPagePosition(position);
+ final LayoutInflater inflater = LayoutInflater.from(container.getContext());
+ final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
+ R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
+ keyboardView.setKeyboard(keyboard);
+ keyboardView.setOnKeyEventListener(mListener);
+ container.addView(keyboardView);
+ mActiveKeyboardViews.put(position, keyboardView);
+ return keyboardView;
+ }
+
+ @Override
+ public boolean isViewFromObject(final View view, final Object object) {
+ return view == object;
+ }
+
+ @Override
+ public void destroyItem(final ViewGroup container, final int position,
+ final Object object) {
+ if (DEBUG_PAGER) {
+ Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
+ }
+ final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position);
+ if (keyboardView != null) {
+ keyboardView.deallocateMemory();
+ mActiveKeyboardViews.remove(position);
+ }
+ if (object instanceof View) {
+ container.removeView((View)object);
+ } else {
+ Log.w(TAG, "Warning!!! Emoji palette may be leaking. " + object);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
index d8b5758a6..3813c578a 100644
--- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java
@@ -14,55 +14,44 @@
* limitations under the License.
*/
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.keyboard.emoji;
import static com.android.inputmethod.latin.Constants.NOT_A_COORDINATE;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
-import android.graphics.Rect;
-import android.os.Build;
+import android.graphics.Typeface;
import android.os.CountDownTimer;
import android.preference.PreferenceManager;
-import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Pair;
-import android.util.SparseArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewGroup;
+import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TabHost.OnTabChangeListener;
import android.widget.TextView;
-import com.android.inputmethod.keyboard.internal.DynamicGridKeyboard;
-import com.android.inputmethod.keyboard.internal.EmojiLayoutParams;
-import com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView;
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.keyboard.KeyboardLayoutSet;
+import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.settings.Settings;
-import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.utils.ResourceUtils;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
@@ -79,17 +68,17 @@ import java.util.concurrent.TimeUnit;
public final class EmojiPalettesView extends LinearLayout implements OnTabChangeListener,
ViewPager.OnPageChangeListener, View.OnClickListener, View.OnTouchListener,
EmojiPageKeyboardView.OnKeyEventListener {
- static final String TAG = EmojiPalettesView.class.getSimpleName();
- private static final boolean DEBUG_PAGER = false;
- private final int mKeyBackgroundId;
- private final int mEmojiFunctionalKeyBackgroundId;
+ private final int mFunctionalKeyBackgroundId;
+ private final int mSpacebarBackgroundId;
private final ColorStateList mTabLabelColor;
private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener;
private EmojiPalettesAdapter mEmojiPalettesAdapter;
private final EmojiLayoutParams mEmojiLayoutParams;
+ private ImageButton mDeleteKey;
private TextView mAlphabetKeyLeft;
private TextView mAlphabetKeyRight;
+ private ImageButton mSpacebar;
private TabHost mTabHost;
private ViewPager mEmojiPager;
private int mCurrentPagerPosition = 0;
@@ -97,317 +86,6 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
- private static final int CATEGORY_ID_UNSPECIFIED = -1;
- public static final int CATEGORY_ID_RECENTS = 0;
- public static final int CATEGORY_ID_PEOPLE = 1;
- public static final int CATEGORY_ID_OBJECTS = 2;
- public static final int CATEGORY_ID_NATURE = 3;
- public static final int CATEGORY_ID_PLACES = 4;
- public static final int CATEGORY_ID_SYMBOLS = 5;
- public static final int CATEGORY_ID_EMOTICONS = 6;
-
- private static class CategoryProperties {
- public int mCategoryId;
- public int mPageCount;
- public CategoryProperties(final int categoryId, final int pageCount) {
- mCategoryId = categoryId;
- mPageCount = pageCount;
- }
- }
-
- private static class EmojiCategory {
- private static final String[] sCategoryName = {
- "recents",
- "people",
- "objects",
- "nature",
- "places",
- "symbols",
- "emoticons" };
- private static final int[] sCategoryIcon = {
- R.drawable.ic_emoji_recent_light,
- R.drawable.ic_emoji_people_light,
- R.drawable.ic_emoji_objects_light,
- R.drawable.ic_emoji_nature_light,
- R.drawable.ic_emoji_places_light,
- R.drawable.ic_emoji_symbols_light,
- 0 };
- private static final String[] sCategoryLabel =
- { null, null, null, null, null, null, ":-)" };
- private static final int[] sAccessibilityDescriptionResourceIdsForCategories = {
- R.string.spoken_descrption_emoji_category_recents,
- R.string.spoken_descrption_emoji_category_people,
- R.string.spoken_descrption_emoji_category_objects,
- R.string.spoken_descrption_emoji_category_nature,
- R.string.spoken_descrption_emoji_category_places,
- R.string.spoken_descrption_emoji_category_symbols,
- R.string.spoken_descrption_emoji_category_emoticons };
- private static final int[] sCategoryElementId = {
- KeyboardId.ELEMENT_EMOJI_RECENTS,
- KeyboardId.ELEMENT_EMOJI_CATEGORY1,
- KeyboardId.ELEMENT_EMOJI_CATEGORY2,
- KeyboardId.ELEMENT_EMOJI_CATEGORY3,
- KeyboardId.ELEMENT_EMOJI_CATEGORY4,
- KeyboardId.ELEMENT_EMOJI_CATEGORY5,
- KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
- private final SharedPreferences mPrefs;
- private final Resources mRes;
- private final int mMaxPageKeyCount;
- private final KeyboardLayoutSet mLayoutSet;
- private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap();
- private final ArrayList<CategoryProperties> mShownCategories =
- CollectionUtils.newArrayList();
- private final ConcurrentHashMap<Long, DynamicGridKeyboard>
- mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>();
-
- private int mCurrentCategoryId = CATEGORY_ID_UNSPECIFIED;
- private int mCurrentCategoryPageId = 0;
-
- public EmojiCategory(final SharedPreferences prefs, final Resources res,
- final KeyboardLayoutSet layoutSet) {
- mPrefs = prefs;
- mRes = res;
- mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count);
- mLayoutSet = layoutSet;
- for (int i = 0; i < sCategoryName.length; ++i) {
- mCategoryNameToIdMap.put(sCategoryName[i], i);
- }
- addShownCategoryId(CATEGORY_ID_RECENTS);
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2
- || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KeyLimePie")
- || android.os.Build.VERSION.CODENAME.equalsIgnoreCase("KitKat")) {
- addShownCategoryId(CATEGORY_ID_PEOPLE);
- addShownCategoryId(CATEGORY_ID_OBJECTS);
- addShownCategoryId(CATEGORY_ID_NATURE);
- addShownCategoryId(CATEGORY_ID_PLACES);
- mCurrentCategoryId =
- Settings.readLastShownEmojiCategoryId(mPrefs, CATEGORY_ID_PEOPLE);
- } else {
- mCurrentCategoryId =
- Settings.readLastShownEmojiCategoryId(mPrefs, CATEGORY_ID_SYMBOLS);
- }
- addShownCategoryId(CATEGORY_ID_SYMBOLS);
- addShownCategoryId(CATEGORY_ID_EMOTICONS);
- getKeyboard(CATEGORY_ID_RECENTS, 0 /* cagetoryPageId */)
- .loadRecentKeys(mCategoryKeyboardMap.values());
- }
-
- private void addShownCategoryId(final int categoryId) {
- // Load a keyboard of categoryId
- getKeyboard(categoryId, 0 /* cagetoryPageId */);
- final CategoryProperties properties =
- new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
- mShownCategories.add(properties);
- }
-
- public String getCategoryName(final int categoryId, final int categoryPageId) {
- return sCategoryName[categoryId] + "-" + categoryPageId;
- }
-
- public int getCategoryId(final String name) {
- final String[] strings = name.split("-");
- return mCategoryNameToIdMap.get(strings[0]);
- }
-
- public int getCategoryIcon(final int categoryId) {
- return sCategoryIcon[categoryId];
- }
-
- public String getCategoryLabel(final int categoryId) {
- return sCategoryLabel[categoryId];
- }
-
- public String getAccessibilityDescription(final int categoryId) {
- return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]);
- }
-
- public ArrayList<CategoryProperties> getShownCategories() {
- return mShownCategories;
- }
-
- public int getCurrentCategoryId() {
- return mCurrentCategoryId;
- }
-
- public int getCurrentCategoryPageSize() {
- return getCategoryPageSize(mCurrentCategoryId);
- }
-
- public int getCategoryPageSize(final int categoryId) {
- for (final CategoryProperties prop : mShownCategories) {
- if (prop.mCategoryId == categoryId) {
- return prop.mPageCount;
- }
- }
- Log.w(TAG, "Invalid category id: " + categoryId);
- // Should not reach here.
- return 0;
- }
-
- public void setCurrentCategoryId(final int categoryId) {
- mCurrentCategoryId = categoryId;
- Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId);
- }
-
- public void setCurrentCategoryPageId(final int id) {
- mCurrentCategoryPageId = id;
- }
-
- public int getCurrentCategoryPageId() {
- return mCurrentCategoryPageId;
- }
-
- public void saveLastTypedCategoryPage() {
- Settings.writeLastTypedEmojiCategoryPageId(
- mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
- }
-
- public boolean isInRecentTab() {
- return mCurrentCategoryId == CATEGORY_ID_RECENTS;
- }
-
- public int getTabIdFromCategoryId(final int categoryId) {
- for (int i = 0; i < mShownCategories.size(); ++i) {
- if (mShownCategories.get(i).mCategoryId == categoryId) {
- return i;
- }
- }
- Log.w(TAG, "categoryId not found: " + categoryId);
- return 0;
- }
-
- // Returns the view pager's page position for the categoryId
- public int getPageIdFromCategoryId(final int categoryId) {
- final int lastSavedCategoryPageId =
- Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId);
- int sum = 0;
- for (int i = 0; i < mShownCategories.size(); ++i) {
- final CategoryProperties props = mShownCategories.get(i);
- if (props.mCategoryId == categoryId) {
- return sum + lastSavedCategoryPageId;
- }
- sum += props.mPageCount;
- }
- Log.w(TAG, "categoryId not found: " + categoryId);
- return 0;
- }
-
- public int getRecentTabId() {
- return getTabIdFromCategoryId(CATEGORY_ID_RECENTS);
- }
-
- private int getCategoryPageCount(final int categoryId) {
- final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
- return (keyboard.getSortedKeys().size() - 1) / mMaxPageKeyCount + 1;
- }
-
- // Returns a pair of the category id and the category page id from the view pager's page
- // position. The category page id is numbered in each category. And the view page position
- // is the position of the current shown page in the view pager which contains all pages of
- // all categories.
- public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(final int position) {
- int sum = 0;
- for (final CategoryProperties properties : mShownCategories) {
- final int temp = sum;
- sum += properties.mPageCount;
- if (sum > position) {
- return new Pair<Integer, Integer>(properties.mCategoryId, position - temp);
- }
- }
- return null;
- }
-
- // Returns a keyboard from the view pager's page position.
- public DynamicGridKeyboard getKeyboardFromPagePosition(final int position) {
- final Pair<Integer, Integer> categoryAndId =
- getCategoryIdAndPageIdFromPagePosition(position);
- if (categoryAndId != null) {
- return getKeyboard(categoryAndId.first, categoryAndId.second);
- }
- return null;
- }
-
- private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) {
- return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
- }
-
- public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
- synchronized (mCategoryKeyboardMap) {
- final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id);
- if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) {
- return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
- }
-
- if (categoryId == CATEGORY_ID_RECENTS) {
- final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs,
- mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
- mMaxPageKeyCount, categoryId);
- mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd);
- return kbd;
- }
-
- final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
- final Key[][] sortedKeys = sortKeysIntoPages(
- keyboard.getSortedKeys(), mMaxPageKeyCount);
- for (int pageId = 0; pageId < sortedKeys.length; ++pageId) {
- final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
- mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
- mMaxPageKeyCount, categoryId);
- for (final Key emojiKey : sortedKeys[pageId]) {
- if (emojiKey == null) {
- break;
- }
- tempKeyboard.addKeyLast(emojiKey);
- }
- mCategoryKeyboardMap.put(
- getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard);
- }
- return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
- }
- }
-
- public int getTotalPageCountOfAllCategories() {
- int sum = 0;
- for (CategoryProperties properties : mShownCategories) {
- sum += properties.mPageCount;
- }
- return sum;
- }
-
- private static Comparator<Key> EMOJI_KEY_COMPARATOR = new Comparator<Key>() {
- @Override
- public int compare(final Key lhs, final Key rhs) {
- final Rect lHitBox = lhs.getHitBox();
- final Rect rHitBox = rhs.getHitBox();
- if (lHitBox.top < rHitBox.top) {
- return -1;
- } else if (lHitBox.top > rHitBox.top) {
- return 1;
- }
- if (lHitBox.left < rHitBox.left) {
- return -1;
- } else if (lHitBox.left > rHitBox.left) {
- return 1;
- }
- if (lhs.getCode() == rhs.getCode()) {
- return 0;
- }
- return lhs.getCode() < rhs.getCode() ? -1 : 1;
- }
- };
-
- private static Key[][] sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount) {
- final ArrayList<Key> keys = CollectionUtils.newArrayList(inKeys);
- Collections.sort(keys, EMOJI_KEY_COMPARATOR);
- final int pageCount = (keys.size() - 1) / maxPageCount + 1;
- final Key[][] retval = new Key[pageCount][maxPageCount];
- for (int i = 0; i < keys.size(); ++i) {
- retval[i / maxPageCount][i % maxPageCount] = keys.get(i);
- }
- return retval;
- }
- }
-
private final EmojiCategory mEmojiCategory;
public EmojiPalettesView(final Context context, final AttributeSet attrs) {
@@ -418,10 +96,12 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
super(context, attrs, defStyle);
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(attrs,
R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
- mKeyBackgroundId = keyboardViewAttr.getResourceId(
+ final int keyBackgroundId = keyboardViewAttr.getResourceId(
R.styleable.KeyboardView_keyBackground, 0);
- mEmojiFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId(
- R.styleable.KeyboardView_keyBackgroundEmojiFunctional, 0);
+ mFunctionalKeyBackgroundId = keyboardViewAttr.getResourceId(
+ R.styleable.KeyboardView_functionalKeyBackground, keyBackgroundId);
+ mSpacebarBackgroundId = keyboardViewAttr.getResourceId(
+ R.styleable.KeyboardView_spacebarBackground, keyBackgroundId);
keyboardViewAttr.recycle();
final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs,
R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView);
@@ -470,8 +150,11 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
final TextView textView = (TextView)LayoutInflater.from(getContext()).inflate(
R.layout.emoji_keyboard_tab_label, null);
textView.setText(mEmojiCategory.getCategoryLabel(categoryId));
+ textView.setTypeface(Typeface.DEFAULT_BOLD);
textView.setContentDescription(mEmojiCategory.getAccessibilityDescription(categoryId));
- textView.setTextColor(mTabLabelColor);
+ if (mTabLabelColor != null) {
+ textView.setTextColor(mTabLabelColor);
+ }
tspec.setIndicator(textView);
}
host.addTab(tspec);
@@ -481,7 +164,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
protected void onFinishInflate() {
mTabHost = (TabHost)findViewById(R.id.emoji_category_tabhost);
mTabHost.setup();
- for (final CategoryProperties properties : mEmojiCategory.getShownCategories()) {
+ for (final EmojiCategory.CategoryProperties properties
+ : mEmojiCategory.getShownCategories()) {
addTab(mTabHost, properties.mCategoryId);
}
mTabHost.setOnTabChangedListener(this);
@@ -506,9 +190,10 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
mEmojiLayoutParams.setActionBarProperties(actionBar);
// deleteKey depends only on OnTouchListener.
- final ImageView deleteKey = (ImageView)findViewById(R.id.emoji_keyboard_delete);
- deleteKey.setTag(Constants.CODE_DELETE);
- deleteKey.setOnTouchListener(mDeleteKeyOnTouchListener);
+ mDeleteKey = (ImageButton)findViewById(R.id.emoji_keyboard_delete);
+ mDeleteKey.setBackgroundResource(mFunctionalKeyBackgroundId);
+ mDeleteKey.setTag(Constants.CODE_DELETE);
+ mDeleteKey.setOnTouchListener(mDeleteKeyOnTouchListener);
// {@link #mAlphabetKeyLeft}, {@link #mAlphabetKeyRight, and spaceKey depend on
// {@link View.OnClickListener} as well as {@link View.OnTouchListener}.
@@ -518,21 +203,21 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
// The text on alphabet keys are set at
// {@link #startEmojiPalettes(String,int,float,Typeface)}.
mAlphabetKeyLeft = (TextView)findViewById(R.id.emoji_keyboard_alphabet_left);
- mAlphabetKeyLeft.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
+ mAlphabetKeyLeft.setBackgroundResource(mFunctionalKeyBackgroundId);
mAlphabetKeyLeft.setTag(Constants.CODE_ALPHA_FROM_EMOJI);
mAlphabetKeyLeft.setOnTouchListener(this);
mAlphabetKeyLeft.setOnClickListener(this);
mAlphabetKeyRight = (TextView)findViewById(R.id.emoji_keyboard_alphabet_right);
- mAlphabetKeyRight.setBackgroundResource(mEmojiFunctionalKeyBackgroundId);
+ mAlphabetKeyRight.setBackgroundResource(mFunctionalKeyBackgroundId);
mAlphabetKeyRight.setTag(Constants.CODE_ALPHA_FROM_EMOJI);
mAlphabetKeyRight.setOnTouchListener(this);
mAlphabetKeyRight.setOnClickListener(this);
- final ImageView spaceKey = (ImageView)findViewById(R.id.emoji_keyboard_space);
- spaceKey.setBackgroundResource(mKeyBackgroundId);
- spaceKey.setTag(Constants.CODE_SPACE);
- spaceKey.setOnTouchListener(this);
- spaceKey.setOnClickListener(this);
- mEmojiLayoutParams.setKeyProperties(spaceKey);
+ mSpacebar = (ImageButton)findViewById(R.id.emoji_keyboard_space);
+ mSpacebar.setBackgroundResource(mSpacebarBackgroundId);
+ mSpacebar.setTag(Constants.CODE_SPACE);
+ mSpacebar.setOnTouchListener(this);
+ mSpacebar.setOnClickListener(this);
+ mEmojiLayoutParams.setKeyProperties(mSpacebar);
}
@Override
@@ -631,7 +316,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
/**
* Called from {@link EmojiPageKeyboardView} through
- * {@link com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView.OnKeyEventListener}
+ * {@link com.android.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener}
* interface to handle touch events from non-View-based elements such as Emoji buttons.
*/
@Override
@@ -642,7 +327,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
/**
* Called from {@link EmojiPageKeyboardView} through
- * {@link com.android.inputmethod.keyboard.internal.EmojiPageKeyboardView.OnKeyEventListener}
+ * {@link com.android.inputmethod.keyboard.emoji.EmojiPageKeyboardView.OnKeyEventListener}
* interface to handle touch events from non-View-based elements such as Emoji buttons.
*/
@Override
@@ -674,10 +359,9 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
public void startEmojiPalettes(final String switchToAlphaLabel,
- final KeyVisualAttributes keyVisualAttr) {
- if (DEBUG_PAGER) {
- Log.d(TAG, "allocate emoji palettes memory " + mCurrentPagerPosition);
- }
+ final KeyVisualAttributes keyVisualAttr, final KeyboardIconsSet iconSet) {
+ mDeleteKey.setImageDrawable(iconSet.getIconDrawable(KeyboardIconsSet.NAME_DELETE_KEY));
+ mSpacebar.setImageDrawable(iconSet.getIconDrawable(KeyboardIconsSet.NAME_SPACE_KEY));
final KeyDrawParams params = new KeyDrawParams();
params.updateParams(mEmojiLayoutParams.getActionBarHeight(), keyVisualAttr);
setupAlphabetKey(mAlphabetKeyLeft, switchToAlphaLabel, params);
@@ -687,9 +371,6 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
public void stopEmojiPalettes() {
- if (DEBUG_PAGER) {
- Log.d(TAG, "deallocate emoji palettes memory");
- }
mEmojiPalettesAdapter.flushPendingRecentKeys();
mEmojiPager.setAdapter(null);
}
@@ -714,7 +395,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
return;
}
- if (oldCategoryId == CATEGORY_ID_RECENTS) {
+ if (oldCategoryId == EmojiCategory.ID_RECENTS) {
// Needs to save pending updates for recent keys when we get out of the recents
// category because we don't want to move the recent emojis around while the user
// is in the recents category.
@@ -733,124 +414,11 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
}
- private static class EmojiPalettesAdapter extends PagerAdapter {
- private final EmojiPageKeyboardView.OnKeyEventListener mListener;
- private final DynamicGridKeyboard mRecentsKeyboard;
- private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews =
- CollectionUtils.newSparseArray();
- private final EmojiCategory mEmojiCategory;
- private int mActivePosition = 0;
-
- public EmojiPalettesAdapter(final EmojiCategory emojiCategory,
- final EmojiPageKeyboardView.OnKeyEventListener listener) {
- mEmojiCategory = emojiCategory;
- mListener = listener;
- mRecentsKeyboard = mEmojiCategory.getKeyboard(CATEGORY_ID_RECENTS, 0);
- }
-
- public void flushPendingRecentKeys() {
- mRecentsKeyboard.flushPendingRecentKeys();
- final KeyboardView recentKeyboardView =
- mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
- if (recentKeyboardView != null) {
- recentKeyboardView.invalidateAllKeys();
- }
- }
-
- public void addRecentKey(final Key key) {
- if (mEmojiCategory.isInRecentTab()) {
- mRecentsKeyboard.addPendingKey(key);
- return;
- }
- mRecentsKeyboard.addKeyFirst(key);
- final KeyboardView recentKeyboardView =
- mActiveKeyboardViews.get(mEmojiCategory.getRecentTabId());
- if (recentKeyboardView != null) {
- recentKeyboardView.invalidateAllKeys();
- }
- }
-
- public void onPageScrolled() {
- // Make sure the delayed key-down event (highlight effect and haptic feedback) will be
- // canceled.
- final EmojiPageKeyboardView currentKeyboardView =
- mActiveKeyboardViews.get(mActivePosition);
- if (currentKeyboardView != null) {
- currentKeyboardView.releaseCurrentKey();
- }
- }
-
- @Override
- public int getCount() {
- return mEmojiCategory.getTotalPageCountOfAllCategories();
- }
-
- @Override
- public void setPrimaryItem(final ViewGroup container, final int position,
- final Object object) {
- if (mActivePosition == position) {
- return;
- }
- final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition);
- if (oldKeyboardView != null) {
- oldKeyboardView.releaseCurrentKey();
- oldKeyboardView.deallocateMemory();
- }
- mActivePosition = position;
- }
-
- @Override
- public Object instantiateItem(final ViewGroup container, final int position) {
- if (DEBUG_PAGER) {
- Log.d(TAG, "instantiate item: " + position);
- }
- final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(position);
- if (oldKeyboardView != null) {
- oldKeyboardView.deallocateMemory();
- // This may be redundant but wanted to be safer..
- mActiveKeyboardViews.remove(position);
- }
- final Keyboard keyboard =
- mEmojiCategory.getKeyboardFromPagePosition(position);
- final LayoutInflater inflater = LayoutInflater.from(container.getContext());
- final EmojiPageKeyboardView keyboardView = (EmojiPageKeyboardView)inflater.inflate(
- R.layout.emoji_keyboard_page, container, false /* attachToRoot */);
- keyboardView.setKeyboard(keyboard);
- keyboardView.setOnKeyEventListener(mListener);
- container.addView(keyboardView);
- mActiveKeyboardViews.put(position, keyboardView);
- return keyboardView;
- }
-
- @Override
- public boolean isViewFromObject(final View view, final Object object) {
- return view == object;
- }
-
- @Override
- public void destroyItem(final ViewGroup container, final int position,
- final Object object) {
- if (DEBUG_PAGER) {
- Log.d(TAG, "destroy item: " + position + ", " + object.getClass().getSimpleName());
- }
- final EmojiPageKeyboardView keyboardView = mActiveKeyboardViews.get(position);
- if (keyboardView != null) {
- keyboardView.deallocateMemory();
- mActiveKeyboardViews.remove(position);
- }
- if (object instanceof View) {
- container.removeView((View)object);
- } else {
- Log.w(TAG, "Warning!!! Emoji palette may be leaking. " + object);
- }
- }
- }
-
private static class DeleteKeyOnTouchListener implements OnTouchListener {
- private static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30);
- private final int mDeleteKeyPressedBackgroundColor;
- private final long mKeyRepeatStartTimeout;
- private final long mKeyRepeatInterval;
+ static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30);
+ final int mDeleteKeyPressedBackgroundColor;
+ final long mKeyRepeatStartTimeout;
+ final long mKeyRepeatInterval;
public DeleteKeyOnTouchListener(Context context) {
final Resources res = context.getResources();
@@ -953,7 +521,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange
}
// Called by {@link #mTimer} in the UI thread as an auto key-repeat signal.
- private void onKeyRepeat() {
+ void onKeyRepeat() {
switch (mState) {
case KEY_REPEAT_STATE_INITIALIZED:
// Basically this should not happen.
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index dfe0df04c..2aeeed87f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -664,6 +664,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
R.styleable.Keyboard_Case_isMultiLine, id.isMultiLine());
final boolean imeActionMatched = matchInteger(caseAttr,
R.styleable.Keyboard_Case_imeAction, id.imeAction());
+ final boolean isIconDefinedMatched = isIconDefined(caseAttr,
+ R.styleable.Keyboard_Case_isIconDefined, mParams.mIconsSet);
final boolean localeCodeMatched = matchString(caseAttr,
R.styleable.Keyboard_Case_localeCode, id.mLocale.toString());
final boolean languageCodeMatched = matchString(caseAttr,
@@ -675,10 +677,11 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
&& passwordInputMatched && clobberSettingsKeyMatched
&& supportsSwitchingToShortcutImeMatched && hasShortcutKeyMatched
&& languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched
- && localeCodeMatched && languageCodeMatched && countryCodeMatched;
+ && isIconDefinedMatched && localeCodeMatched && languageCodeMatched
+ && countryCodeMatched;
if (DEBUG) {
- startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
+ startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE,
textAttr(caseAttr.getString(
R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"),
textAttr(caseAttr.getString(
@@ -704,6 +707,8 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
"languageSwitchKeyEnabled"),
booleanAttr(caseAttr, R.styleable.Keyboard_Case_isMultiLine,
"isMultiLine"),
+ textAttr(caseAttr.getString(R.styleable.Keyboard_Case_isIconDefined),
+ "isIconDefined"),
textAttr(caseAttr.getString(R.styleable.Keyboard_Case_localeCode),
"localeCode"),
textAttr(caseAttr.getString(R.styleable.Keyboard_Case_languageCode),
@@ -755,6 +760,16 @@ public class KeyboardBuilder<KP extends KeyboardParams> {
return false;
}
+ private static boolean isIconDefined(final TypedArray a, final int index,
+ final KeyboardIconsSet iconsSet) {
+ if (!a.hasValue(index)) {
+ return true;
+ }
+ final String iconName = a.getString(index);
+ final int iconId = KeyboardIconsSet.getIconId(iconName);
+ return iconsSet.getIconDrawable(iconId) != null;
+ }
+
private boolean parseDefault(final XmlPullParser parser, final KeyboardRow row,
final boolean skip) throws XmlPullParserException, IOException {
if (DEBUG) startTag("<%s>", TAG_DEFAULT);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 6c9b5adc3..b5a94807e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -42,7 +42,12 @@ public final class KeyboardIconsSet {
public static final String NAME_SPACE_KEY = "space_key";
public static final String NAME_SPACE_KEY_FOR_NUMBER_LAYOUT = "space_key_for_number_layout";
public static final String NAME_ENTER_KEY = "enter_key";
+ public static final String NAME_GO_KEY = "go_key";
public static final String NAME_SEARCH_KEY = "search_key";
+ public static final String NAME_SEND_KEY = "send_key";
+ public static final String NAME_NEXT_KEY = "next_key";
+ public static final String NAME_DONE_KEY = "done_key";
+ public static final String NAME_PREVIOUS_KEY = "previous_key";
public static final String NAME_TAB_KEY = "tab_key";
public static final String NANE_TAB_KEY_PREVIEW = "tab_key_preview";
public static final String NAME_SHORTCUT_KEY = "shortcut_key";
@@ -64,7 +69,12 @@ public final class KeyboardIconsSet {
NAME_SETTINGS_KEY, R.styleable.Keyboard_iconSettingsKey,
NAME_SPACE_KEY, R.styleable.Keyboard_iconSpaceKey,
NAME_ENTER_KEY, R.styleable.Keyboard_iconEnterKey,
+ NAME_GO_KEY, R.styleable.Keyboard_iconGoKey,
NAME_SEARCH_KEY, R.styleable.Keyboard_iconSearchKey,
+ NAME_SEND_KEY, R.styleable.Keyboard_iconSendKey,
+ NAME_NEXT_KEY, R.styleable.Keyboard_iconNextKey,
+ NAME_DONE_KEY, R.styleable.Keyboard_iconDoneKey,
+ NAME_PREVIOUS_KEY, R.styleable.Keyboard_iconPreviousKey,
NAME_TAB_KEY, R.styleable.Keyboard_iconTabKey,
NAME_SHORTCUT_KEY, R.styleable.Keyboard_iconShortcutKey,
NAME_SPACE_KEY_FOR_NUMBER_LAYOUT, R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
@@ -128,6 +138,10 @@ public final class KeyboardIconsSet {
throw new RuntimeException("unknown icon name: " + name);
}
+ public Drawable getIconDrawable(final String name) {
+ return getIconDrawable(getIconId(name));
+ }
+
public Drawable getIconDrawable(final int iconId) {
if (isValidIconId(iconId)) {
return mIcons[iconId];
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index e71723a15..67ca59540 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -115,6 +115,11 @@ public final class Constants {
*/
public static final String IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
+ /**
+ * The subtype extra value used to specify the combining rules.
+ */
+ public static final String COMBINING_RULES = "CombiningRules";
+
private ExtraValue() {
// This utility class is not publicly instantiable.
}
@@ -164,6 +169,8 @@ public final class Constants {
// How many continuous deletes at which to start deleting at a higher speed.
public static final int DELETE_ACCELERATE_AT = 20;
+ public static final String WORD_SEPARATOR = " ";
+
public static boolean isValidCoordinate(final int coordinate) {
// Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE},
// and {@link SPELL_CHECKER_COORDINATE}.
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 0742fbde9..cd380db6b 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -57,6 +57,8 @@ public abstract class Dictionary {
public static final String TYPE_USER_HISTORY = "history";
// Personalization dictionary.
public static final String TYPE_PERSONALIZATION = "personalization";
+ // Contextual dictionary.
+ public static final String TYPE_CONTEXTUAL = "contextual";
public final String mDictType;
public Dictionary(final String dictType) {
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index 9735645cb..e0220e137 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -23,6 +23,7 @@ import android.util.Log;
import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.ContextualDictionary;
import com.android.inputmethod.latin.personalization.PersonalizationDictionary;
import com.android.inputmethod.latin.personalization.UserHistoryDictionary;
import com.android.inputmethod.latin.utils.CollectionUtils;
@@ -55,7 +56,7 @@ public class DictionaryFacilitatorForSuggest {
private boolean mIsUserDictEnabled = false;
private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0);
// To synchronize assigning mDictionaries to ensure closing dictionaries.
- private Object mLock = new Object();
+ private final Object mLock = new Object();
private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTION =
new String[] {
@@ -63,7 +64,8 @@ public class DictionaryFacilitatorForSuggest {
Dictionary.TYPE_USER_HISTORY,
Dictionary.TYPE_PERSONALIZATION,
Dictionary.TYPE_USER,
- Dictionary.TYPE_CONTACTS
+ Dictionary.TYPE_CONTACTS,
+ Dictionary.TYPE_CONTEXTUAL
};
private static final Map<String, Class<? extends ExpandableBinaryDictionary>>
@@ -74,6 +76,7 @@ public class DictionaryFacilitatorForSuggest {
DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_PERSONALIZATION, PersonalizationDictionary.class);
DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER, UserBinaryDictionary.class);
DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTACTS, ContactsBinaryDictionary.class);
+ DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_CONTEXTUAL, ContextualDictionary.class);
}
private static final String DICT_FACTORY_METHOD_NAME = "getDictionary";
@@ -201,6 +204,7 @@ public class DictionaryFacilitatorForSuggest {
if (usePersonalizedDicts) {
subDictTypesToUse.add(Dictionary.TYPE_USER_HISTORY);
subDictTypesToUse.add(Dictionary.TYPE_PERSONALIZATION);
+ subDictTypesToUse.add(Dictionary.TYPE_CONTEXTUAL);
}
final Dictionary newMainDict;
@@ -366,32 +370,46 @@ public class DictionaryFacilitatorForSuggest {
}
public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
- final String previousWord, final int timeStampInSeconds) {
+ final String previousWord, final int timeStampInSeconds,
+ final boolean blockPotentiallyOffensive) {
final Dictionaries dictionaries = mDictionaries;
+ final String[] words = suggestion.split(Constants.WORD_SEPARATOR);
+ for (int i = 0; i < words.length; i++) {
+ final String currentWord = words[i];
+ final String prevWord = (i == 0) ? previousWord : words[i - 1];
+ final boolean wasCurrentWordAutoCapitalized = (i == 0) ? wasAutoCapitalized : false;
+ addWordToUserHistory(dictionaries, prevWord, currentWord,
+ wasCurrentWordAutoCapitalized, timeStampInSeconds, blockPotentiallyOffensive);
+ }
+ }
+
+ private void addWordToUserHistory(final Dictionaries dictionaries, final String prevWord,
+ final String word, final boolean wasAutoCapitalized, final int timeStampInSeconds,
+ final boolean blockPotentiallyOffensive) {
final ExpandableBinaryDictionary userHistoryDictionary =
dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
if (userHistoryDictionary == null) {
return;
}
- final int maxFreq = getMaxFrequency(suggestion);
- if (maxFreq == 0) {
+ final int maxFreq = getMaxFrequency(word);
+ if (maxFreq == 0 && blockPotentiallyOffensive) {
return;
}
- final String suggestionLowerCase = suggestion.toLowerCase(dictionaries.mLocale);
+ final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale);
final String secondWord;
if (wasAutoCapitalized) {
- if (isValidWord(suggestion, false /* ignoreCase */)
- && !isValidWord(suggestionLowerCase, false /* ignoreCase */)) {
+ if (isValidWord(word, false /* ignoreCase */)
+ && !isValidWord(lowerCasedWord, false /* ignoreCase */)) {
// If the word was auto-capitalized and exists only as a capitalized word in the
// dictionary, then we must not downcase it before registering it. For example,
// the name of the contacts in start-of-sentence position would come here with the
// wasAutoCapitalized flag: if we downcase it, we'd register a lower-case version
// of that contact's name which would end up popping in suggestions.
- secondWord = suggestion;
+ secondWord = word;
} else {
// If however the word is not in the dictionary, or exists as a lower-case word
// only, then we consider that was a lower-case word that had been auto-capitalized.
- secondWord = suggestionLowerCase;
+ secondWord = lowerCasedWord;
}
} else {
// HACK: We'd like to avoid adding the capitalized form of common words to the User
@@ -399,20 +417,20 @@ public class DictionaryFacilitatorForSuggest {
// consolidation is done.
// TODO: Remove this hack when ready.
final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ?
- dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(suggestionLowerCase) :
+ dictionaries.getDict(Dictionary.TYPE_MAIN).getFrequency(lowerCasedWord) :
Dictionary.NOT_A_PROBABILITY;
if (maxFreq < lowerCaseFreqInMainDict
&& lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
// Use lower cased word as the word can be a distracter of the popular word.
- secondWord = suggestionLowerCase;
+ secondWord = lowerCasedWord;
} else {
- secondWord = suggestion;
+ secondWord = word;
}
}
// We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
// We don't add words with 0-frequency (assuming they would be profanity etc.).
final boolean isValid = maxFreq > 0;
- UserHistoryDictionary.addToDictionary(userHistoryDictionary, previousWord, secondWord,
+ UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWord, secondWord,
isValid, timeStampInSeconds);
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 6818c156e..e323f0ab2 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -607,6 +607,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
Log.d(TAG, "Dump dictionary: " + mDictName);
try {
final DictionaryHeader header = mBinaryDictionary.getHeader();
+ Log.d(TAG, "Format version: " + mBinaryDictionary.getFormatVersion());
Log.d(TAG, CombinedFormatUtils.formatAttributeMap(
header.mDictionaryOptions.mAttributes));
} catch (final UnsupportedFormatException e) {
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d64a1a6f7..8a2ed1088 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -540,18 +540,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
refreshPersonalizationDictionarySession();
}
- private DistracterFilter createDistracterFilter() {
- final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
- // TODO: Create Keyboard when mainKeyboardView is null.
- // TODO: Figure out the most reasonable keyboard for the filter. Refer to the
- // spellchecker's logic.
- final Keyboard keyboard = (mainKeyboardView != null) ?
- mainKeyboardView.getKeyboard() : null;
- final DistracterFilter distracterFilter = new DistracterFilter(mInputLogic.mSuggest,
- keyboard);
- return distracterFilter;
- }
-
private void refreshPersonalizationDictionarySession() {
final DictionaryFacilitatorForSuggest dictionaryFacilitator =
mInputLogic.mSuggest.mDictionaryFacilitator;
@@ -733,6 +721,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
mSubtypeSwitcher.onSubtypeChanged(subtype);
+ mInputLogic.onSubtypeChanged(SubtypeLocaleUtils.getCombiningRulesExtraValue(subtype));
loadKeyboard();
}
@@ -808,7 +797,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// The app calling setText() has the effect of clearing the composing
// span, so we should reset our state unconditionally, even if restarting is true.
- mInputLogic.startInput(restarting, editorInfo);
+ // We also tell the input logic about the combining rules for the current subtype, so
+ // it can adjust its combiners if needed.
+ mInputLogic.startInput(restarting, editorInfo,
+ mSubtypeSwitcher.getCombiningRulesExtraValueOfCurrentSubtype());
// Note: the following does a round-trip IPC on the main thread: be careful
final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
@@ -1751,6 +1743,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mInputLogic.mSuggest.mDictionaryFacilitator.clearPersonalizationDictionary();
}
+ @UsedForTesting
+ /* package for test */ DistracterFilter createDistracterFilter() {
+ return DistracterFilter.createDistracterFilter(mInputLogic.mSuggest, mKeyboardSwitcher);
+ }
+
public void dumpDictionaryForDebug(final String dictName) {
final DictionaryFacilitatorForSuggest dictionaryFacilitator =
mInputLogic.mSuggest.mDictionaryFacilitator;
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 021133945..c8a2fb2f9 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -52,7 +52,6 @@ public final class SubtypeSwitcher {
private /* final */ RichInputMethodManager mRichImm;
private /* final */ Resources mResources;
- private /* final */ ConnectivityManager mConnectivityManager;
private final LanguageOnSpacebarHelper mLanguageOnSpacebarHelper =
new LanguageOnSpacebarHelper();
@@ -111,10 +110,10 @@ public final class SubtypeSwitcher {
}
mResources = context.getResources();
mRichImm = RichInputMethodManager.getInstance();
- mConnectivityManager = (ConnectivityManager) context.getSystemService(
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
- final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
+ final NetworkInfo info = connectivityManager.getActiveNetworkInfo();
mIsNetworkConnected = (info != null && info.isConnected());
onSubtypeChanged(getCurrentSubtype());
@@ -327,4 +326,8 @@ public final class SubtypeSwitcher {
+ DUMMY_EMOJI_SUBTYPE);
return DUMMY_EMOJI_SUBTYPE;
}
+
+ public String getCombiningRulesExtraValueOfCurrentSubtype() {
+ return SubtypeLocaleUtils.getCombiningRulesExtraValue(getCurrentSubtype());
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index d755195f2..cdee496a8 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -41,6 +41,7 @@ public final class WordComposer {
public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
private CombinerChain mCombinerChain;
+ private String mCombiningSpec; // Memory so that we don't uselessly recreate the combiner chain
// The list of events that served to compose this string.
private final ArrayList<Event> mEvents;
@@ -91,6 +92,21 @@ public final class WordComposer {
}
/**
+ * Restart input with a new combining spec.
+ * @param combiningSpec The spec string for combining. This is found in the extra value.
+ */
+ public void restart(final String combiningSpec) {
+ final String nonNullCombiningSpec = null == combiningSpec ? "" : combiningSpec;
+ if (nonNullCombiningSpec.equals(mCombiningSpec)) {
+ mCombinerChain.reset();
+ } else {
+ mCombinerChain = new CombinerChain(CombinerChain.createCombiners(nonNullCombiningSpec));
+ mCombiningSpec = nonNullCombiningSpec;
+ }
+ reset();
+ }
+
+ /**
* Clear out the keys registered so far.
*/
public void reset() {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index 75432fbac..1156c7737 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -97,6 +97,11 @@ public final class InputLogic {
private boolean mIsAutoCorrectionIndicatorOn;
private long mDoubleSpacePeriodCountdownStart;
+ /**
+ * Create a new instance of the input logic.
+ * @param latinIME the instance of the parent LatinIME. We should remove this when we can.
+ * @param suggestionStripViewAccessor an object to access the suggestion strip view.
+ */
public InputLogic(final LatinIME latinIME,
final SuggestionStripViewAccessor suggestionStripViewAccessor) {
mLatinIME = latinIME;
@@ -117,9 +122,12 @@ public final class InputLogic {
*
* @param restarting whether input is starting in the same field as before. Unused for now.
* @param editorInfo the editorInfo associated with the editor.
+ * @param combiningSpec the combining spec string for this subtype
*/
- public void startInput(final boolean restarting, final EditorInfo editorInfo) {
+ public void startInput(final boolean restarting, final EditorInfo editorInfo,
+ final String combiningSpec) {
mEnteredText = null;
+ mWordComposer.restart(combiningSpec);
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SpaceState.NONE;
@@ -138,6 +146,14 @@ public final class InputLogic {
}
/**
+ * Call this when the subtype changes.
+ * @param combiningSpec the spec string for the combining rules
+ */
+ public void onSubtypeChanged(final String combiningSpec) {
+ mWordComposer.restart(combiningSpec);
+ }
+
+ /**
* Clean up the input logic after input is finished.
*/
public void finishInput() {
@@ -588,7 +604,7 @@ public final class InputLogic {
if (null != candidate
&& mSuggestedWords.mSequenceNumber >= mAutoCommitSequenceNumber) {
if (candidate.mSourceDict.shouldAutoCommit(candidate)) {
- final String[] commitParts = candidate.mWord.split(" ", 2);
+ final String[] commitParts = candidate.mWord.split(Constants.WORD_SEPARATOR, 2);
batchPointers.shift(candidate.mIndexOfTouchPointOfSecondWord);
promotePhantomSpace(settingsValues);
mConnection.commitText(commitParts[0], 0);
@@ -920,7 +936,11 @@ public final class InputLogic {
} else {
mWordComposer.processEvent(inputTransaction.mEvent);
}
- mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+ if (mWordComposer.isComposingWord()) {
+ mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1);
+ } else {
+ mConnection.commitText("", 1);
+ }
inputTransaction.setRequiresUpdateSuggestions();
} else {
if (mLastComposedWord.canRevertCommit()) {
@@ -1225,7 +1245,7 @@ public final class InputLogic {
final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds(
System.currentTimeMillis());
mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWord,
- timeStampInSeconds);
+ timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive);
}
public void performUpdateSuggestionStripSync(final SettingsValues settingsValues) {
@@ -1946,10 +1966,11 @@ public final class InputLogic {
final CharSequence chosenWordWithSuggestions =
SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord,
suggestedWords);
- mConnection.commitText(chosenWordWithSuggestions, 1);
- // TODO: we pass 2 here, but would it be better to move this above and pass 1 instead?
+ // Use the 2nd previous word as the previous word because the 1st previous word is the word
+ // to be committed.
final String prevWord = mConnection.getNthPreviousWord(
settingsValues.mSpacingAndPunctuations, 2);
+ mConnection.commitText(chosenWordWithSuggestions, 1);
// Add the word to the user history dictionary
performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWord);
// TODO: figure out here if this is an auto-correct or if the best word is actually
diff --git a/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
new file mode 100644
index 000000000..96f03f9ff
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/ContextualDictionary.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 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.latin.personalization;
+
+import android.content.Context;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
+import java.io.File;
+import java.util.Locale;
+
+public class ContextualDictionary extends ExpandableBinaryDictionary {
+ /* package */ static final String NAME = PersonalizationDictionary.class.getSimpleName();
+
+ private ContextualDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ super(context, getDictName(NAME, locale, dictFile), locale, Dictionary.TYPE_CONTEXTUAL,
+ dictFile);
+ // Always reset the contents.
+ clear();
+ }
+ @UsedForTesting
+ public static ContextualDictionary getDictionary(final Context context, final Locale locale,
+ final File dictFile) {
+ return new ContextualDictionary(context, locale, dictFile);
+ }
+
+ @Override
+ public boolean isValidWord(final String word) {
+ // Strings out of this dictionary should not be considered existing words.
+ return false;
+ }
+
+ @Override
+ protected void loadInitialContentsLocked() {
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index a3aae8cb3..4e4c8885c 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -64,7 +64,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
"pref_show_language_switch_key";
public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
"pref_include_other_imes_in_language_switch_list";
- public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
+ public static final String PREF_KEYBOARD_THEME = "pref_keyboard_theme";
public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
// TODO: consolidate key preview dismiss delay with the key preview animation parameters.
public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 22cbd204c..e1d38e7c4 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -37,6 +37,7 @@ import android.util.Log;
import android.view.inputmethod.InputMethodSubtype;
import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
+import com.android.inputmethod.keyboard.KeyboardTheme;
import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -253,11 +254,31 @@ public final class SettingsFragment extends InputMethodSettingsFragment
}
updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
- updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+ final ListPreference keyboardThemePref = (ListPreference)findPreference(
+ Settings.PREF_KEYBOARD_THEME);
+ if (keyboardThemePref != null) {
+ final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
+ final String value = Integer.toString(keyboardTheme.mThemeId);
+ final CharSequence entries[] = keyboardThemePref.getEntries();
+ final int entryIndex = keyboardThemePref.findIndexOfValue(value);
+ keyboardThemePref.setSummary(entryIndex < 0 ? null : entries[entryIndex]);
+ keyboardThemePref.setValue(value);
+ }
updateCustomInputStylesSummary(prefs, res);
}
@Override
+ public void onPause() {
+ super.onPause();
+ final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
+ final ListPreference keyboardThemePref = (ListPreference)findPreference(
+ Settings.PREF_KEYBOARD_THEME);
+ if (keyboardThemePref != null) {
+ KeyboardTheme.saveKeyboardThemeId(keyboardThemePref.getValue(), prefs);
+ }
+ }
+
+ @Override
public void onDestroy() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
this);
@@ -287,7 +308,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment
ensureConsistencyOfAutoCorrectionSettings();
updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
- updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_LAYOUT);
+ updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_THEME);
refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index dde50ccaf..de2eb951e 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -205,7 +205,8 @@ public final class SettingsValues {
}
public boolean isWordCodePoint(final int code) {
- return Character.isLetter(code) || isWordConnector(code);
+ return Character.isLetter(code) || isWordConnector(code)
+ || Character.COMBINING_SPACING_MARK == Character.getType(code);
}
public boolean isUsuallyPrecededBySpace(final int code) {
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 8bfa63c3c..810bda758 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -381,6 +381,7 @@ final class SuggestionStripLayoutHelper {
}
// Disable this suggestion if the suggestion is null or empty.
+ // TODO: Fix disabled {@link TextView}'s content description.
wordView.setEnabled(!TextUtils.isEmpty(word));
final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
final float scaleX = getTextScaleX(word, width, wordView.getPaint());
@@ -424,7 +425,9 @@ final class SuggestionStripLayoutHelper {
final int countInStrip) {
// Clear all suggestions first
for (int positionInStrip = 0; positionInStrip < countInStrip; ++positionInStrip) {
- mWordViews.get(positionInStrip).setText(null);
+ final TextView wordView = mWordViews.get(positionInStrip);
+ wordView.setText(null);
+ wordView.setTag(null);
// Make this inactive for touches in {@link #layoutWord(int,int)}.
if (SuggestionStripView.DBG) {
mDebugInfoViews.get(positionInStrip).setText(null);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index a578fa4a4..619804afa 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -18,7 +18,9 @@ package com.android.inputmethod.latin.suggestions;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -31,6 +33,7 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -59,12 +62,14 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
public void addWordToUserDictionary(String word);
public void showImportantNoticeContents();
public void pickSuggestionManually(int index, SuggestedWordInfo word);
+ public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
}
static final boolean DBG = LatinImeLogger.sDBG;
private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.0f;
private final ViewGroup mSuggestionsStrip;
+ private final ImageButton mVoiceKey;
private final ViewGroup mAddToDictionaryStrip;
private final View mImportantNoticeStrip;
MainKeyboardView mMainKeyboardView;
@@ -86,39 +91,42 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
private static class StripVisibilityGroup {
private final View mSuggestionsStrip;
+ private final View mVoiceKey;
private final View mAddToDictionaryStrip;
private final View mImportantNoticeStrip;
- public StripVisibilityGroup(final View suggestionsStrip, final View addToDictionaryStrip,
- final View importantNoticeStrip) {
+ public StripVisibilityGroup(final View suggestionsStrip, final View voiceKey,
+ final View addToDictionaryStrip, final View importantNoticeStrip) {
mSuggestionsStrip = suggestionsStrip;
+ mVoiceKey = voiceKey;
mAddToDictionaryStrip = addToDictionaryStrip;
mImportantNoticeStrip = importantNoticeStrip;
- showSuggestionsStrip();
+ showSuggestionsStrip(false /* voiceKeyEnabled */);
}
- public void setLayoutDirection(final boolean isRtlLanguage) {
- final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
- : ViewCompat.LAYOUT_DIRECTION_LTR;
+ public void setLayoutDirection(final int layoutDirection) {
ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection);
ViewCompat.setLayoutDirection(mAddToDictionaryStrip, layoutDirection);
ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection);
}
- public void showSuggestionsStrip() {
+ public void showSuggestionsStrip(final boolean enableVoiceKey) {
mSuggestionsStrip.setVisibility(VISIBLE);
+ mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE);
mAddToDictionaryStrip.setVisibility(INVISIBLE);
mImportantNoticeStrip.setVisibility(INVISIBLE);
}
public void showAddToDictionaryStrip() {
mSuggestionsStrip.setVisibility(INVISIBLE);
+ mVoiceKey.setVisibility(INVISIBLE);
mAddToDictionaryStrip.setVisibility(VISIBLE);
mImportantNoticeStrip.setVisibility(INVISIBLE);
}
- public void showImportantNoticeStrip() {
+ public void showImportantNoticeStrip(final boolean enableVoiceKey) {
mSuggestionsStrip.setVisibility(INVISIBLE);
+ mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE);
mAddToDictionaryStrip.setVisibility(INVISIBLE);
mImportantNoticeStrip.setVisibility(VISIBLE);
}
@@ -145,10 +153,11 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
inflater.inflate(R.layout.suggestions_strip, this);
mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
+ mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key);
mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip);
mImportantNoticeStrip = findViewById(R.id.important_notice_strip);
- mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mAddToDictionaryStrip,
- mImportantNoticeStrip);
+ mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mVoiceKey,
+ mAddToDictionaryStrip, mImportantNoticeStrip);
for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) {
final TextView word = new TextView(context, null, R.attr.suggestionWordStyle);
@@ -177,6 +186,13 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
R.dimen.config_more_suggestions_modal_tolerance);
mMoreSuggestionsSlidingDetector = new GestureDetector(
context, mMoreSuggestionsSlidingListener);
+
+ final TypedArray keyboardAttr = context.obtainStyledAttributes(attrs,
+ R.styleable.Keyboard, defStyle, R.style.SuggestionStripView);
+ final Drawable iconVoice = keyboardAttr.getDrawable(R.styleable.Keyboard_iconShortcutKey);
+ keyboardAttr.recycle();
+ mVoiceKey.setImageDrawable(iconVoice);
+ mVoiceKey.setOnClickListener(this);
}
/**
@@ -188,16 +204,30 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
}
+ private boolean isVoiceKeyEnabled() {
+ if (mMainKeyboardView == null) {
+ return false;
+ }
+ final Keyboard keyboard = mMainKeyboardView.getKeyboard();
+ if (keyboard == null) {
+ return false;
+ }
+ return keyboard.mId.mHasShortcutKey;
+ }
+
public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) {
clear();
- mStripVisibilityGroup.setLayoutDirection(isRtlLanguage);
+ final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
+ : ViewCompat.LAYOUT_DIRECTION_LTR;
+ setLayoutDirection(layoutDirection);
+ mStripVisibilityGroup.setLayoutDirection(layoutDirection);
mSuggestedWords = suggestedWords;
mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip(
mSuggestedWords, mSuggestionsStrip, this);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
}
- mStripVisibilityGroup.showSuggestionsStrip();
+ mStripVisibilityGroup.showSuggestionsStrip(isVoiceKeyEnabled());
}
public int setMoreSuggestionsHeight(final int remainingHeight) {
@@ -244,7 +274,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
dismissMoreSuggestionsPanel();
}
mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, importantNoticeTitle);
- mStripVisibilityGroup.showImportantNoticeStrip();
+ mStripVisibilityGroup.showImportantNoticeStrip(isVoiceKeyEnabled());
mImportantNoticeStrip.setOnClickListener(this);
return true;
}
@@ -252,7 +282,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
public void clear() {
mSuggestionsStrip.removeAllViews();
removeAllDebugInfoViews();
- mStripVisibilityGroup.showSuggestionsStrip();
+ mStripVisibilityGroup.showSuggestionsStrip(false /* enableVoiceKey */);
dismissMoreSuggestionsPanel();
}
@@ -415,6 +445,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mListener.showImportantNoticeContents();
return;
}
+ if (view == mVoiceKey) {
+ mListener.onCodeInput(Constants.CODE_SHORTCUT,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+ false /* isKeyRepeat */);
+ return;
+ }
final Object tag = view.getTag();
// {@link String} tag is set at {@link #showAddToDictionaryHint(String,CharSequence)}.
if (tag instanceof String) {
diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
index 48e43d6ef..55cbf79b3 100644
--- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
+++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java
@@ -17,21 +17,35 @@
package com.android.inputmethod.latin.utils;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Suggest;
+import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.WordComposer;
/**
- * This class is used to prevent distracters/misspellings being added to personalization
+ * This class is used to prevent distracters being added to personalization
* or user history dictionaries
*/
public class DistracterFilter {
private final Suggest mSuggest;
private final Keyboard mKeyboard;
+ // If the score of the top suggestion exceeds this value, the tested word (e.g.,
+ // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to
+ // words in dictionary. The greater the threshold is, the less likely the tested word would
+ // become a distracter, which means the tested word will be more likely to be added to
+ // the dictionary.
+ private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 2.0f;
+
/**
* Create a DistracterFilter instance.
*
* @param suggest an instance of Suggest which will be used to obtain a list of suggestions
- * for a potential distracter/misspelling
+ * for a potential distracter
* @param keyboard the keyboard that is currently being used. This information is needed
* when calling mSuggest.getSuggestedWords(...) to obtain a list of suggestions.
*/
@@ -40,9 +54,79 @@ public class DistracterFilter {
mKeyboard = keyboard;
}
- public boolean isDistracterToWordsInDictionaries(final String prevWord,
- final String targetWord) {
- // TODO: to be implemented
+ public static DistracterFilter createDistracterFilter(final Suggest suggest,
+ final KeyboardSwitcher keyboardSwitcher) {
+ final MainKeyboardView mainKeyboardView = keyboardSwitcher.getMainKeyboardView();
+ // TODO: Create Keyboard when mainKeyboardView is null.
+ // TODO: Figure out the most reasonable keyboard for the filter. Refer to the
+ // spellchecker's logic.
+ final Keyboard keyboard = (mainKeyboardView != null) ?
+ mainKeyboardView.getKeyboard() : null;
+ final DistracterFilter distracterFilter = new DistracterFilter(suggest, keyboard);
+ return distracterFilter;
+ }
+
+ private static boolean suggestionExceedsDistracterThreshold(
+ final SuggestedWordInfo suggestion, final String consideredWord,
+ final float distracterThreshold) {
+ if (null != suggestion) {
+ final int suggestionScore = suggestion.mScore;
+ final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore(
+ consideredWord, suggestion.mWord, suggestionScore);
+ if (normalizedScore > distracterThreshold) {
+ return true;
+ }
+ }
return false;
}
+
+ /**
+ * Determine whether a word is a distracter to words in dictionaries.
+ *
+ * @param prevWord the previous word, or null if none.
+ * @param testedWord the word that will be tested to see whether it is a distracter to words
+ * in dictionaries.
+ * @return true if testedWord is a distracter, otherwise false.
+ */
+ public boolean isDistracterToWordsInDictionaries(final String prevWord,
+ final String testedWord) {
+ if (mSuggest == null) {
+ return false;
+ }
+
+ final WordComposer composer = new WordComposer();
+ final int[] codePoints = StringUtils.toCodePointArray(testedWord);
+ final int[] coordinates;
+ if (null == mKeyboard) {
+ coordinates = CoordinateUtils.newCoordinateArray(codePoints.length,
+ Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+ } else {
+ coordinates = mKeyboard.getCoordinates(codePoints);
+ }
+ composer.setComposingWord(codePoints, coordinates, prevWord);
+
+ final int trailingSingleQuotesCount = composer.trailingSingleQuotesCount();
+ final String consideredWord = trailingSingleQuotesCount > 0 ? testedWord.substring(0,
+ testedWord.length() - trailingSingleQuotesCount) : testedWord;
+ final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>();
+ final OnGetSuggestedWordsCallback callback = new OnGetSuggestedWordsCallback() {
+ @Override
+ public void onGetSuggestedWords(final SuggestedWords suggestedWords) {
+ if (suggestedWords != null && suggestedWords.size() > 1) {
+ // The suggestedWordInfo at 0 is the typed word. The 1st suggestion from
+ // the decoder is at index 1.
+ final SuggestedWordInfo firstSuggestion = suggestedWords.getInfo(1);
+ final boolean hasStrongDistractor = suggestionExceedsDistracterThreshold(
+ firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD);
+ holder.set(hasStrongDistractor);
+ }
+ }
+ };
+ mSuggest.getSuggestedWords(composer, prevWord, mKeyboard.getProximityInfo(),
+ true /* blockOffensiveWords */, true /* isCorrectionEnbaled */,
+ null /* additionalFeaturesOptions */, 0 /* sessionId */,
+ SuggestedWords.NOT_A_SEQUENCE_NUMBER, callback);
+
+ return holder.get(false /* defaultValue */, Constants.GET_SUGGESTED_WORDS_TIMEOUT);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
index 55061f45f..74e7db901 100644
--- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
+++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java
@@ -129,6 +129,9 @@ public final class LanguageModelParam {
if (locale == null) {
return null;
}
+ // TODO: Though targetWord is an IV (in-vocabulary) word, we should still apply
+ // distracterFilter in the following code. If targetWord is a distracter,
+ // it should be filtered out.
if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) {
return createAndGetLanguageModelParamOfWord(prevWord, targetWord, timestamp,
true /* isValidWord */, locale);
diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index b37779bdc..938d27122 100644
--- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -324,4 +324,8 @@ public final class SubtypeLocaleUtils {
public static boolean isRtlLanguage(final InputMethodSubtype subtype) {
return isRtlLanguage(getSubtypeLocale(subtype));
}
+
+ public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) {
+ return subtype.getExtraValueOf(Constants.Subtype.ExtraValue.COMBINING_RULES);
+ }
}