aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rwxr-xr-xjava/src/com/android/inputmethod/latin/CandidateView.java9
-rw-r--r--java/src/com/android/inputmethod/latin/CandidateViewContainer.java83
-rw-r--r--java/src/com/android/inputmethod/latin/EditingUtil.java188
-rw-r--r--java/src/com/android/inputmethod/latin/KeyDetector.java113
-rw-r--r--java/src/com/android/inputmethod/latin/KeyboardSwitcher.java53
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java411
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIMESettings.java12
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIMEUtil.java3
-rw-r--r--java/src/com/android/inputmethod/latin/LatinImeLogger.java857
-rw-r--r--java/src/com/android/inputmethod/latin/LatinKeyboard.java180
-rw-r--r--java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java939
-rw-r--r--java/src/com/android/inputmethod/latin/LatinKeyboardView.java245
-rw-r--r--java/src/com/android/inputmethod/latin/ModifierKeyState.java42
-rw-r--r--java/src/com/android/inputmethod/latin/PointerTracker.java459
-rw-r--r--java/src/com/android/inputmethod/latin/ProximityKeyDetector.java49
-rw-r--r--java/src/com/android/inputmethod/voice/VoiceInput.java16
-rw-r--r--java/src/com/android/inputmethod/voice/VoiceInputLogger.java15
17 files changed, 1571 insertions, 2103 deletions
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 7fcc3d532..bd73c6fb8 100755
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -107,7 +107,6 @@ public class CandidateView extends View {
}
break;
}
-
}
};
@@ -238,6 +237,8 @@ public class CandidateView extends View {
final boolean typedWordValid = mTypedWordValid;
final int y = (int) (height + mPaint.getTextSize() - mDescent) / 2;
+ boolean existsAutoCompletion = false;
+
for (int i = 0; i < count; i++) {
CharSequence suggestion = mSuggestions.get(i);
if (suggestion == null) continue;
@@ -246,6 +247,7 @@ public class CandidateView extends View {
&& ((i == 1 && !typedWordValid) || (i == 0 && typedWordValid))) {
paint.setTypeface(Typeface.DEFAULT_BOLD);
paint.setColor(mColorRecommended);
+ existsAutoCompletion = true;
} else if (i != 0) {
paint.setColor(mColorOther);
}
@@ -286,6 +288,7 @@ public class CandidateView extends View {
paint.setTypeface(Typeface.DEFAULT);
x += wordWidth;
}
+ mService.onAutoCompletionStateChanged(existsAutoCompletion);
mTotalWidth = x;
if (mTargetScrollX != scrollX) {
scrollToTarget();
@@ -333,6 +336,10 @@ public class CandidateView extends View {
requestLayout();
}
+ public boolean isShowingAddToDictionaryHint() {
+ return mShowingAddToDictionary;
+ }
+
public void showAddToDictionaryHint(CharSequence word) {
ArrayList<CharSequence> suggestions = new ArrayList<CharSequence>();
suggestions.add(word);
diff --git a/java/src/com/android/inputmethod/latin/CandidateViewContainer.java b/java/src/com/android/inputmethod/latin/CandidateViewContainer.java
deleted file mode 100644
index e0cb8c3b0..000000000
--- a/java/src/com/android/inputmethod/latin/CandidateViewContainer.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.widget.LinearLayout;
-
-public class CandidateViewContainer extends LinearLayout implements OnTouchListener {
-
- private View mButtonLeft;
- private View mButtonRight;
- private View mButtonLeftLayout;
- private View mButtonRightLayout;
- private CandidateView mCandidates;
-
- public CandidateViewContainer(Context screen, AttributeSet attrs) {
- super(screen, attrs);
- }
-
- public void initViews() {
- if (mCandidates == null) {
- mButtonLeftLayout = findViewById(R.id.candidate_left_parent);
- mButtonLeft = findViewById(R.id.candidate_left);
- if (mButtonLeft != null) {
- mButtonLeft.setOnTouchListener(this);
- }
- mButtonRightLayout = findViewById(R.id.candidate_right_parent);
- mButtonRight = findViewById(R.id.candidate_right);
- if (mButtonRight != null) {
- mButtonRight.setOnTouchListener(this);
- }
- mCandidates = (CandidateView) findViewById(R.id.candidates);
- }
- }
-
- @Override
- public void requestLayout() {
- if (mCandidates != null) {
- int availableWidth = mCandidates.getWidth();
- int neededWidth = mCandidates.computeHorizontalScrollRange();
- int x = mCandidates.getScrollX();
- boolean leftVisible = x > 0;
- boolean rightVisible = x + availableWidth < neededWidth;
- if (mButtonLeftLayout != null) {
- mButtonLeftLayout.setVisibility(leftVisible ? VISIBLE : GONE);
- }
- if (mButtonRightLayout != null) {
- mButtonRightLayout.setVisibility(rightVisible ? VISIBLE : GONE);
- }
- }
- super.requestLayout();
- }
-
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- if (v == mButtonRight) {
- mCandidates.scrollNext();
- } else if (v == mButtonLeft) {
- mCandidates.scrollPrev();
- }
- }
- return false;
- }
-
-}
diff --git a/java/src/com/android/inputmethod/latin/EditingUtil.java b/java/src/com/android/inputmethod/latin/EditingUtil.java
index be31cb787..781d7fd4a 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtil.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtil.java
@@ -16,10 +16,13 @@
package com.android.inputmethod.latin;
+import android.text.TextUtils;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.regex.Pattern;
/**
@@ -31,6 +34,11 @@ public class EditingUtil {
*/
private static final int LOOKBACK_CHARACTER_NUM = 15;
+ // Cache Method pointers
+ private static boolean sMethodsInitialized;
+ private static Method sMethodGetSelectedText;
+ private static Method sMethodSetComposingRegion;
+
private EditingUtil() {};
/**
@@ -65,36 +73,16 @@ public class EditingUtil {
return extracted.startOffset + extracted.selectionStart;
}
- private static int getSelectionEnd(InputConnection connection) {
- ExtractedText extracted = connection.getExtractedText(
- new ExtractedTextRequest(), 0);
- if (extracted == null) {
- return -1;
- }
- return extracted.startOffset + extracted.selectionEnd;
- }
-
/**
* @param connection connection to the current text field.
* @param sep characters which may separate words
+ * @param range the range object to store the result into
* @return the word that surrounds the cursor, including up to one trailing
* separator. For example, if the field contains "he|llo world", where |
* represents the cursor, then "hello " will be returned.
*/
public static String getWordAtCursor(
- InputConnection connection, String separators) {
- return getWordAtCursor(connection, separators, null);
- }
-
- /**
- * @param connection connection to the current text field.
- * @param sep characters which may separate words
- * @return the word that surrounds the cursor, including up to one trailing
- * separator. For example, if the field contains "he|llo world", where |
- * represents the cursor, then "hello " will be returned.
- */
- public static String getWordAtCursor(
- InputConnection connection, String separators, Range range) {
+ InputConnection connection, String separators, Range range) {
Range r = getWordRangeAtCursor(connection, separators, range);
return (r == null) ? null : r.word;
}
@@ -204,26 +192,146 @@ public class EditingUtil {
}
}
+ public static class SelectedWord {
+ public int start;
+ public int end;
+ public CharSequence word;
+ }
+
/**
- * Checks if the cursor is touching/inside a word or the selection is for a whole
- * word and no more and no less.
- * @param range the Range object that contains the bounds of the word around the cursor
- * @param start the start of the selection
- * @param end the end of the selection, which could be the same as the start, if text is not
- * in selection mode
- * @return false if the selection is a partial word or straddling multiple words, true if
- * the selection is a full word or there is no selection.
+ * Takes a character sequence with a single character and checks if the character occurs
+ * in a list of word separators or is empty.
+ * @param singleChar A CharSequence with null, zero or one character
+ * @param wordSeparators A String containing the word separators
+ * @return true if the character is at a word boundary, false otherwise
*/
- public static boolean isFullWordOrInside(Range range, int start, int end) {
- // Is the cursor inside or touching a word?
- if (start == end) return true;
-
- // Is it a selection? Then is the start of the selection the start of the word and
- // the size of the selection the size of the word? Then return true
- if (start < end
- && (range.charsBefore == 0 && range.charsAfter == end - start)) {
- return true;
+ private static boolean isWordBoundary(CharSequence singleChar, String wordSeparators) {
+ return TextUtils.isEmpty(singleChar) || wordSeparators.contains(singleChar);
+ }
+
+ /**
+ * Checks if the cursor is inside a word or the current selection is a whole word.
+ * @param ic the InputConnection for accessing the text field
+ * @param selStart the start position of the selection within the text field
+ * @param selEnd the end position of the selection within the text field. This could be
+ * the same as selStart, if there's no selection.
+ * @param wordSeparators the word separator characters for the current language
+ * @return an object containing the text and coordinates of the selected/touching word,
+ * null if the selection/cursor is not marking a whole word.
+ */
+ public static SelectedWord getWordAtCursorOrSelection(final InputConnection ic,
+ int selStart, int selEnd, String wordSeparators) {
+ if (selStart == selEnd) {
+ // There is just a cursor, so get the word at the cursor
+ EditingUtil.Range range = new EditingUtil.Range();
+ CharSequence touching = getWordAtCursor(ic, wordSeparators, range);
+ if (!TextUtils.isEmpty(touching)) {
+ SelectedWord selWord = new SelectedWord();
+ selWord.word = touching;
+ selWord.start = selStart - range.charsBefore;
+ selWord.end = selEnd + range.charsAfter;
+ return selWord;
+ }
+ } else {
+ // Is the previous character empty or a word separator? If not, return null.
+ CharSequence charsBefore = ic.getTextBeforeCursor(1, 0);
+ if (!isWordBoundary(charsBefore, wordSeparators)) {
+ return null;
+ }
+
+ // Is the next character empty or a word separator? If not, return null.
+ CharSequence charsAfter = ic.getTextAfterCursor(1, 0);
+ if (!isWordBoundary(charsAfter, wordSeparators)) {
+ return null;
+ }
+
+ // Extract the selection alone
+ CharSequence touching = getSelectedText(ic, selStart, selEnd);
+ if (TextUtils.isEmpty(touching)) return null;
+ // Is any part of the selection a separator? If so, return null.
+ final int length = touching.length();
+ for (int i = 0; i < length; i++) {
+ if (wordSeparators.contains(touching.subSequence(i, i + 1))) {
+ return null;
+ }
+ }
+ // Prepare the selected word
+ SelectedWord selWord = new SelectedWord();
+ selWord.start = selStart;
+ selWord.end = selEnd;
+ selWord.word = touching;
+ return selWord;
+ }
+ return null;
+ }
+
+ /**
+ * Cache method pointers for performance
+ */
+ private static void initializeMethodsForReflection() {
+ try {
+ // These will either both exist or not, so no need for separate try/catch blocks.
+ // If other methods are added later, use separate try/catch blocks.
+ sMethodGetSelectedText = InputConnection.class.getMethod("getSelectedText", int.class);
+ sMethodSetComposingRegion = InputConnection.class.getMethod("setComposingRegion",
+ int.class, int.class);
+ } catch (NoSuchMethodException exc) {
+ // Ignore
+ }
+ sMethodsInitialized = true;
+ }
+
+ /**
+ * Returns the selected text between the selStart and selEnd positions.
+ */
+ private static CharSequence getSelectedText(InputConnection ic, int selStart, int selEnd) {
+ // Use reflection, for backward compatibility
+ CharSequence result = null;
+ if (!sMethodsInitialized) {
+ initializeMethodsForReflection();
+ }
+ if (sMethodGetSelectedText != null) {
+ try {
+ result = (CharSequence) sMethodGetSelectedText.invoke(ic, 0);
+ return result;
+ } catch (InvocationTargetException exc) {
+ // Ignore
+ } catch (IllegalArgumentException e) {
+ // Ignore
+ } catch (IllegalAccessException e) {
+ // Ignore
+ }
+ }
+ // Reflection didn't work, try it the poor way, by moving the cursor to the start,
+ // getting the text after the cursor and moving the text back to selected mode.
+ // TODO: Verify that this works properly in conjunction with
+ // LatinIME#onUpdateSelection
+ ic.setSelection(selStart, selEnd);
+ result = ic.getTextAfterCursor(selEnd - selStart, 0);
+ ic.setSelection(selStart, selEnd);
+ return result;
+ }
+
+ /**
+ * Tries to set the text into composition mode if there is support for it in the framework.
+ */
+ public static void underlineWord(InputConnection ic, SelectedWord word) {
+ // Use reflection, for backward compatibility
+ // If method not found, there's nothing we can do. It still works but just wont underline
+ // the word.
+ if (!sMethodsInitialized) {
+ initializeMethodsForReflection();
+ }
+ if (sMethodSetComposingRegion != null) {
+ try {
+ sMethodSetComposingRegion.invoke(ic, word.start, word.end);
+ } catch (InvocationTargetException exc) {
+ // Ignore
+ } catch (IllegalArgumentException e) {
+ // Ignore
+ } catch (IllegalAccessException e) {
+ // Ignore
+ }
}
- return false;
}
}
diff --git a/java/src/com/android/inputmethod/latin/KeyDetector.java b/java/src/com/android/inputmethod/latin/KeyDetector.java
new file mode 100644
index 000000000..76fe1200e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/KeyDetector.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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;
+
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.Keyboard.Key;
+
+import java.util.Arrays;
+import java.util.List;
+
+abstract class KeyDetector {
+ protected Keyboard mKeyboard;
+
+ private Key[] mKeys;
+
+ protected int mCorrectionX;
+
+ protected int mCorrectionY;
+
+ protected boolean mProximityCorrectOn;
+
+ protected int mProximityThresholdSquare;
+
+ public Key[] setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
+ if (keyboard == null)
+ throw new NullPointerException();
+ mCorrectionX = (int)correctionX;
+ mCorrectionY = (int)correctionY;
+ mKeyboard = keyboard;
+ List<Key> keys = mKeyboard.getKeys();
+ Key[] array = keys.toArray(new Key[keys.size()]);
+ mKeys = array;
+ return array;
+ }
+
+ protected int getTouchX(int x) {
+ return x + mCorrectionX;
+ }
+
+ protected int getTouchY(int y) {
+ return y + mCorrectionY;
+ }
+
+ protected Key[] getKeys() {
+ if (mKeys == null)
+ throw new IllegalStateException("keyboard isn't set");
+ // mKeyboard is guaranteed not to be null at setKeybaord() method if mKeys is not null
+ return mKeys;
+ }
+
+ public void setProximityCorrectionEnabled(boolean enabled) {
+ mProximityCorrectOn = enabled;
+ }
+
+ public boolean isProximityCorrectionEnabled() {
+ return mProximityCorrectOn;
+ }
+
+ public void setProximityThreshold(int threshold) {
+ mProximityThresholdSquare = threshold * threshold;
+ }
+
+ /**
+ * Allocates array that can hold all key indices returned by {@link #getKeyIndexAndNearbyCodes}
+ * method. The maximum size of the array should be computed by {@link #getMaxNearbyKeys}.
+ *
+ * @return Allocates and returns an array that can hold all key indices returned by
+ * {@link #getKeyIndexAndNearbyCodes} method. All elements in the returned array are
+ * initialized by {@link com.android.inputmethod.latin.LatinKeyboardView.NOT_A_KEY}
+ * value.
+ */
+ public int[] newCodeArray() {
+ int[] codes = new int[getMaxNearbyKeys()];
+ Arrays.fill(codes, LatinKeyboardBaseView.NOT_A_KEY);
+ return codes;
+ }
+
+ /**
+ * Computes maximum size of the array that can contain all nearby key indices returned by
+ * {@link #getKeyIndexAndNearbyCodes}.
+ *
+ * @return Returns maximum size of the array that can contain all nearby key indices returned
+ * by {@link #getKeyIndexAndNearbyCodes}.
+ */
+ abstract protected int getMaxNearbyKeys();
+
+ /**
+ * Finds all possible nearby key indices around a touch event point and returns the nearest key
+ * index. The algorithm to determine the nearby keys depends on the threshold set by
+ * {@link #setProximityThreshold(int)} and the mode set by
+ * {@link #setProximityCorrectionEnabled(boolean)}.
+ *
+ * @param x The x-coordinate of a touch point
+ * @param y The y-coordinate of a touch point
+ * @param allKeys All nearby key indices are returned in this array
+ * @return The nearest key index
+ */
+ abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys);
+}
diff --git a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
index dec29b7cb..2919e9b56 100644
--- a/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/KeyboardSwitcher.java
@@ -37,29 +37,24 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
public static final int MODE_EMAIL = 5;
public static final int MODE_IM = 6;
public static final int MODE_WEB = 7;
-
- public static final int MODE_TEXT_QWERTY = 0;
- public static final int MODE_TEXT_ALPHA = 1;
- public static final int MODE_TEXT_COUNT = 2;
-
+
public static final int KEYBOARDMODE_NORMAL = R.id.mode_normal;
public static final int KEYBOARDMODE_URL = R.id.mode_url;
public static final int KEYBOARDMODE_EMAIL = R.id.mode_email;
public static final int KEYBOARDMODE_IM = R.id.mode_im;
public static final int KEYBOARDMODE_WEB = R.id.mode_webentry;
- public static final String DEFAULT_LAYOUT_ID = "3";
- public static final String PREF_KEYBOARD_LAYOUT = "keyboard_layout";
+ public static final String DEFAULT_LAYOUT_ID = "4";
+ public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20100902";
private static final int[] THEMES = new int [] {
R.layout.input_basic, R.layout.input_basic_highcontrast, R.layout.input_stone_normal,
- R.layout.input_stone_bold};
+ R.layout.input_stone_bold, R.layout.input_gingerbread};
// Ids for each characters' color in the keyboard
private static final int CHAR_THEME_COLOR_WHITE = 0;
private static final int CHAR_THEME_COLOR_BLACK = 1;
// Tables which contains resource ids for each character theme color
- private static final int[] KBD_ALPHA = new int[] {R.xml.kbd_alpha, R.xml.kbd_alpha_black};
private static final int[] KBD_PHONE = new int[] {R.xml.kbd_phone, R.xml.kbd_phone_black};
private static final int[] KBD_PHONE_SYMBOLS = new int[] {
R.xml.kbd_phone_symbols, R.xml.kbd_phone_symbols_black};
@@ -83,7 +78,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
Context mContext;
LatinIME mInputMethodService;
-
+
private KeyboardId mSymbolsId;
private KeyboardId mSymbolsShiftedId;
@@ -92,8 +87,10 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private int mMode = MODE_NONE; /** One of the MODE_XXX values */
private int mImeOptions;
- private int mTextMode = MODE_TEXT_QWERTY;
private boolean mIsSymbols;
+ /** mIsAutoCompletionActive indicates that auto completed word will be input instead of
+ * what user actually typed. */
+ private boolean mIsAutoCompletionActive;
private boolean mHasVoice;
private boolean mVoiceOnPrimary;
private boolean mPreferSymbols;
@@ -245,7 +242,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
keyboard.setShifted(false);
keyboard.setShiftLocked(keyboard.isShiftLocked());
keyboard.setImeOptions(mContext.getResources(), mMode, imeOptions);
- keyboard.setBlackFlag(isBlackSym());
+ keyboard.setColorOfSymbolIcons(mIsAutoCompletionActive, isBlackSym());
}
private LatinKeyboard getKeyboard(KeyboardId id) {
@@ -255,20 +252,10 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
Locale saveLocale = conf.locale;
conf.locale = mInputLocale;
orig.updateConfiguration(conf, null);
- LatinKeyboard keyboard = new LatinKeyboard(
- mContext, id.mXml, id.mKeyboardMode);
+ LatinKeyboard keyboard = new LatinKeyboard(mContext, id.mXml, id.mKeyboardMode);
keyboard.setVoiceMode(hasVoiceButton(id.mXml == R.xml.kbd_symbols
|| id.mXml == R.xml.kbd_symbols_black), mHasVoice);
- keyboard.setLanguageSwitcher(mLanguageSwitcher);
- keyboard.setBlackFlag(isBlackSym());
- if (id.mKeyboardMode == KEYBOARDMODE_NORMAL
- || id.mKeyboardMode == KEYBOARDMODE_URL
- || id.mKeyboardMode == KEYBOARDMODE_IM
- || id.mKeyboardMode == KEYBOARDMODE_EMAIL
- || id.mKeyboardMode == KEYBOARDMODE_WEB
- ) {
- keyboard.setExtension(R.xml.kbd_extension);
- }
+ keyboard.setLanguageSwitcher(mLanguageSwitcher, mIsAutoCompletionActive, isBlackSym());
if (id.mEnableShiftLock) {
keyboard.enableShiftLock();
@@ -299,11 +286,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
"getKeyboardId:" + mode + "," + imeOptions + "," + isSymbols);
/* fall through */
case MODE_TEXT:
- if (mTextMode == MODE_TEXT_ALPHA) {
- return new KeyboardId(
- KBD_ALPHA[charColorId], KEYBOARDMODE_NORMAL, true, hasVoice);
- }
- // Normally mTextMode should be MODE_TEXT_QWERTY.
return new KeyboardId(keyboardRowsResId, KEYBOARDMODE_NORMAL, true, hasVoice);
case MODE_SYMBOLS:
return new KeyboardId(KBD_SYMBOLS[charColorId], hasVoice);
@@ -328,10 +310,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
boolean isTextMode() {
return mMode == MODE_TEXT;
}
-
- int getTextModeCount() {
- return MODE_TEXT_COUNT;
- }
boolean isAlphabetMode() {
if (mCurrentId == null) {
@@ -439,7 +417,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
mLayoutId + "," + newLayout, e);
}
}
- mInputView.setExtentionLayoutResId(THEMES[newLayout]);
mInputView.setOnKeyboardActionListener(mInputMethodService);
mLayoutId = newLayout;
}
@@ -474,4 +451,12 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
}
}
+ public void onAutoCompletionStateChanged(boolean isAutoCompletion) {
+ if (isAutoCompletion != mIsAutoCompletionActive) {
+ LatinKeyboardView keyboardView = getInputView();
+ mIsAutoCompletionActive = isAutoCompletion;
+ keyboardView.invalidateKey(((LatinKeyboard) keyboardView.getKeyboard())
+ .onAutoCompletionStateChanged(isAutoCompletion));
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 763e5e53c..e4776f888 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -61,6 +61,7 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.widget.LinearLayout;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -85,7 +86,6 @@ public class LatinIME extends InputMethodService
static final boolean TRACE = false;
static final boolean VOICE_INSTALLED = true;
static final boolean ENABLE_VOICE_BUTTON = true;
- private static final boolean MODIFY_TEXT_FOR_CORRECTION = false;
private static final String PREF_VIBRATE_ON = "vibrate_on";
private static final String PREF_SOUND_ON = "sound_on";
@@ -153,7 +153,7 @@ public class LatinIME extends InputMethodService
private static final int POS_METHOD = 1;
//private LatinKeyboardView mInputView;
- private CandidateViewContainer mCandidateViewContainer;
+ private LinearLayout mCandidateViewContainer;
private CandidateView mCandidateView;
private Suggest mSuggest;
private CompletionInfo[] mCompletions;
@@ -226,6 +226,9 @@ public class LatinIME extends InputMethodService
private int mDeleteCount;
private long mLastKeyTime;
+ // Shift modifier key state
+ private ModifierKeyState mShiftKeyState = new ModifierKeyState();
+
private Tutorial mTutorial;
private AudioManager mAudioManager;
@@ -537,9 +540,8 @@ public class LatinIME extends InputMethodService
@Override
public View onCreateCandidatesView() {
mKeyboardSwitcher.makeKeyboards(true);
- mCandidateViewContainer = (CandidateViewContainer) getLayoutInflater().inflate(
+ mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate(
R.layout.candidates, null);
- mCandidateViewContainer.initViews();
mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
mCandidateView.setService(this);
setCandidatesViewShown(true);
@@ -646,16 +648,14 @@ public class LatinIME extends InputMethodService
(attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
mInputTypeNoAutoCorrect = true;
}
- if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
+ if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
mPredictionOn = false;
- mCompletionOn = true && isFullscreenMode();
+ mCompletionOn = isFullscreenMode();
}
- updateShiftKeyState(attribute);
break;
default:
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
attribute.imeOptions, enableVoiceButton);
- updateShiftKeyState(attribute);
}
inputView.closing();
mComposing.setLength(0);
@@ -665,8 +665,9 @@ public class LatinIME extends InputMethodService
loadSettings();
updateShiftKeyState(attribute);
- setCandidatesViewShown(false);
- setSuggestions(null, false, false, false);
+ setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn,
+ false /* needsInputViewShown */ );
+ updateSuggestions();
// If the dictionary is not big enough, don't auto correct
mHasDictionary = mSuggest.hasMainDictionary();
@@ -685,6 +686,7 @@ public class LatinIME extends InputMethodService
super.onFinishInput();
LatinImeLogger.commit();
+ onAutoCompletionStateChanged(false);
if (VOICE_INSTALLED && !mConfigurationChanging) {
if (mAfterVoiceInput) {
@@ -767,27 +769,29 @@ public class LatinIME extends InputMethodService
mLastSelectionEnd = newSelEnd;
- // Check if we should go in or out of correction mode.
- // TODO: Uncomment this block when we enable re-editing feature
- // If a word is selected
- /*
- if (isPredictionOn() && mJustRevertedSeparator == null
- && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
- || TextEntryState.isCorrecting())
- && (newSelStart < newSelEnd - 1 || (!mPredicting))
- && !mVoiceInputHighlighted) {
- if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
- postUpdateOldSuggestions();
- } else {
- abortCorrection(false);
+ // Don't look for corrections if the keyboard is not visible
+ if (mKeyboardSwitcher != null && mKeyboardSwitcher.getInputView() != null
+ && mKeyboardSwitcher.getInputView().isShown()) {
+ // Check if we should go in or out of correction mode.
+ if (isPredictionOn()
+ && mJustRevertedSeparator == null
+ && (candidatesStart == candidatesEnd || newSelStart != oldSelStart
+ || TextEntryState.isCorrecting())
+ && (newSelStart < newSelEnd - 1 || (!mPredicting))
+ && !mVoiceInputHighlighted) {
+ if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
+ postUpdateOldSuggestions();
+ } else {
+ abortCorrection(false);
+ }
}
}
- */
}
@Override
public void hideWindow() {
LatinImeLogger.commit();
+ onAutoCompletionStateChanged(false);
if (TRACE) Debug.stopMethodTracing();
if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
@@ -822,7 +826,7 @@ public class LatinIME extends InputMethodService
if (mCompletionOn) {
mCompletions = completions;
if (completions == null) {
- setSuggestions(null, false, false, false);
+ clearSuggestions();
return;
}
@@ -834,21 +838,24 @@ public class LatinIME extends InputMethodService
// When in fullscreen mode, show completions generated by the application
setSuggestions(stringList, true, true, true);
mBestWord = null;
- setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
+ setCandidatesViewShown(true);
}
}
- @Override
- public void setCandidatesViewShown(boolean shown) {
+ private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
// TODO: Remove this if we support candidates with hard keyboard
if (onEvaluateInputViewShown()) {
- // Show the candidates view only if input view is showing
super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null
- && mKeyboardSwitcher.getInputView().isShown());
+ && (needsInputViewShown ? mKeyboardSwitcher.getInputView().isShown() : true));
}
}
@Override
+ public void setCandidatesViewShown(boolean shown) {
+ setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ );
+ }
+
+ @Override
public void onComputeInsets(InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
if (!isFullscreenMode()) {
@@ -966,13 +973,15 @@ public class LatinIME extends InputMethodService
private void postUpdateShiftKeyState() {
mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
+ // TODO: Should remove this 300ms delay?
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SHIFT_STATE), 300);
}
public void updateShiftKeyState(EditorInfo attr) {
InputConnection ic = getCurrentInputConnection();
if (ic != null && attr != null && mKeyboardSwitcher.isAlphabetMode()) {
- mKeyboardSwitcher.setShifted(mCapsLock || getCursorCapsMode(ic, attr) != 0);
+ mKeyboardSwitcher.setShifted(mShiftKeyState.isMomentary() || mCapsLock
+ || getCursorCapsMode(ic, attr) != 0);
}
}
@@ -1075,6 +1084,40 @@ public class LatinIME extends InputMethodService
}
}
+ private boolean hasMultipleEnabledIMEs() {
+ return ((InputMethodManager) getSystemService(
+ INPUT_METHOD_SERVICE)).getEnabledInputMethodList().size() > 1;
+ }
+
+ private void showInputMethodPicker() {
+ ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
+ .showInputMethodPicker();
+ }
+
+ private void onOptionKeyPressed() {
+ if (!isShowingOptionDialog()) {
+ if (hasMultipleEnabledIMEs()) {
+ showOptionsMenu();
+ } else {
+ launchSettings();
+ }
+ }
+ }
+
+ private void onOptionKeyLongPressed() {
+ if (!isShowingOptionDialog()) {
+ if (hasMultipleEnabledIMEs()) {
+ showInputMethodPicker();
+ } else {
+ launchSettings();
+ }
+ }
+ }
+
+ private boolean isShowingOptionDialog() {
+ return mOptionsDialog != null && mOptionsDialog.isShowing();
+ }
+
// Implementation of KeyboardViewListener
public void onKey(int primaryCode, int[] keyCodes, int x, int y) {
@@ -1091,15 +1134,18 @@ public class LatinIME extends InputMethodService
LatinImeLogger.logOnDelete();
break;
case Keyboard.KEYCODE_SHIFT:
- handleShift();
+ // Shift key is handled in onPress().
break;
case Keyboard.KEYCODE_CANCEL:
- if (mOptionsDialog == null || !mOptionsDialog.isShowing()) {
+ if (!isShowingOptionDialog()) {
handleClose();
}
break;
case LatinKeyboardView.KEYCODE_OPTIONS:
- showOptionsMenu();
+ onOptionKeyPressed();
+ break;
+ case LatinKeyboardView.KEYCODE_OPTIONS_LONGPRESS:
+ onOptionKeyLongPressed();
break;
case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE:
toggleLanguage(false, true);
@@ -1107,10 +1153,8 @@ public class LatinIME extends InputMethodService
case LatinKeyboardView.KEYCODE_PREV_LANGUAGE:
toggleLanguage(false, false);
break;
- case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS:
- handleCapsLock();
- break;
case Keyboard.KEYCODE_MODE_CHANGE:
+ // TODO: Mode change (symbol key) should be handled in onPress().
changeKeyboardMode();
break;
case LatinKeyboardView.KEYCODE_VOICE:
@@ -1231,12 +1275,20 @@ public class LatinIME extends InputMethodService
ic.endBatchEdit();
}
+ private void resetShift() {
+ handleShiftInternal(true);
+ }
+
private void handleShift() {
+ handleShiftInternal(false);
+ }
+
+ private void handleShiftInternal(boolean forceNormal) {
mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
KeyboardSwitcher switcher = mKeyboardSwitcher;
LatinKeyboardView inputView = switcher.getInputView();
if (switcher.isAlphabetMode()) {
- if (mCapsLock) {
+ if (mCapsLock || forceNormal) {
mCapsLock = false;
switcher.setShifted(false);
} else if (inputView != null) {
@@ -1252,23 +1304,10 @@ public class LatinIME extends InputMethodService
}
}
- private void handleCapsLock() {
- mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
- KeyboardSwitcher switcher = mKeyboardSwitcher;
- if (switcher.isAlphabetMode()) {
- mCapsLock = !mCapsLock;
- if (mCapsLock) {
- switcher.setShiftLocked(true);
- } else {
- switcher.setShifted(false);
- }
- }
- }
-
private void abortCorrection(boolean force) {
if (force || TextEntryState.isCorrecting()) {
getCurrentInputConnection().finishComposingText();
- setSuggestions(null, false, false, false);
+ clearSuggestions();
}
}
@@ -1281,7 +1320,9 @@ public class LatinIME extends InputMethodService
// Assume input length is 1. This assumption fails for smiley face insertions.
mVoiceInput.incrementTextModificationInsertCount(1);
}
- abortCorrection(false);
+ if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
+ abortCorrection(false);
+ }
if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
if (!mPredicting) {
@@ -1436,8 +1477,7 @@ public class LatinIME extends InputMethodService
}
private boolean isPredictionOn() {
- boolean predictionOn = mPredictionOn;
- return predictionOn;
+ return mPredictionOn;
}
private boolean isCandidateStripVisible() {
@@ -1511,7 +1551,7 @@ public class LatinIME extends InputMethodService
}
// Clear N-best suggestions
- setSuggestions(null, false, false, true);
+ clearSuggestions();
FieldContext context = new FieldContext(
getCurrentInputConnection(),
@@ -1618,13 +1658,15 @@ public class LatinIME extends InputMethodService
mVoiceInputHighlighted = true;
mWordToSuggestions.putAll(mVoiceResults.alternatives);
+ }
+ private void clearSuggestions() {
+ setSuggestions(null, false, false, false);
}
private void setSuggestions(
List<CharSequence> suggestions,
boolean completions,
-
boolean typedWordValid,
boolean haveMinimalSuggestion) {
@@ -1668,14 +1710,14 @@ public class LatinIME extends InputMethodService
}
private void showSuggestions(WordComposer word) {
- //long startTime = System.currentTimeMillis(); // TIME MEASUREMENT!
+ // long startTime = System.currentTimeMillis(); // TIME MEASUREMENT!
// TODO Maybe need better way of retrieving previous word
CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(),
mWordSeparators);
List<CharSequence> stringList = mSuggest.getSuggestions(
- mKeyboardSwitcher.getInputView(), word, false, prevWord);
- //long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT!
- //Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime));
+ mKeyboardSwitcher.getInputView(), word, false, prevWord);
+ // long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT!
+ // Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime));
int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
@@ -1734,13 +1776,13 @@ public class LatinIME extends InputMethodService
}
public void pickSuggestionManually(int index, CharSequence suggestion) {
- if (mAfterVoiceInput && mShowingVoiceSuggestions) mVoiceInput.logNBestChoose(index);
List<CharSequence> suggestions = mCandidateView.getSuggestions();
-
- if (mAfterVoiceInput && !mShowingVoiceSuggestions) {
+ if (mAfterVoiceInput && mShowingVoiceSuggestions) {
mVoiceInput.flushAllTextModificationCounters();
// send this intent AFTER logging any prior aggregated edits.
- mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.length());
+ mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
+ mWordSeparators,
+ getCurrentInputConnection());
}
final boolean correcting = TextEntryState.isCorrecting();
@@ -1796,18 +1838,23 @@ public class LatinIME extends InputMethodService
mJustAddedAutoSpace = true;
}
- // Fool the state watcher so that a subsequent backspace will not do a revert, unless
- // we just did a correction, in which case we need to stay in
- // TextEntryState.State.PICKED_SUGGESTION state.
+ final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0
+ && !mSuggest.isValidWord(suggestion)
+ && !mSuggest.isValidWord(suggestion.toString().toLowerCase());
+
if (!correcting) {
+ // Fool the state watcher so that a subsequent backspace will not do a revert, unless
+ // we just did a correction, in which case we need to stay in
+ // TextEntryState.State.PICKED_SUGGESTION state.
TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
setNextSuggestions();
- } else {
+ } else if (!showingAddToDictionaryHint) {
+ // If we're not showing the "Tap again to save hint", then show corrections again.
// In case the cursor position doesn't change, make sure we show the suggestions again.
+ clearSuggestions();
postUpdateOldSuggestions();
}
- if (index == 0 && mCorrectionMode > 0 && !mSuggest.isValidWord(suggestion)
- && !mSuggest.isValidWord(suggestion.toString().toLowerCase())) {
+ if (showingAddToDictionaryHint) {
mCandidateView.showAddToDictionaryHint(suggestion);
}
if (ic != null) {
@@ -1857,16 +1904,6 @@ public class LatinIME extends InputMethodService
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
rememberReplacedWord(suggestion);
- // If text is in correction mode and we're not using composing
- // text to underline, then the word at the cursor position needs
- // to be removed before committing the correction
- if (correcting && !MODIFY_TEXT_FOR_CORRECTION) {
- if (mLastSelectionStart < mLastSelectionEnd) {
- ic.setSelection(mLastSelectionStart, mLastSelectionStart);
- }
- EditingUtil.deleteWordAtCursor(ic, getWordSeparators());
- }
-
ic.commitText(suggestion, 1);
}
saveWordInHistory(suggestion);
@@ -1880,96 +1917,108 @@ public class LatinIME extends InputMethodService
updateShiftKeyState(getCurrentInputEditorInfo());
}
+ /**
+ * Tries to apply any voice alternatives for the word if this was a spoken word and
+ * there are voice alternatives.
+ * @param touching The word that the cursor is touching, with position information
+ * @return true if an alternative was found, false otherwise.
+ */
+ private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
+ // Search for result in spoken word alternatives
+ String selectedWord = touching.word.toString().trim();
+ if (!mWordToSuggestions.containsKey(selectedWord)) {
+ selectedWord = selectedWord.toLowerCase();
+ }
+ if (mWordToSuggestions.containsKey(selectedWord)) {
+ mShowingVoiceSuggestions = true;
+ List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
+ // If the first letter of touching is capitalized, make all the suggestions
+ // start with a capital letter.
+ if (Character.isUpperCase((char) touching.word.charAt(0))) {
+ for (int i = 0; i < suggestions.size(); i++) {
+ String origSugg = (String) suggestions.get(i);
+ String capsSugg = origSugg.toUpperCase().charAt(0)
+ + origSugg.subSequence(1, origSugg.length()).toString();
+ suggestions.set(i, capsSugg);
+ }
+ }
+ setSuggestions(suggestions, false, true, true);
+ setCandidatesViewShown(true);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Tries to apply any typed alternatives for the word if we have any cached alternatives,
+ * otherwise tries to find new corrections and completions for the word.
+ * @param touching The word that the cursor is touching, with position information
+ * @return true if an alternative was found, false otherwise.
+ */
+ private boolean applyTypedAlternatives(EditingUtil.SelectedWord touching) {
+ // If we didn't find a match, search for result in typed word history
+ WordComposer foundWord = null;
+ WordAlternatives alternatives = null;
+ for (WordAlternatives entry : mWordHistory) {
+ if (TextUtils.equals(entry.getChosenWord(), touching.word)) {
+ if (entry instanceof TypedWordAlternatives) {
+ foundWord = ((TypedWordAlternatives) entry).word;
+ }
+ alternatives = entry;
+ break;
+ }
+ }
+ // If we didn't find a match, at least suggest completions
+ if (foundWord == null
+ && (mSuggest.isValidWord(touching.word)
+ || mSuggest.isValidWord(touching.word.toString().toLowerCase()))) {
+ foundWord = new WordComposer();
+ for (int i = 0; i < touching.word.length(); i++) {
+ foundWord.add(touching.word.charAt(i), new int[] {
+ touching.word.charAt(i)
+ });
+ }
+ foundWord.setCapitalized(Character.isUpperCase(touching.word.charAt(0)));
+ }
+ // Found a match, show suggestions
+ if (foundWord != null || alternatives != null) {
+ if (alternatives == null) {
+ alternatives = new TypedWordAlternatives(touching.word, foundWord);
+ }
+ showCorrections(alternatives);
+ if (foundWord != null) {
+ mWord = new WordComposer(foundWord);
+ } else {
+ mWord.reset();
+ }
+ return true;
+ }
+ return false;
+ }
+
private void setOldSuggestions() {
- // TODO: Inefficient to check if touching word and then get the touching word. Do it
- // in one go.
mShowingVoiceSuggestions = false;
+ if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
+ return;
+ }
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
- ic.beginBatchEdit();
- // If there is a selection, then undo the selection first. Unfortunately this causes
- // a flicker. TODO: Add getSelectionText() to InputConnection API.
- if (mLastSelectionStart < mLastSelectionEnd) {
- ic.setSelection(mLastSelectionStart, mLastSelectionStart);
- }
- if (!mPredicting && isCursorTouchingWord()) {
- EditingUtil.Range range = new EditingUtil.Range();
- CharSequence touching = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
- mWordSeparators, range);
- // If it's a selection, check if it's an entire word and no more, no less.
- boolean fullword = EditingUtil.isFullWordOrInside(range, mLastSelectionStart,
- mLastSelectionEnd);
- if (fullword && touching != null && touching.length() > 1) {
- // Strip out any trailing word separator
- if (mWordSeparators.indexOf(touching.charAt(touching.length() - 1)) > 0) {
- touching = touching.toString().substring(0, touching.length() - 1);
- }
+ if (!mPredicting) {
+ // Extract the selected or touching text
+ EditingUtil.SelectedWord touching = EditingUtil.getWordAtCursorOrSelection(ic,
+ mLastSelectionStart, mLastSelectionEnd, mWordSeparators);
- // Search for result in spoken word alternatives
- String selectedWord = touching.toString().trim();
- if (!mWordToSuggestions.containsKey(selectedWord)){
- selectedWord = selectedWord.toLowerCase();
- }
- if (mWordToSuggestions.containsKey(selectedWord)){
- mShowingVoiceSuggestions = true;
- underlineWord(touching, range.charsBefore, range.charsAfter);
- List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
- // If the first letter of touching is capitalized, make all the suggestions
- // start with a capital letter.
- if (Character.isUpperCase((char) touching.charAt(0))) {
- for (int i=0; i< suggestions.size(); i++) {
- String origSugg = (String) suggestions.get(i);
- String capsSugg = origSugg.toUpperCase().charAt(0)
- + origSugg.subSequence(1, origSugg.length()).toString();
- suggestions.set(i,capsSugg);
- }
- }
- setSuggestions(suggestions, false, true, true);
- setCandidatesViewShown(true);
- TextEntryState.selectedForCorrection();
- ic.endBatchEdit();
- return;
- }
+ if (touching != null && touching.word.length() > 1) {
+ ic.beginBatchEdit();
- // If we didn't find a match, search for result in typed word history
- WordComposer foundWord = null;
- WordAlternatives alternatives = null;
- for (WordAlternatives entry : mWordHistory) {
- if (TextUtils.equals(entry.getChosenWord(), touching)) {
- if (entry instanceof TypedWordAlternatives) {
- foundWord = ((TypedWordAlternatives)entry).word;
- }
- alternatives = entry;
- break;
- }
- }
- // If we didn't find a match, at least suggest completions
- if (foundWord == null && mSuggest.isValidWord(touching)) {
- foundWord = new WordComposer();
- for (int i = 0; i < touching.length(); i++) {
- foundWord.add(touching.charAt(i), new int[] { touching.charAt(i) });
- }
- }
- // Found a match, show suggestions
- if (foundWord != null || alternatives != null) {
- underlineWord(touching, range.charsBefore, range.charsAfter);
+ if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) {
+ abortCorrection(true);
+ } else {
TextEntryState.selectedForCorrection();
- if (alternatives == null) alternatives = new TypedWordAlternatives(touching,
- foundWord);
- showCorrections(alternatives);
- if (foundWord != null) {
- mWord = new WordComposer(foundWord);
- } else {
- mWord.reset();
- }
- // Revert the selection
- if (mLastSelectionStart < mLastSelectionEnd) {
- ic.setSelection(mLastSelectionStart, mLastSelectionEnd);
- }
- ic.endBatchEdit();
- return;
+ EditingUtil.underlineWord(ic, touching);
}
- abortCorrection(true);
+
+ ic.endBatchEdit();
} else {
abortCorrection(true);
setNextSuggestions();
@@ -1977,28 +2026,12 @@ public class LatinIME extends InputMethodService
} else {
abortCorrection(true);
}
- // Revert the selection
- if (mLastSelectionStart < mLastSelectionEnd) {
- ic.setSelection(mLastSelectionStart, mLastSelectionEnd);
- }
- ic.endBatchEdit();
}
private void setNextSuggestions() {
setSuggestions(mSuggestPuncList, false, false, false);
}
- private void underlineWord(CharSequence word, int left, int right) {
- InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
- if (MODIFY_TEXT_FOR_CORRECTION) {
- ic.finishComposingText();
- ic.deleteSurroundingText(left, right);
- ic.setComposingText(word, 1);
- }
- ic.setSelection(mLastSelectionStart, mLastSelectionStart);
- }
-
private void addToDictionaries(CharSequence suggestion, int frequencyDelta) {
checkAddToDictionary(suggestion, frequencyDelta, false);
}
@@ -2162,12 +2195,27 @@ public class LatinIME extends InputMethodService
public void onPress(int primaryCode) {
vibrate();
playKeyClick(primaryCode);
+ if (primaryCode == Keyboard.KEYCODE_SHIFT) {
+ mShiftKeyState.onPress();
+ handleShift();
+ } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
+ // TODO: We should handle KEYCODE_MODE_CHANGE (symbol) here as well.
+ } else {
+ mShiftKeyState.onOtherKeyPressed();
+ }
}
public void onRelease(int primaryCode) {
// Reset any drag flags in the keyboard
((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).keyReleased();
//vibrate();
+ if (primaryCode == Keyboard.KEYCODE_SHIFT) {
+ if (mShiftKeyState.isMomentary())
+ resetShift();
+ mShiftKeyState.onRelease();
+ } else if (primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
+ // TODO: We should handle KEYCODE_MODE_CHANGE (symbol) here as well.
+ }
}
private FieldContext makeFieldContext() {
@@ -2389,7 +2437,7 @@ public class LatinIME extends InputMethodService
builder.setIcon(R.drawable.ic_dialog_keyboard);
builder.setNegativeButton(android.R.string.cancel, null);
CharSequence itemSettings = getString(R.string.english_ime_settings);
- CharSequence itemInputMethod = getString(R.string.inputMethod);
+ CharSequence itemInputMethod = getString(R.string.selectInputMethod);
builder.setItems(new CharSequence[] {
itemSettings, itemInputMethod},
new DialogInterface.OnClickListener() {
@@ -2472,4 +2520,7 @@ public class LatinIME extends InputMethodService
System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
}
+ public void onAutoCompletionStateChanged(boolean isAutoCompletion) {
+ mKeyboardSwitcher.onAutoCompletionStateChanged(isAutoCompletion);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIMESettings.java b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
index 806ef00af..565c1e6e8 100644
--- a/java/src/com/android/inputmethod/latin/LatinIMESettings.java
+++ b/java/src/com/android/inputmethod/latin/LatinIMESettings.java
@@ -76,7 +76,7 @@ public class LatinIMESettings extends PreferenceActivity
mLogger = VoiceInputLogger.getLogger(this);
mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY);
- updateDebugMode(mDebugMode.isChecked());
+ updateDebugMode();
}
@Override
@@ -111,16 +111,20 @@ public class LatinIMESettings extends PreferenceActivity
showVoiceConfirmation();
}
} else if (key.equals(DEBUG_MODE_KEY)) {
- updateDebugMode(prefs.getBoolean(DEBUG_MODE_KEY, false));
+ if (mDebugMode != null) {
+ mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false));
+ updateDebugMode();
+ }
}
mVoiceOn = !(prefs.getString(VOICE_SETTINGS_KEY, mVoiceModeOff).equals(mVoiceModeOff));
updateVoiceModeSummary();
}
- private void updateDebugMode(boolean isDebugMode) {
+ private void updateDebugMode() {
if (mDebugMode == null) {
return;
}
+ boolean isDebugMode = mDebugMode.isChecked();
String version = "";
try {
PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0);
@@ -134,8 +138,8 @@ public class LatinIMESettings extends PreferenceActivity
mDebugMode.setSummary("");
} else {
mDebugMode.setEnabled(true);
- mDebugMode.setTitle(getResources().getString(R.string.prefs_debug_mode));
mDebugMode.setSummary(version);
+ mDebugMode.setSummary("");
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java
index 838b4fe10..93ad4072d 100644
--- a/java/src/com/android/inputmethod/latin/LatinIMEUtil.java
+++ b/java/src/com/android/inputmethod/latin/LatinIMEUtil.java
@@ -54,9 +54,6 @@ public class LatinIMEUtil {
}
public boolean tryGCOrWait(String metaData, Throwable t) {
- if (LatinImeLogger.sDBG) {
- Log.d(TAG, "Encountered Exception or Error. Try GC.");
- }
if (mGCTryCount == 0) {
System.gc();
}
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index 716f7207f..007d0ccdd 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -20,907 +20,52 @@ import com.android.inputmethod.latin.Dictionary.DataType;
import android.content.Context;
import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
import android.inputmethodservice.Keyboard;
-import android.os.AsyncTask;
-import android.os.DropBoxManager;
-import android.preference.PreferenceManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import java.io.ByteArrayOutputStream;
-import java.io.PrintStream;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
public class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String TAG = "LatinIMELogs";
- public static boolean sDBG = false;
- private static boolean sPRINTLOGGING = false;
- // SUPPRESS_EXCEPTION should be true when released to public.
- private static final boolean SUPPRESS_EXCEPTION = true;
- // DEFAULT_LOG_ENABLED should be false when released to public.
- private static final boolean DEFAULT_LOG_ENABLED = false;
-
- private static final long MINIMUMSENDINTERVAL = 300 * DateUtils.SECOND_IN_MILLIS; // 300 sec
- private static final long MINIMUMCOUNTINTERVAL = 20 * DateUtils.SECOND_IN_MILLIS; // 20 sec
- private static final long MINIMUMSENDSIZE = 40;
- private static final char SEPARATER = ';';
- private static final char NULL_CHAR = '\uFFFC';
- private static final int EXCEPTION_MAX_LENGTH = 400;
- private static final int INVALID_COORDINATE = -2;
-
- // ID_MANUALSUGGESTION has been replaced by ID_MANUALSUGGESTION_WITH_DATATYPE
- // private static final int ID_MANUALSUGGESTION = 0;
- // private static final int ID_AUTOSUGGESTIONCANCELLED = 1;
- // private static final int ID_AUTOSUGGESTION = 2;
- private static final int ID_INPUT_COUNT = 3;
- private static final int ID_DELETE_COUNT = 4;
- private static final int ID_WORD_COUNT = 5;
- private static final int ID_ACTUAL_CHAR_COUNT = 6;
- private static final int ID_THEME_ID = 7;
- private static final int ID_SETTING_AUTO_COMPLETE = 8;
- private static final int ID_VERSION = 9;
- private static final int ID_EXCEPTION = 10;
- private static final int ID_MANUALSUGGESTIONCOUNT = 11;
- private static final int ID_AUTOSUGGESTIONCANCELLEDCOUNT = 12;
- private static final int ID_AUTOSUGGESTIONCOUNT = 13;
- private static final int ID_LANGUAGES = 14;
- private static final int ID_MANUALSUGGESTION_WITH_DATATYPE = 15;
- private static final int ID_AUTOSUGGESTIONCANCELLED_WITH_COORDINATES = 16;
- private static final int ID_AUTOSUGGESTION_WITH_COORDINATES = 17;
-
- private static final String PREF_ENABLE_LOG = "enable_logging";
- private static final String PREF_DEBUG_MODE = "debug_mode";
- private static final String PREF_AUTO_COMPLETE = "auto_complete";
-
- public static boolean sLogEnabled = true;
- /* package */ static LatinImeLogger sLatinImeLogger = new LatinImeLogger();
- // Store the last auto suggested word.
- // This is required for a cancellation log of auto suggestion of that word.
- /* package */ static String sLastAutoSuggestBefore;
- /* package */ static String sLastAutoSuggestAfter;
- /* package */ static String sLastAutoSuggestSeparator;
- private static int[] sLastAutoSuggestXCoordinates;
- private static int[] sLastAutoSuggestYCoordinates;
- // This value holds MAIN, USER, AUTO, etc...
- private static int sLastAutoSuggestDicTypeId;
- // This value holds 0 (= unigram), 1 (= bigram) etc...
- private static int sLastAutoSuggestDataType;
- private static HashMap<String, Pair<Integer, Integer>> sSuggestDicMap
- = new HashMap<String, Pair<Integer, Integer>>();
- private static String[] sPreviousWords;
- private static DebugKeyEnabler sDebugKeyEnabler = new DebugKeyEnabler();
- private static int sKeyboardWidth = 0;
- private static int sKeyboardHeight = 0;
-
- private ArrayList<LogEntry> mLogBuffer = null;
- private ArrayList<LogEntry> mPrivacyLogBuffer = null;
- /* package */ RingCharBuffer mRingCharBuffer = null;
-
- private Context mContext = null;
- private DropBoxManager mDropBox = null;
- private AddTextToDropBoxTask mAddTextToDropBoxTask;
- private long mLastTimeActive;
- private long mLastTimeSend;
- private long mLastTimeCountEntry;
-
- private String mThemeId;
- private String mSelectedLanguages;
- private String mCurrentLanguage;
- private int mDeleteCount;
- private int mInputCount;
- private int mWordCount;
- private int[] mAutoSuggestCountPerDic = new int[Suggest.DIC_TYPE_LAST_ID + 1];
- private int[] mManualSuggestCountPerDic = new int[Suggest.DIC_TYPE_LAST_ID + 1];
- private int[] mAutoCancelledCountPerDic = new int[Suggest.DIC_TYPE_LAST_ID + 1];
- private int mActualCharCount;
-
- private static class LogEntry implements Comparable<LogEntry> {
- public final int mTag;
- public final String[] mData;
- public long mTime;
-
- public LogEntry (long time, int tag, String[] data) {
- mTag = tag;
- mTime = time;
- mData = data;
- }
-
- public int compareTo(LogEntry log2) {
- if (mData.length == 0 && log2.mData.length == 0) {
- return 0;
- } else if (mData.length == 0) {
- return 1;
- } else if (log2.mData.length == 0) {
- return -1;
- }
- return log2.mData[0].compareTo(mData[0]);
- }
- }
-
- private class AddTextToDropBoxTask extends AsyncTask<Void, Void, Void> {
- private final DropBoxManager mDropBox;
- private final long mTime;
- private final String mData;
- public AddTextToDropBoxTask(DropBoxManager db, long time, String data) {
- mDropBox = db;
- mTime = time;
- mData = data;
- }
- @Override
- protected Void doInBackground(Void... params) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Commit log: " + mData);
- }
- mDropBox.addText(TAG, mData);
- return null;
- }
- @Override
- protected void onPostExecute(Void v) {
- mLastTimeSend = mTime;
- }
- }
-
- private void initInternal(Context context) {
- mContext = context;
- mDropBox = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
- mLastTimeSend = System.currentTimeMillis();
- mLastTimeActive = mLastTimeSend;
- mLastTimeCountEntry = mLastTimeSend;
- mDeleteCount = 0;
- mInputCount = 0;
- mWordCount = 0;
- mActualCharCount = 0;
- Arrays.fill(mAutoSuggestCountPerDic, 0);
- Arrays.fill(mManualSuggestCountPerDic, 0);
- Arrays.fill(mAutoCancelledCountPerDic, 0);
- mLogBuffer = new ArrayList<LogEntry>();
- mPrivacyLogBuffer = new ArrayList<LogEntry>();
- mRingCharBuffer = new RingCharBuffer(context);
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- sLogEnabled = prefs.getBoolean(PREF_ENABLE_LOG, DEFAULT_LOG_ENABLED);
- mThemeId = prefs.getString(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT,
- KeyboardSwitcher.DEFAULT_LAYOUT_ID);
- mSelectedLanguages = prefs.getString(LatinIME.PREF_SELECTED_LANGUAGES, "");
- mCurrentLanguage = prefs.getString(LatinIME.PREF_INPUT_LANGUAGE, "");
- sPRINTLOGGING = prefs.getBoolean(PREF_DEBUG_MODE, sPRINTLOGGING);
- sDBG = sPRINTLOGGING;
- prefs.registerOnSharedPreferenceChangeListener(this);
- }
-
- /**
- * Clear all logged data
- */
- private void reset() {
- mDeleteCount = 0;
- mInputCount = 0;
- mWordCount = 0;
- mActualCharCount = 0;
- Arrays.fill(mAutoSuggestCountPerDic, 0);
- Arrays.fill(mManualSuggestCountPerDic, 0);
- Arrays.fill(mAutoCancelledCountPerDic, 0);
- mLogBuffer.clear();
- mPrivacyLogBuffer.clear();
- }
-
- public void destroy() {
- LatinIMEUtil.cancelTask(mAddTextToDropBoxTask, false);
- }
-
- /**
- * Check if the input string is safe as an entry or not.
- */
- private static boolean checkStringDataSafe(String s) {
- if (sDBG) {
- Log.d(TAG, "Check String safety: " + s);
- }
- for (int i = 0; i < s.length(); ++i) {
- if (Character.isDigit(s.charAt(i))) {
- return false;
- }
- }
- return true;
- }
-
- private void addCountEntry(long time) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Log counts. (4)");
- }
- mLogBuffer.add(new LogEntry (time, ID_DELETE_COUNT,
- new String[] {String.valueOf(mDeleteCount)}));
- mLogBuffer.add(new LogEntry (time, ID_INPUT_COUNT,
- new String[] {String.valueOf(mInputCount)}));
- mLogBuffer.add(new LogEntry (time, ID_WORD_COUNT,
- new String[] {String.valueOf(mWordCount)}));
- mLogBuffer.add(new LogEntry (time, ID_ACTUAL_CHAR_COUNT,
- new String[] {String.valueOf(mActualCharCount)}));
- mDeleteCount = 0;
- mInputCount = 0;
- mWordCount = 0;
- mActualCharCount = 0;
- mLastTimeCountEntry = time;
- }
-
- private void addSuggestionCountEntry(long time) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "log suggest counts. (1)");
- }
- String[] s = new String[mAutoSuggestCountPerDic.length];
- for (int i = 0; i < s.length; ++i) {
- s[i] = String.valueOf(mAutoSuggestCountPerDic[i]);
- }
- mLogBuffer.add(new LogEntry(time, ID_AUTOSUGGESTIONCOUNT, s));
-
- s = new String[mAutoCancelledCountPerDic.length];
- for (int i = 0; i < s.length; ++i) {
- s[i] = String.valueOf(mAutoCancelledCountPerDic[i]);
- }
- mLogBuffer.add(new LogEntry(time, ID_AUTOSUGGESTIONCANCELLEDCOUNT, s));
-
- s = new String[mManualSuggestCountPerDic.length];
- for (int i = 0; i < s.length; ++i) {
- s[i] = String.valueOf(mManualSuggestCountPerDic[i]);
- }
- mLogBuffer.add(new LogEntry(time, ID_MANUALSUGGESTIONCOUNT, s));
-
- Arrays.fill(mAutoSuggestCountPerDic, 0);
- Arrays.fill(mManualSuggestCountPerDic, 0);
- Arrays.fill(mAutoCancelledCountPerDic, 0);
- }
-
- private void addThemeIdEntry(long time) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Log theme Id. (1)");
- }
- // TODO: Not to convert theme ID here. Currently "2" is treated as "6" in a log server.
- if (mThemeId.equals("2")) {
- mThemeId = "6";
- } else if (mThemeId.equals("3")) {
- mThemeId = "7";
- }
- mLogBuffer.add(new LogEntry (time, ID_THEME_ID,
- new String[] {mThemeId}));
- }
-
- private void addLanguagesEntry(long time) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Log language settings. (1)");
- }
- // CurrentLanguage and SelectedLanguages will be blank if user doesn't use multi-language
- // switching.
- if (TextUtils.isEmpty(mCurrentLanguage)) {
- mCurrentLanguage = mContext.getResources().getConfiguration().locale.toString();
- }
- mLogBuffer.add(new LogEntry (time, ID_LANGUAGES,
- new String[] {mCurrentLanguage , mSelectedLanguages}));
- }
-
- private void addSettingsEntry(long time) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Log settings. (1)");
- }
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
- mLogBuffer.add(new LogEntry (time, ID_SETTING_AUTO_COMPLETE,
- new String[] {String.valueOf(prefs.getBoolean(PREF_AUTO_COMPLETE,
- mContext.getResources().getBoolean(R.bool.enable_autocorrect)))}));
- }
-
- private void addVersionNameEntry(long time) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Log Version. (1)");
- }
- try {
- PackageInfo info = mContext.getPackageManager().getPackageInfo(
- mContext.getPackageName(), 0);
- mLogBuffer.add(new LogEntry (time, ID_VERSION,
- new String[] {String.valueOf(info.versionCode), info.versionName}));
- } catch (NameNotFoundException e) {
- Log.e(TAG, "Could not find version name.");
- }
- }
-
- private void addExceptionEntry(long time, String[] data) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Log Exception. (1)");
- }
- mLogBuffer.add(new LogEntry(time, ID_EXCEPTION, data));
- }
-
- private void flushPrivacyLogSafely() {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Log obfuscated data. (" + mPrivacyLogBuffer.size() + ")");
- }
- long now = System.currentTimeMillis();
- Collections.sort(mPrivacyLogBuffer);
- for (LogEntry l: mPrivacyLogBuffer) {
- l.mTime = now;
- mLogBuffer.add(l);
- }
- mPrivacyLogBuffer.clear();
- }
-
- /**
- * Add an entry
- * @param tag
- * @param data
- */
- private void addData(int tag, Object data) {
- switch (tag) {
- case ID_DELETE_COUNT:
- if (((mLastTimeActive - mLastTimeCountEntry) > MINIMUMCOUNTINTERVAL)
- || (mDeleteCount == 0 && mInputCount == 0)) {
- addCountEntry(mLastTimeActive);
- }
- mDeleteCount += (Integer)data;
- break;
- case ID_INPUT_COUNT:
- if (((mLastTimeActive - mLastTimeCountEntry) > MINIMUMCOUNTINTERVAL)
- || (mDeleteCount == 0 && mInputCount == 0)) {
- addCountEntry(mLastTimeActive);
- }
- mInputCount += (Integer)data;
- break;
- case ID_MANUALSUGGESTION_WITH_DATATYPE:
- case ID_AUTOSUGGESTION_WITH_COORDINATES:
- ++mWordCount;
- String[] dataStrings = (String[]) data;
- if (dataStrings.length < 2) {
- if (sDBG) {
- Log.e(TAG, "The length of logged string array is invalid.");
- }
- break;
- }
- mActualCharCount += dataStrings[1].length();
- if (checkStringDataSafe(dataStrings[0]) && checkStringDataSafe(dataStrings[1])) {
- mPrivacyLogBuffer.add(
- new LogEntry (System.currentTimeMillis(), tag, dataStrings));
- } else {
- if (sDBG) {
- Log.d(TAG, "Skipped to add an entry because data is unsafe.");
- }
- }
- break;
- case ID_AUTOSUGGESTIONCANCELLED_WITH_COORDINATES:
- --mWordCount;
- dataStrings = (String[]) data;
- if (dataStrings.length < 2) {
- if (sDBG) {
- Log.e(TAG, "The length of logged string array is invalid.");
- }
- break;
- }
- mActualCharCount -= dataStrings[1].length();
- if (checkStringDataSafe(dataStrings[0]) && checkStringDataSafe(dataStrings[1])) {
- mPrivacyLogBuffer.add(
- new LogEntry (System.currentTimeMillis(), tag, dataStrings));
- } else {
- if (sDBG) {
- Log.d(TAG, "Skipped to add an entry because data is unsafe.");
- }
- }
- break;
- case ID_EXCEPTION:
- dataStrings = (String[]) data;
- if (dataStrings.length < 2) {
- if (sDBG) {
- Log.e(TAG, "The length of logged string array is invalid.");
- }
- break;
- }
- addExceptionEntry(System.currentTimeMillis(), dataStrings);
- break;
- default:
- if (sDBG) {
- Log.e(TAG, "Log Tag is not entried.");
- }
- break;
- }
- }
-
- private void commitInternal() {
- // if there is no log entry in mLogBuffer, will not send logs to DropBox.
- if (!mLogBuffer.isEmpty() && (mAddTextToDropBoxTask == null
- || mAddTextToDropBoxTask.getStatus() == AsyncTask.Status.FINISHED)) {
- if (sPRINTLOGGING) {
- Log.d(TAG, "Commit (" + mLogBuffer.size() + ")");
- }
- flushPrivacyLogSafely();
- long now = System.currentTimeMillis();
- addCountEntry(now);
- addThemeIdEntry(now);
- addLanguagesEntry(now);
- addSettingsEntry(now);
- addVersionNameEntry(now);
- addSuggestionCountEntry(now);
- String s = LogSerializer.createStringFromEntries(mLogBuffer);
- reset();
- mAddTextToDropBoxTask = (AddTextToDropBoxTask) new AddTextToDropBoxTask(
- mDropBox, now, s).execute();
- }
- }
-
- private void commitInternalAndStopSelf() {
- if (sDBG) {
- Log.e(TAG, "Exception was thrown and let's die.");
- }
- commitInternal();
- LatinIME ime = ((LatinIME) mContext);
- ime.hideWindow();
- ime.stopSelf();
- }
-
- private synchronized void sendLogToDropBox(int tag, Object s) {
- long now = System.currentTimeMillis();
- if (sDBG) {
- String out = "";
- if (s instanceof String[]) {
- for (String str: ((String[]) s)) {
- out += str + ",";
- }
- } else if (s instanceof Integer) {
- out += (Integer) s;
- }
- Log.d(TAG, "SendLog: " + tag + ";" + out + " -> will be sent after "
- + (- (now - mLastTimeSend - MINIMUMSENDINTERVAL) / 1000) + " sec.");
- }
- if (now - mLastTimeActive > MINIMUMSENDINTERVAL) {
- // Send a log before adding an log entry if the last data is too old.
- commitInternal();
- addData(tag, s);
- } else if (now - mLastTimeSend > MINIMUMSENDINTERVAL) {
- // Send a log after adding an log entry.
- addData(tag, s);
- commitInternal();
- } else {
- addData(tag, s);
- }
- mLastTimeActive = now;
- }
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (PREF_ENABLE_LOG.equals(key)) {
- if (sharedPreferences.getBoolean(key, DEFAULT_LOG_ENABLED)) {
- sLogEnabled = (mContext != null);
- } else {
- sLogEnabled = false;
- }
- if (sDebugKeyEnabler.check()) {
- sharedPreferences.edit().putBoolean(PREF_DEBUG_MODE, true).commit();
- }
- } else if (KeyboardSwitcher.PREF_KEYBOARD_LAYOUT.equals(key)) {
- mThemeId = sharedPreferences.getString(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT,
- KeyboardSwitcher.DEFAULT_LAYOUT_ID);
- addThemeIdEntry(mLastTimeActive);
- } else if (PREF_DEBUG_MODE.equals(key)) {
- sPRINTLOGGING = sharedPreferences.getBoolean(PREF_DEBUG_MODE, sPRINTLOGGING);
- sDBG = sPRINTLOGGING;
- } else if (LatinIME.PREF_INPUT_LANGUAGE.equals(key)) {
- mCurrentLanguage = sharedPreferences.getString(LatinIME.PREF_INPUT_LANGUAGE, "");
- addLanguagesEntry(mLastTimeActive);
- } else if (LatinIME.PREF_INPUT_LANGUAGE.equals(key)) {
- mSelectedLanguages = sharedPreferences.getString(LatinIME.PREF_SELECTED_LANGUAGES, "");
- }
}
public static void init(Context context) {
- sLatinImeLogger.initInternal(context);
}
public static void commit() {
- if (sLogEnabled) {
- if (System.currentTimeMillis() - sLatinImeLogger.mLastTimeActive > MINIMUMCOUNTINTERVAL
- || (sLatinImeLogger.mLogBuffer.size()
- + sLatinImeLogger.mPrivacyLogBuffer.size() > MINIMUMSENDSIZE)) {
- sLatinImeLogger.commitInternal();
- }
- }
}
public static void onDestroy() {
- sLatinImeLogger.commitInternal();
- sLatinImeLogger.destroy();
}
- // TODO: Handle CharSequence instead of String
public static void logOnManualSuggestion(String before, String after, int position
, List<CharSequence> suggestions) {
- if (sLogEnabled) {
- // log punctuation
- if (before.length() == 0 && after.length() == 1) {
- sLatinImeLogger.sendLogToDropBox(ID_MANUALSUGGESTION_WITH_DATATYPE, new String[] {
- before, after, String.valueOf(position), ""});
- } else if (!sSuggestDicMap.containsKey(after)) {
- if (sDBG) {
- Log.e(TAG, "logOnManualSuggestion was cancelled: from unknown dic.");
- }
- } else {
- int dicTypeId = sSuggestDicMap.get(after).first;
- sLatinImeLogger.mManualSuggestCountPerDic[dicTypeId]++;
- if (dicTypeId != Suggest.DIC_MAIN) {
- if (sDBG) {
- Log.d(TAG, "logOnManualSuggestion was cancelled: not from main dic.");
- }
- before = "";
- after = "";
- sPreviousWords = null;
- }
- // TODO: Don't send a log if this doesn't come from Main Dictionary.
- {
- if (before.equals(after)) {
- before = "";
- after = "";
- }
-
- /* Example:
- * When user typed "Illegal imm" and picked "immigrants",
- * the suggestion list has "immigrants, immediate, immigrant".
- * At this time, the log strings will be something like below:
- * strings[0 = COLUMN_BEFORE_ID] = imm
- * strings[1 = COLUMN_AFTER_ID] = immigrants
- * strings[2 = COLUMN_PICKED_POSITION_ID] = 0
- * strings[3 = COLUMN_SUGGESTION_LENGTH_ID] = 3
- * strings[4 = COLUMN_PREVIOUS_WORDS_COUNT_ID] = 1
- * strings[5] = immigrants
- * strings[6] = immediate
- * strings[7] = immigrant
- * strings[8] = 1 (= bigram)
- * strings[9] = 0 (= unigram)
- * strings[10] = 1 (= bigram)
- * strings[11] = Illegal
- */
-
- // 0 for unigram, 1 for bigram, 2 for trigram...
- int previousWordsLength = (sPreviousWords == null) ? 0 : sPreviousWords.length;
- int suggestionLength = suggestions.size();
-
- final int COLUMN_BEFORE_ID = 0;
- final int COLUMN_AFTER_ID = 1;
- final int COLUMN_PICKED_POSITION_ID = 2;
- final int COLUMN_SUGGESTION_LENGTH_ID = 3;
- final int COLUMN_PREVIOUS_WORDS_COUNT_ID = 4;
- final int BASE_COLUMN_SIZE = 5;
-
- String[] strings =
- new String[BASE_COLUMN_SIZE + suggestionLength * 2 + previousWordsLength];
- strings[COLUMN_BEFORE_ID] = before;
- strings[COLUMN_AFTER_ID] = after;
- strings[COLUMN_PICKED_POSITION_ID] = String.valueOf(position);
- strings[COLUMN_SUGGESTION_LENGTH_ID] = String.valueOf(suggestionLength);
- strings[COLUMN_PREVIOUS_WORDS_COUNT_ID] = String.valueOf(previousWordsLength);
-
- for (int i = 0; i < suggestionLength; ++i) {
- String s = suggestions.get(i).toString();
- if (sSuggestDicMap.containsKey(s)) {
- strings[BASE_COLUMN_SIZE + i] = s;
- strings[BASE_COLUMN_SIZE + suggestionLength + i]
- = sSuggestDicMap.get(s).second.toString();
- } else {
- strings[BASE_COLUMN_SIZE + i] = "";
- strings[BASE_COLUMN_SIZE + suggestionLength + i] = "";
- }
- }
-
- for (int i = 0; i < previousWordsLength; ++i) {
- strings[BASE_COLUMN_SIZE + suggestionLength * 2 + i] = sPreviousWords[i];
- }
-
- sLatinImeLogger.sendLogToDropBox(ID_MANUALSUGGESTION_WITH_DATATYPE, strings);
- }
- }
- sSuggestDicMap.clear();
- }
- }
+ }
public static void logOnAutoSuggestion(String before, String after) {
- if (sLogEnabled) {
- if (!sSuggestDicMap.containsKey(after)) {
- if (sDBG) {
- Log.e(TAG, "logOnAutoSuggestion was cancelled: from unknown dic.");
- }
- } else {
- String separator = String.valueOf(sLatinImeLogger.mRingCharBuffer.getLastChar());
- sLastAutoSuggestDicTypeId = sSuggestDicMap.get(after).first;
- sLastAutoSuggestDataType = sSuggestDicMap.get(after).second;
- sLatinImeLogger.mAutoSuggestCountPerDic[sLastAutoSuggestDicTypeId]++;
- if (sLastAutoSuggestDicTypeId != Suggest.DIC_MAIN) {
- if (sDBG) {
- Log.d(TAG, "logOnAutoSuggestion was cancelled: not from main dic.:"
- + sLastAutoSuggestDicTypeId);
- }
- before = "";
- after = "";
- sPreviousWords = null;
- }
- // TODO: Not to send a log if this doesn't come from Main Dictionary.
- {
- if (before.equals(after)) {
- before = "";
- after = "";
- }
-
- final int COLUMN_BEFORE_ID = 0;
- final int COLUMN_AFTER_ID = 1;
- final int COLUMN_SEPARATOR_ID = 2;
- final int COLUMN_DATA_TYPE_ID = 3;
- final int COLUMN_KEYBOARD_SIZE_WIDTH = 4;
- final int COLUMN_KEYBOARD_SIZE_HEIGHT = 5;
- final int BASE_COLUMN_SIZE = 6;
-
- final int userTypedWordLength = before.length();
- final int previousWordsLength = (sPreviousWords == null) ? 0
- : sPreviousWords.length;
- String[] strings = new String[BASE_COLUMN_SIZE + userTypedWordLength * 2
- + previousWordsLength];
- sLastAutoSuggestXCoordinates = new int[userTypedWordLength];
- sLastAutoSuggestXCoordinates = new int[userTypedWordLength];
-
- strings[COLUMN_BEFORE_ID] = before;
- strings[COLUMN_AFTER_ID] = after;
- strings[COLUMN_SEPARATOR_ID] = separator;
- strings[COLUMN_DATA_TYPE_ID] = String.valueOf(sLastAutoSuggestDataType);
- strings[COLUMN_KEYBOARD_SIZE_WIDTH] = String.valueOf(sKeyboardWidth);
- strings[COLUMN_KEYBOARD_SIZE_HEIGHT] = String.valueOf(sKeyboardHeight);
-
- for (int i = 0; i < userTypedWordLength; ++i) {
- int x = sLatinImeLogger.mRingCharBuffer.getPreviousX(before.charAt(i),
- userTypedWordLength - i - 1);
- int y = sLatinImeLogger.mRingCharBuffer.getPreviousY(before.charAt(i),
- userTypedWordLength - i - 1);
- strings[BASE_COLUMN_SIZE + i * 2] = String.valueOf(x);
- strings[BASE_COLUMN_SIZE + i * 2 + 1] = String.valueOf(y);
- sLastAutoSuggestXCoordinates[i] = x;
- sLastAutoSuggestXCoordinates[i] = y;
- }
-
- for (int i = 0; i < previousWordsLength; ++i) {
- strings[BASE_COLUMN_SIZE + userTypedWordLength * 2 + i] = sPreviousWords[i];
- }
-
- sLatinImeLogger.sendLogToDropBox(ID_AUTOSUGGESTION_WITH_COORDINATES, strings);
- }
- synchronized (LatinImeLogger.class) {
- sLastAutoSuggestBefore = before;
- sLastAutoSuggestAfter = after;
- sLastAutoSuggestSeparator = separator;
- }
- }
- sSuggestDicMap.clear();
- }
}
public static void logOnAutoSuggestionCanceled() {
- if (sLogEnabled) {
- sLatinImeLogger.mAutoCancelledCountPerDic[sLastAutoSuggestDicTypeId]++;
- if (sLastAutoSuggestBefore != null && sLastAutoSuggestAfter != null) {
- final int COLUMN_BEFORE_ID = 0;
- final int COLUMN_AFTER_ID = 1;
- final int COLUMN_SEPARATOR_ID = 2;
- final int COLUMN_KEYBOARD_SIZE_WIDTH = 3;
- final int COLUMN_KEYBOARD_SIZE_HEIGHT = 4;
- final int BASE_COLUMN_SIZE = 5;
-
- final int userTypedWordLength = sLastAutoSuggestBefore.length();
-
- String[] strings = new String[BASE_COLUMN_SIZE + userTypedWordLength * 2];
- strings[COLUMN_BEFORE_ID] = sLastAutoSuggestBefore;
- strings[COLUMN_AFTER_ID] = sLastAutoSuggestAfter;
- strings[COLUMN_SEPARATOR_ID] = sLastAutoSuggestSeparator;
- strings[COLUMN_KEYBOARD_SIZE_WIDTH] = String.valueOf(sKeyboardWidth);
- strings[COLUMN_KEYBOARD_SIZE_HEIGHT] = String.valueOf(sKeyboardHeight);
- for (int i = 0; i < userTypedWordLength; ++i) {
- strings[BASE_COLUMN_SIZE + i * 2] = String.valueOf(
- sLastAutoSuggestXCoordinates);
- strings[BASE_COLUMN_SIZE + i * 2 + 1] = String.valueOf(
- sLastAutoSuggestYCoordinates);
- }
- sLatinImeLogger.sendLogToDropBox(
- ID_AUTOSUGGESTIONCANCELLED_WITH_COORDINATES, strings);
- }
- synchronized (LatinImeLogger.class) {
- sLastAutoSuggestBefore = "";
- sLastAutoSuggestAfter = "";
- sLastAutoSuggestSeparator = "";
- }
- }
}
public static void logOnDelete() {
- if (sLogEnabled) {
- String mLastWord = sLatinImeLogger.mRingCharBuffer.getLastString();
- if (!TextUtils.isEmpty(mLastWord)
- && mLastWord.equalsIgnoreCase(sLastAutoSuggestBefore)) {
- logOnAutoSuggestionCanceled();
- }
- sLatinImeLogger.mRingCharBuffer.pop();
- sLatinImeLogger.sendLogToDropBox(ID_DELETE_COUNT, 1);
- }
}
public static void logOnInputChar(char c, int x, int y) {
- if (sLogEnabled) {
- sLatinImeLogger.mRingCharBuffer.push(c, x, y);
- sLatinImeLogger.sendLogToDropBox(ID_INPUT_COUNT, 1);
- }
}
public static void logOnException(String metaData, Throwable e) {
- if (sLogEnabled) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- PrintStream ps = new PrintStream(baos);
- e.printStackTrace(ps);
- String exceptionString = URLEncoder.encode(new String(baos.toByteArray(), 0,
- Math.min(EXCEPTION_MAX_LENGTH, baos.size())));
- sLatinImeLogger.sendLogToDropBox(
- ID_EXCEPTION, new String[] {metaData, exceptionString});
- if (sDBG) {
- Log.e(TAG, "Exception: " + new String(baos.toByteArray())+ ":" + exceptionString);
- }
- if (SUPPRESS_EXCEPTION) {
- sLatinImeLogger.commitInternalAndStopSelf();
- } else {
- sLatinImeLogger.commitInternal();
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- } else if (e instanceof Error) {
- throw (Error) e;
- }
- }
- }
}
public static void logOnWarning(String warning) {
- if (sLogEnabled) {
- sLatinImeLogger.sendLogToDropBox(
- ID_EXCEPTION, new String[] {warning, ""});
- }
}
- // TODO: This code supports only Bigram.
public static void onStartSuggestion(CharSequence previousWords) {
- if (sLogEnabled) {
- sSuggestDicMap.clear();
- sPreviousWords = new String[] {
- (previousWords == null) ? "" : previousWords.toString()};
- }
}
public static void onAddSuggestedWord(String word, int typeId, DataType dataType) {
- if (sLogEnabled) {
- sSuggestDicMap.put(word, new Pair<Integer, Integer>(typeId, dataType.ordinal()));
- }
}
public static void onSetKeyboard(Keyboard kb) {
- if (sLogEnabled) {
- sKeyboardWidth = kb.getMinWidth();
- sKeyboardHeight = kb.getHeight();
- }
- }
-
- private static class LogSerializer {
- private static void appendWithLength(StringBuffer sb, String data) {
- sb.append(data.length());
- sb.append(SEPARATER);
- sb.append(data);
- sb.append(SEPARATER);
- }
-
- private static void appendLogEntry(StringBuffer sb, String time, String tag,
- String[] data) {
- if (data.length > 0) {
- appendWithLength(sb, String.valueOf(data.length + 2));
- appendWithLength(sb, time);
- appendWithLength(sb, tag);
- for (String s: data) {
- appendWithLength(sb, s);
- }
- }
- }
-
- public static String createStringFromEntries(ArrayList<LogEntry> logs) {
- StringBuffer sb = new StringBuffer();
- for (LogEntry log: logs) {
- appendLogEntry(sb, String.valueOf(log.mTime), String.valueOf(log.mTag), log.mData);
- }
- return sb.toString();
- }
}
- /* package */ static class RingCharBuffer {
- final int BUFSIZE = 20;
- private Context mContext;
- private int mEnd = 0;
- /* package */ int mLength = 0;
- private char[] mCharBuf = new char[BUFSIZE];
- private int[] mXBuf = new int[BUFSIZE];
- private int[] mYBuf = new int[BUFSIZE];
-
- public RingCharBuffer(Context context) {
- mContext = context;
- }
- private int normalize(int in) {
- int ret = in % BUFSIZE;
- return ret < 0 ? ret + BUFSIZE : ret;
- }
- public void push(char c, int x, int y) {
- mCharBuf[mEnd] = c;
- mXBuf[mEnd] = x;
- mYBuf[mEnd] = y;
- mEnd = normalize(mEnd + 1);
- if (mLength < BUFSIZE) {
- ++mLength;
- }
- }
- public char pop() {
- if (mLength < 1) {
- return NULL_CHAR;
- } else {
- mEnd = normalize(mEnd - 1);
- --mLength;
- return mCharBuf[mEnd];
- }
- }
- public char getLastChar() {
- if (mLength < 1) {
- return NULL_CHAR;
- } else {
- return mCharBuf[normalize(mEnd - 1)];
- }
- }
- public int getPreviousX(char c, int back) {
- int index = normalize(mEnd - 2 - back);
- if (mLength <= back
- || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
- return INVALID_COORDINATE;
- } else {
- return mXBuf[index];
- }
- }
- public int getPreviousY(char c, int back) {
- int index = normalize(mEnd - 2 - back);
- if (mLength <= back
- || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
- return INVALID_COORDINATE;
- } else {
- return mYBuf[index];
- }
- }
- public String getLastString() {
- StringBuffer sb = new StringBuffer();
- for (int i = 0; i < mLength; ++i) {
- char c = mCharBuf[normalize(mEnd - 1 - i)];
- if (!((LatinIME)mContext).isWordSeparator(c)) {
- sb.append(c);
- } else {
- break;
- }
- }
- return sb.reverse().toString();
- }
- public void reset() {
- mLength = 0;
- }
- }
-
- private static class DebugKeyEnabler {
- private int mCounter = 0;
- private long mLastTime = 0;
- public boolean check() {
- if (System.currentTimeMillis() - mLastTime > 10 * 1000) {
- mCounter = 0;
- mLastTime = System.currentTimeMillis();
- } else if (++mCounter >= 10) {
- return true;
- }
- return false;
- }
- }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboard.java b/java/src/com/android/inputmethod/latin/LatinKeyboard.java
index db4d167d4..c7ca67727 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboard.java
@@ -43,11 +43,14 @@ public class LatinKeyboard extends Keyboard {
private static final boolean DEBUG_PREFERRED_LETTER = false;
private static final String TAG = "LatinKeyboard";
+ private static final int OPACITY_FULLY_OPAQUE = 255;
+ private static final int SPACE_LED_LENGTH_PERCENT = 80;
private Drawable mShiftLockIcon;
private Drawable mShiftLockPreviewIcon;
private Drawable mOldShiftIcon;
private Drawable mSpaceIcon;
+ private Drawable mSpaceAutoCompletionIndicator;
private Drawable mSpacePreviewIcon;
private Drawable mMicIcon;
private Drawable mMicPreviewIcon;
@@ -81,7 +84,6 @@ public class LatinKeyboard extends Keyboard {
private int mPrefLetterY;
private int mPrefDistance;
- private int mExtensionResId;
// TODO: generalize for any keyboardId
private boolean mIsBlackSym;
@@ -112,6 +114,7 @@ public class LatinKeyboard extends Keyboard {
mShiftLockPreviewIcon.getIntrinsicWidth(),
mShiftLockPreviewIcon.getIntrinsicHeight());
mSpaceIcon = res.getDrawable(R.drawable.sym_keyboard_space);
+ mSpaceAutoCompletionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led);
mSpacePreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_space);
mMicIcon = res.getDrawable(R.drawable.sym_keyboard_mic);
mMicPreviewIcon = res.getDrawable(R.drawable.sym_keyboard_feedback_mic);
@@ -278,17 +281,9 @@ public class LatinKeyboard extends Keyboard {
return mIsAlphaKeyboard;
}
- public void setExtension(int resId) {
- mExtensionResId = resId;
- }
-
- public int getExtension() {
- return mExtensionResId;
- }
-
- public void setBlackFlag(boolean f) {
- mIsBlackSym = f;
- if (f) {
+ public void setColorOfSymbolIcons(boolean isAutoCompletion, boolean isBlack) {
+ mIsBlackSym = isBlack;
+ if (isBlack) {
mShiftLockIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_shift_locked);
mSpaceIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_space);
mMicIcon = mRes.getDrawable(R.drawable.sym_bkeyboard_mic);
@@ -301,8 +296,7 @@ public class LatinKeyboard extends Keyboard {
}
updateF1Key();
if (mSpaceKey != null) {
- mSpaceKey.icon = mSpaceIcon;
- updateSpaceBarForLocale(f);
+ updateSpaceBarForLocale(isAutoCompletion, isBlack);
}
}
@@ -343,57 +337,87 @@ public class LatinKeyboard extends Keyboard {
}
}
- private void updateSpaceBarForLocale(boolean isBlack) {
+ /**
+ * @return a key which should be invalidated.
+ */
+ public Key onAutoCompletionStateChanged(boolean isAutoCompletion) {
+ updateSpaceBarForLocale(isAutoCompletion, mIsBlackSym);
+ return mSpaceKey;
+ }
+
+ private void updateSpaceBarForLocale(boolean isAutoCompletion, boolean isBlack) {
+ // If application locales are explicitly selected.
if (mLocale != null) {
- // Create the graphic for spacebar
- Bitmap buffer = Bitmap.createBitmap(mSpaceKey.width, mSpaceIcon.getIntrinsicHeight(),
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(buffer);
- drawSpaceBar(canvas, buffer.getWidth(), buffer.getHeight(), 255, isBlack);
- mSpaceKey.icon = new BitmapDrawable(mRes, buffer);
+ mSpaceKey.icon = new BitmapDrawable(mRes,
+ drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion, isBlack));
mSpaceKey.repeatable = mLanguageSwitcher.getLocaleCount() < 2;
} else {
- mSpaceKey.icon = isBlack ? mRes.getDrawable(R.drawable.sym_bkeyboard_space)
- : mRes.getDrawable(R.drawable.sym_keyboard_space);
+ // sym_keyboard_space_led can be shared with Black and White symbol themes.
+ if (isAutoCompletion) {
+ mSpaceKey.icon = new BitmapDrawable(mRes,
+ drawSpaceBar(OPACITY_FULLY_OPAQUE, isAutoCompletion, isBlack));
+ } else {
+ mSpaceKey.icon = isBlack ? mRes.getDrawable(R.drawable.sym_bkeyboard_space)
+ : mRes.getDrawable(R.drawable.sym_keyboard_space);
+ }
mSpaceKey.repeatable = true;
}
}
- private void drawSpaceBar(Canvas canvas, int width, int height, int opacity, boolean isBlack) {
+ private Bitmap drawSpaceBar(int opacity, boolean isAutoCompletion, boolean isBlack) {
+ int width = mSpaceKey.width;
+ int height = mSpaceIcon.getIntrinsicHeight();
+ Bitmap buffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(buffer);
canvas.drawColor(mRes.getColor(R.color.latinkeyboard_transparent), PorterDuff.Mode.CLEAR);
- Paint paint = new Paint();
- paint.setAntiAlias(true);
- paint.setAlpha(opacity);
- // Get the text size from the theme
- paint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14));
- paint.setTextAlign(Align.CENTER);
- final String language = getInputLanguage(mSpaceKey.width, paint);
- final int ascent = (int) -paint.ascent();
-
- int shadowColor = isBlack ? mRes.getColor(R.color.latinkeyboard_bar_language_shadow_black)
- : mRes.getColor(R.color.latinkeyboard_bar_language_shadow_white);
-
- paint.setColor(shadowColor);
- canvas.drawText(language, width / 2, ascent - 1, paint);
- paint.setColor(mRes.getColor(R.color.latinkeyboard_bar_language_text));
- canvas.drawText(language, width / 2, ascent, paint);
- // Put arrows on either side of the text
- if (mLanguageSwitcher.getLocaleCount() > 1) {
- Rect bounds = new Rect();
- paint.getTextBounds(language, 0, language.length(), bounds);
- drawButtonArrow(mButtonArrowLeftIcon, canvas,
- (mSpaceKey.width - bounds.right) / 2
- - mButtonArrowLeftIcon.getIntrinsicWidth(),
- (int) paint.getTextSize());
- drawButtonArrow(mButtonArrowRightIcon, canvas,
- (mSpaceKey.width + bounds.right) / 2, (int) paint.getTextSize());
+ // If application locales are explicitly selected.
+ if (mLocale != null) {
+ Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setAlpha(opacity);
+ // Get the text size from the theme
+ paint.setTextSize(getTextSizeFromTheme(android.R.style.TextAppearance_Small, 14));
+ paint.setTextAlign(Align.CENTER);
+ final String language = getInputLanguage(mSpaceKey.width, paint);
+ final int ascent = (int) -paint.ascent();
+
+ int shadowColor = isBlack ?
+ mRes.getColor(R.color.latinkeyboard_bar_language_shadow_black)
+ : mRes.getColor(R.color.latinkeyboard_bar_language_shadow_white);
+
+ paint.setColor(shadowColor);
+ canvas.drawText(language, width / 2, ascent - 1, paint);
+ paint.setColor(mRes.getColor(R.color.latinkeyboard_bar_language_text));
+ canvas.drawText(language, width / 2, ascent, paint);
+ // Put arrows on either side of the text
+ if (mLanguageSwitcher.getLocaleCount() > 1) {
+ Rect bounds = new Rect();
+ paint.getTextBounds(language, 0, language.length(), bounds);
+ drawButtonArrow(mButtonArrowLeftIcon, canvas,
+ (mSpaceKey.width - bounds.right) / 2
+ - mButtonArrowLeftIcon.getIntrinsicWidth(),
+ (int) paint.getTextSize());
+ drawButtonArrow(mButtonArrowRightIcon, canvas,
+ (mSpaceKey.width + bounds.right) / 2, (int) paint.getTextSize());
+ }
}
// Draw the spacebar icon at the bottom
- int x = (width - mSpaceIcon.getIntrinsicWidth()) / 2;
- int y = height - mSpaceIcon.getIntrinsicHeight();
- mSpaceIcon.setBounds(x, y,
- x + mSpaceIcon.getIntrinsicWidth(), y + mSpaceIcon.getIntrinsicHeight());
- mSpaceIcon.draw(canvas);
+ if (isAutoCompletion) {
+ final int iconWidth = width * SPACE_LED_LENGTH_PERCENT / 100;
+ final int iconHeight = mSpaceAutoCompletionIndicator.getIntrinsicHeight();
+ int x = (width - iconWidth) / 2;
+ int y = height - iconHeight;
+ mSpaceAutoCompletionIndicator.setBounds(x, y, x + iconWidth, y + iconHeight);
+ mSpaceAutoCompletionIndicator.draw(canvas);
+ } else {
+ final int iconWidth = mSpaceIcon.getIntrinsicWidth();
+ final int iconHeight = mSpaceIcon.getIntrinsicHeight();
+ int x = (width - iconWidth) / 2;
+ int y = height - iconHeight;
+ mSpaceIcon.setBounds(x, y, x + iconWidth, y + iconHeight);
+ mSpaceIcon.draw(canvas);
+ }
+ return buffer;
}
private void drawButtonArrow(Drawable arrow, Canvas canvas, int x, int bottomY) {
@@ -447,7 +471,8 @@ public class LatinKeyboard extends Keyboard {
return mSpaceDragLastDiff > 0 ? 1 : -1;
}
- public void setLanguageSwitcher(LanguageSwitcher switcher) {
+ public void setLanguageSwitcher(LanguageSwitcher switcher, boolean isAutoCompletion,
+ boolean isBlackSym) {
mLanguageSwitcher = switcher;
Locale locale = mLanguageSwitcher.getLocaleCount() > 0
? mLanguageSwitcher.getInputLocale()
@@ -459,9 +484,9 @@ public class LatinKeyboard extends Keyboard {
.equalsIgnoreCase(locale.getLanguage())) {
locale = null;
}
+ setColorOfSymbolIcons(isAutoCompletion, isBlackSym);
if (mLocale != null && mLocale.equals(locale)) return;
mLocale = locale;
- updateSpaceBarForLocale(mIsBlackSym);
}
boolean isCurrentlyInSpace() {
@@ -637,9 +662,20 @@ public class LatinKeyboard extends Keyboard {
}
class LatinKey extends Keyboard.Key {
-
+
+ // functional normal state (with properties)
+ private final int[] KEY_STATE_FUNCTIONAL_NORMAL = {
+ android.R.attr.state_single
+ };
+
+ // functional pressed state (with properties)
+ private final int[] KEY_STATE_FUNCTIONAL_PRESSED = {
+ android.R.attr.state_single,
+ android.R.attr.state_pressed
+ };
+
private boolean mShiftLockEnabled;
-
+
public LatinKey(Resources res, Keyboard.Row parent, int x, int y,
XmlResourceParser parser) {
super(res, parent, x, y, parser);
@@ -648,11 +684,17 @@ public class LatinKeyboard extends Keyboard {
popupResId = 0;
}
}
-
- void enableShiftLock() {
+
+ private void enableShiftLock() {
mShiftLockEnabled = true;
}
+ // sticky is used for shift key. If a key is not sticky and is modifier,
+ // the key will be treated as functional.
+ private boolean isFunctionalKey() {
+ return !sticky && modifier;
+ }
+
@Override
public void onReleased(boolean inside) {
if (!mShiftLockEnabled) {
@@ -674,6 +716,18 @@ public class LatinKeyboard extends Keyboard {
boolean isInsideSuper(int x, int y) {
return super.isInside(x, y);
}
+
+ @Override
+ public int[] getCurrentDrawableState() {
+ if (isFunctionalKey()) {
+ if (pressed) {
+ return KEY_STATE_FUNCTIONAL_PRESSED;
+ } else {
+ return KEY_STATE_FUNCTIONAL_NORMAL;
+ }
+ }
+ return super.getCurrentDrawableState();
+ }
}
/**
@@ -709,7 +763,7 @@ public class LatinKeyboard extends Keyboard {
mTextPaint.setTextSize(textSize);
mTextPaint.setColor(R.color.latinkeyboard_transparent);
mTextPaint.setTextAlign(Align.CENTER);
- mTextPaint.setAlpha(255);
+ mTextPaint.setAlpha(OPACITY_FULLY_OPAQUE);
mTextPaint.setAntiAlias(true);
mAscent = (int) mTextPaint.ascent();
mMiddleX = (mWidth - mBackground.getIntrinsicWidth()) / 2;
@@ -741,7 +795,7 @@ public class LatinKeyboard extends Keyboard {
public void draw(Canvas canvas) {
canvas.save();
if (mHitThreshold) {
- mTextPaint.setColor(mRes.getColor(R.color.latinkeyboard_text_color));
+ mTextPaint.setColor(mRes.getColor(R.color.latinkeyboard_feedback_language_text));
canvas.clipRect(0, 0, mWidth, mHeight);
if (mCurrentLanguage == null) {
mCurrentLanguage = getInputLanguage(mWidth, mTextPaint);
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
index 4007c2b55..c449b36e7 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardBaseView.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin;
import android.content.Context;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -32,19 +33,20 @@ import android.inputmethodservice.Keyboard.Key;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.ViewGroup.LayoutParams;
import android.widget.PopupWindow;
import android.widget.TextView;
+import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
+import java.util.LinkedList;
import java.util.Map;
/**
@@ -60,7 +62,11 @@ import java.util.Map;
* @attr ref R.styleable#LatinKeyboardBaseView_verticalCorrection
* @attr ref R.styleable#LatinKeyboardBaseView_popupLayout
*/
-public class LatinKeyboardBaseView extends View implements View.OnClickListener {
+public class LatinKeyboardBaseView extends View implements PointerTracker.UIProxy {
+ private static final String TAG = "LatinKeyboardBaseView";
+ private static final boolean DEBUG = false;
+
+ public static final int NOT_A_TOUCH_COORDINATE = -1;
public interface OnKeyboardActionListener {
@@ -139,116 +145,97 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
void swipeUp();
}
- public static final int NOT_A_TOUCH_COORDINATE = -1;
+ // Timing constants
+ private static final int DELAY_BEFORE_PREVIEW = 0;
+ private static final int DELAY_AFTER_PREVIEW = 70;
+ private static final int REPEAT_INTERVAL = PointerTracker.REPEAT_INTERVAL;
- private static final boolean DEBUG = false;
- static final int NOT_A_KEY = -1;
- private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
+ // Miscellaneous constants
+ /* package */ static final int NOT_A_KEY = -1;
private static final int[] LONG_PRESSABLE_STATE_SET = { android.R.attr.state_long_pressable };
- private Keyboard mKeyboard;
- private int mCurrentKeyIndex = NOT_A_KEY;
- private int mLabelTextSize;
+ // XML attribute
private int mKeyTextSize;
private int mKeyTextColor;
- private float mShadowRadius;
+ private Typeface mKeyTextStyle = Typeface.DEFAULT;
+ private int mLabelTextSize;
+ private int mSymbolColorScheme = 0;
private int mShadowColor;
+ private float mShadowRadius;
+ private Drawable mKeyBackground;
private float mBackgroundDimAmount;
-
- private TextView mPreviewText;
- private PopupWindow mPreviewPopup;
- private int mPreviewTextSizeLarge;
+ private float mVerticalCorrection;
private int mPreviewOffset;
private int mPreviewHeight;
- private int[] mOffsetInWindow;
+ private int mPopupLayout;
- private PopupWindow mPopupKeyboard;
- private View mMiniKeyboardContainer;
- private LatinKeyboardBaseView mMiniKeyboard;
- private boolean mMiniKeyboardOnScreen;
- private View mPopupParent;
- private int mMiniKeyboardOffsetX;
- private int mMiniKeyboardOffsetY;
- private Map<Key,View> mMiniKeyboardCache;
- private int[] mWindowOffset;
+ // Main keyboard
+ private Keyboard mKeyboard;
private Key[] mKeys;
- private Typeface mKeyTextStyle = Typeface.DEFAULT;
- private int mSymbolColorScheme = 0;
- /** Listener for {@link OnKeyboardActionListener}. */
- private OnKeyboardActionListener mKeyboardActionListener;
-
- private static final int DELAY_BEFORE_PREVIEW = 0;
- private static final int DELAY_AFTER_PREVIEW = 70;
- private static final int DEBOUNCE_TIME = 70;
-
- private int mVerticalCorrection;
- private ProximityKeyDetector mProximityKeyDetector = new ProximityKeyDetector();
-
- private boolean mPreviewCentered = false;
+ // Key preview popup
+ private final static boolean PREVIEW_CENTERED = false;
+ private TextView mPreviewText;
+ private PopupWindow mPreviewPopup;
+ private int mPreviewTextSizeLarge;
+ private int[] mOffsetInWindow;
+ private int mOldPreviewKeyIndex = NOT_A_KEY;
private boolean mShowPreview = true;
private boolean mShowTouchPoints = true;
private int mPopupPreviewX;
private int mPopupPreviewY;
+ private int mPopupPreviewOffsetX;
+ private int mPopupPreviewOffsetY;
private int mWindowY;
- private Paint mPaint;
- private Rect mPadding;
-
- private int mCurrentKey = NOT_A_KEY;
- private int mStartX;
- private int mStartY;
-
- private KeyDebouncer mDebouncer;
-
- private GestureDetector mGestureDetector;
- private int mPopupX;
- private int mPopupY;
- private int mPopupLayout;
- private boolean mAbortKey;
- private Key mInvalidatedKey;
- private Rect mClipRegion = new Rect(0, 0, 0, 0);
- private SwipeTracker mSwipeTracker = new SwipeTracker();
- private int mSwipeThreshold;
- private boolean mDisambiguateSwipe;
+ // Popup mini keyboard
+ private PopupWindow mMiniKeyboardPopup;
+ private LatinKeyboardBaseView mMiniKeyboard;
+ private View mMiniKeyboardParent;
+ private Map<Key,View> mMiniKeyboardCache;
+ private int mMiniKeyboardOriginX;
+ private int mMiniKeyboardOriginY;
+ private long mMiniKeyboardPopupTime;
+ private int[] mWindowOffset;
- // Variables for dealing with multiple pointers
- private int mOldPointerCount = 1;
- private int mOldPointerX;
- private int mOldPointerY;
+ /** Listener for {@link OnKeyboardActionListener}. */
+ private OnKeyboardActionListener mKeyboardActionListener;
- private Drawable mKeyBackground;
+ private final ArrayList<PointerTracker> mPointerTrackers = new ArrayList<PointerTracker>();
+ private final PointerQueue mPointerQueue = new PointerQueue();
+ private final float mDebounceHysteresis;
- private static final int REPEAT_INTERVAL = 50; // ~20 keys per second
- private static final int REPEAT_START_DELAY = 400;
- private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+ protected KeyDetector mKeyDetector = new ProximityKeyDetector();
- // For multi-tap
- private int mLastSentIndex;
- private int mTapCount;
- private long mLastTapTime;
- private boolean mInMultiTap;
- private static final int MULTITAP_INTERVAL = 800; // milliseconds
- private StringBuilder mPreviewLabel = new StringBuilder(1);
+ // Swipe gesture detector
+ private final GestureDetector mGestureDetector;
+ private final SwipeTracker mSwipeTracker = new SwipeTracker();
+ private final int mSwipeThreshold;
+ private final boolean mDisambiguateSwipe;
+ // Drawing
/** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
private boolean mDrawPending;
/** The dirty region in the keyboard bitmap */
- private Rect mDirtyRect = new Rect();
+ private final Rect mDirtyRect = new Rect();
/** The keyboard bitmap for faster updates */
private Bitmap mBuffer;
/** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
private boolean mKeyboardChanged;
+ private Key mInvalidatedKey;
/** The canvas for the above mutable keyboard bitmap */
private Canvas mCanvas;
+ private final Paint mPaint;
+ private final Rect mPadding;
+ private final Rect mClipRegion = new Rect(0, 0, 0, 0);
- UIHandler mHandler = new UIHandler();
+ private final UIHandler mHandler = new UIHandler();
class UIHandler extends Handler {
private static final int MSG_POPUP_PREVIEW = 1;
private static final int MSG_DISMISS_PREVIEW = 2;
private static final int MSG_REPEAT_KEY = 3;
- private static final int MSG_LOGPRESS_KEY = 4;
+ private static final int MSG_LONGPRESS_KEY = 4;
private boolean mInKeyRepeat;
@@ -256,24 +243,34 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_POPUP_PREVIEW:
- showKey(msg.arg1);
+ showKey(msg.arg1, (PointerTracker)msg.obj);
break;
case MSG_DISMISS_PREVIEW:
mPreviewText.setVisibility(INVISIBLE);
break;
- case MSG_REPEAT_KEY:
- repeatKey(msg.arg1);
- startKeyRepeatTimer(REPEAT_INTERVAL, msg.arg1);
+ case MSG_REPEAT_KEY: {
+ final PointerTracker tracker = (PointerTracker)msg.obj;
+ tracker.repeatKey(msg.arg1);
+ startKeyRepeatTimer(REPEAT_INTERVAL, msg.arg1, tracker);
break;
- case MSG_LOGPRESS_KEY:
- openPopupIfRequired(msg.arg1);
+ }
+ case MSG_LONGPRESS_KEY: {
+ final PointerTracker tracker = (PointerTracker)msg.obj;
+ openPopupIfRequired(msg.arg1, tracker);
break;
+ }
}
}
- public void popupPreview(int keyIndex, long delay) {
+ public void popupPreview(long delay, int keyIndex, PointerTracker tracker) {
removeMessages(MSG_POPUP_PREVIEW);
- sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0), delay);
+ if (mPreviewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
+ // Show right away, if it's already visible and finger is moving around
+ showKey(keyIndex, tracker);
+ } else {
+ sendMessageDelayed(obtainMessage(MSG_POPUP_PREVIEW, keyIndex, 0, tracker),
+ delay);
+ }
}
public void cancelPopupPreview() {
@@ -281,16 +278,18 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
}
public void dismissPreview(long delay) {
- sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
+ if (mPreviewPopup.isShowing()) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_PREVIEW), delay);
+ }
}
public void cancelDismissPreview() {
removeMessages(MSG_DISMISS_PREVIEW);
}
- public void startKeyRepeatTimer(long delay, int keyIndex) {
+ public void startKeyRepeatTimer(long delay, int keyIndex, PointerTracker tracker) {
mInKeyRepeat = true;
- sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0), delay);
+ sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, keyIndex, 0, tracker), delay);
}
public void cancelKeyRepeatTimer() {
@@ -302,13 +301,13 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
return mInKeyRepeat;
}
- public void startLongPressTimer(int keyIndex, long delay) {
- removeMessages(MSG_LOGPRESS_KEY);
- sendMessageDelayed(obtainMessage(MSG_LOGPRESS_KEY, keyIndex, 0), delay);
+ public void startLongPressTimer(long delay, int keyIndex, PointerTracker tracker) {
+ removeMessages(MSG_LONGPRESS_KEY);
+ sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, keyIndex, 0, tracker), delay);
}
public void cancelLongPressTimer() {
- removeMessages(MSG_LOGPRESS_KEY);
+ removeMessages(MSG_LONGPRESS_KEY);
}
public void cancelKeyTimers() {
@@ -323,109 +322,38 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
}
};
- static class KeyDebouncer {
- private final Key[] mKeys;
- private final int mKeyDebounceThresholdSquared;
-
- // for move de-bouncing
- private int mLastCodeX;
- private int mLastCodeY;
- private int mLastX;
- private int mLastY;
-
- // for time de-bouncing
- private int mLastKey;
- private long mLastKeyTime;
- private long mLastMoveTime;
- private long mCurrentKeyTime;
-
- KeyDebouncer(Key[] keys, float hysteresisPixel) {
- if (keys == null || hysteresisPixel < 1.0f)
- throw new IllegalArgumentException();
- mKeys = keys;
- mKeyDebounceThresholdSquared = (int)(hysteresisPixel * hysteresisPixel);
- }
-
- public int getLastCodeX() {
- return mLastCodeX;
- }
-
- public int getLastCodeY() {
- return mLastCodeY;
- }
-
- public int getLastX() {
- return mLastX;
- }
-
- public int getLastY() {
- return mLastY;
- }
-
- public int getLastKey() {
- return mLastKey;
- }
-
- public void startMoveDebouncing(int x, int y) {
- mLastCodeX = x;
- mLastCodeY = y;
- }
+ static class PointerQueue {
+ private LinkedList<PointerTracker> mQueue = new LinkedList<PointerTracker>();
- public void updateMoveDebouncing(int x, int y) {
- mLastX = x;
- mLastY = y;
+ public void add(PointerTracker tracker) {
+ mQueue.add(tracker);
}
- public void resetMoveDebouncing() {
- mLastCodeX = mLastX;
- mLastCodeY = mLastY;
- }
-
- public boolean isMinorMoveBounce(int x, int y, int newKey, int curKey) {
- if (newKey == curKey) {
- return true;
- } else if (curKey >= 0 && curKey < mKeys.length) {
- return getSquareDistanceToKeyEdge(x, y, mKeys[curKey])
- < mKeyDebounceThresholdSquared;
- } else {
- return false;
+ public int lastIndexOf(PointerTracker tracker) {
+ LinkedList<PointerTracker> queue = mQueue;
+ for (int index = queue.size() - 1; index >= 0; index--) {
+ PointerTracker t = queue.get(index);
+ if (t == tracker)
+ return index;
}
+ return -1;
}
- private static int getSquareDistanceToKeyEdge(int x, int y, Key key) {
- final int left = key.x;
- final int right = key.x + key.width;
- final int top = key.y;
- final int bottom = key.y + key.height;
- final int edgeX = x < left ? left : (x > right ? right : x);
- final int edgeY = y < top ? top : (y > bottom ? bottom : y);
- final int dx = x - edgeX;
- final int dy = y - edgeY;
- return dx * dx + dy * dy;
- }
-
- public void startTimeDebouncing(long eventTime) {
- mLastKey = NOT_A_KEY;
- mLastKeyTime = 0;
- mCurrentKeyTime = 0;
- mLastMoveTime = eventTime;
- }
-
- public void updateTimeDebouncing(long eventTime) {
- mCurrentKeyTime += eventTime - mLastMoveTime;
- mLastMoveTime = eventTime;
- }
-
- public void resetTimeDebouncing(long eventTime, int currentKey) {
- mLastKey = currentKey;
- mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
- mCurrentKeyTime = 0;
- mLastMoveTime = eventTime;
+ public void releasePointersOlderThan(PointerTracker tracker, long eventTime) {
+ LinkedList<PointerTracker> queue = mQueue;
+ int oldestPos = 0;
+ for (PointerTracker t = queue.get(oldestPos); t != tracker; t = queue.get(oldestPos)) {
+ if (t.isModifier()) {
+ oldestPos++;
+ } else {
+ t.onUpEvent(t.getLastX(), t.getLastY(), eventTime);
+ queue.remove(oldestPos);
+ }
+ }
}
- public boolean isMinorTimeBounce() {
- return mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < DEBOUNCE_TIME
- && mLastKey != NOT_A_KEY;
+ public void remove(PointerTracker tracker) {
+ mQueue.remove(tracker);
}
}
@@ -516,15 +444,11 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
} else {
mShowPreview = false;
}
-
mPreviewPopup.setTouchable(false);
+ mMiniKeyboardParent = this;
- mPopupKeyboard = new PopupWindow(context);
- mPopupKeyboard.setBackgroundDrawable(null);
- //mPopupKeyboard.setClippingEnabled(false);
-
- mPopupParent = this;
- //mPredicting = true;
+ mMiniKeyboardPopup = new PopupWindow(context);
+ mMiniKeyboardPopup.setBackgroundDrawable(null);
mPaint = new Paint();
mPaint.setAntiAlias(true);
@@ -536,19 +460,17 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
mMiniKeyboardCache = new HashMap<Key,View>();
mKeyBackground.getPadding(mPadding);
- mSwipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
+ final Resources res = getResources();
+ mSwipeThreshold = (int) (500 * res.getDisplayMetrics().density);
// TODO: Refer frameworks/base/core/res/res/values/config.xml
- mDisambiguateSwipe = getResources().getBoolean(R.bool.config_swipeDisambiguation);
- resetMultiTap();
- initGestureDetector();
- }
+ mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation);
+ mDebounceHysteresis = res.getDimension(R.dimen.key_debounce_hysteresis_distance);
- private void initGestureDetector() {
GestureDetector.SimpleOnGestureListener listener =
new GestureDetector.SimpleOnGestureListener() {
@Override
- public boolean onFling(MotionEvent me1, MotionEvent me2,
- float velocityX, float velocityY) {
+ public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX,
+ float velocityY) {
final float absX = Math.abs(velocityX);
final float absY = Math.abs(velocityY);
float deltaX = me2.getX() - me1.getX();
@@ -590,6 +512,9 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
mKeyboardActionListener = listener;
+ for (PointerTracker tracker : mPointerTrackers) {
+ tracker.setOnKeyboardActionListener(listener);
+ }
}
/**
@@ -609,26 +534,24 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
*/
public void setKeyboard(Keyboard keyboard) {
if (mKeyboard != null) {
- showPreview(NOT_A_KEY);
+ dismissKeyPreview();
}
// Remove any pending messages, except dismissing preview
mHandler.cancelKeyTimers();
mHandler.cancelPopupPreview();
mKeyboard = keyboard;
- LatinImeLogger.onSetKeyboard(mKeyboard);
- List<Key> keys = mKeyboard.getKeys();
- mKeys = keys.toArray(new Key[keys.size()]);
- mProximityKeyDetector.setKeyboard(keyboard, mKeys);
+ LatinImeLogger.onSetKeyboard(keyboard);
+ mKeys = mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
+ -getPaddingTop() + mVerticalCorrection);
+ for (PointerTracker tracker : mPointerTrackers) {
+ tracker.setKeyboard(mKeys, mDebounceHysteresis);
+ }
requestLayout();
// Hint to reallocate the buffer if the size changed
mKeyboardChanged = true;
invalidateAllKeys();
computeProximityThreshold(keyboard);
mMiniKeyboardCache.clear();
- // Not really necessary to do every time, but will free up views
- // Switching to a different keyboard should abort any pending keys so that the key up
- // doesn't get delivered to the old or new keyboard
- mAbortKey = true; // Until the next ACTION_DOWN
}
/**
@@ -691,16 +614,13 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
return mSymbolColorScheme;
}
- public void setVerticalCorrection(int verticalOffset) {
- }
-
public void setPopupParent(View v) {
- mPopupParent = v;
+ mMiniKeyboardParent = v;
}
public void setPopupOffset(int x, int y) {
- mMiniKeyboardOffsetX = x;
- mMiniKeyboardOffsetY = y;
+ mPopupPreviewOffsetX = x;
+ mPopupPreviewOffsetY = y;
if (mPreviewPopup.isShowing()) {
mPreviewPopup.dismiss();
}
@@ -713,22 +633,14 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
* @param enabled whether or not the proximity correction is enabled
*/
public void setProximityCorrectionEnabled(boolean enabled) {
- mProximityKeyDetector.setProximityCorrectionEnabled(enabled);
+ mKeyDetector.setProximityCorrectionEnabled(enabled);
}
/**
* Returns true if proximity correction is enabled.
*/
public boolean isProximityCorrectionEnabled() {
- return mProximityKeyDetector.isProximityCorrectionEnabled();
- }
-
- /**
- * Popup keyboard close button clicked.
- * @hide
- */
- public void onClick(View v) {
- dismissPopupKeyboard();
+ return mKeyDetector.isProximityCorrectionEnabled();
}
protected CharSequence adjustCase(CharSequence label) {
@@ -772,11 +684,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
dimensionSum += Math.min(key.width, key.height) + key.gap;
}
if (dimensionSum < 0 || length == 0) return;
- mProximityKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length));
-
- final float hysteresisPixel = getContext().getResources()
- .getDimension(R.dimen.key_debounce_hysteresis_distance);
- mDebouncer = new KeyDebouncer(keys, hysteresisPixel);
+ mKeyDetector.setProximityThreshold((int) (dimensionSum * 1.4f / length));
}
@Override
@@ -889,23 +797,27 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
}
mInvalidatedKey = null;
// Overlay a dark rectangle to dim the keyboard
- if (mMiniKeyboardOnScreen) {
+ if (mMiniKeyboard != null) {
paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
}
if (DEBUG) {
if (mShowTouchPoints) {
- int lastX = mDebouncer.getLastX();
- int lastY = mDebouncer.getLastY();
- paint.setAlpha(128);
- paint.setColor(0xFFFF0000);
- canvas.drawCircle(mStartX, mStartY, 3, paint);
- canvas.drawLine(mStartX, mStartY, lastX, lastY, paint);
- paint.setColor(0xFF0000FF);
- canvas.drawCircle(lastX, lastY, 3, paint);
- paint.setColor(0xFF00FF00);
- canvas.drawCircle((mStartX + lastX) / 2, (mStartY + lastY) / 2, 2, paint);
+ for (PointerTracker tracker : mPointerTrackers) {
+ int startX = tracker.getStartX();
+ int startY = tracker.getStartY();
+ int lastX = tracker.getLastX();
+ int lastY = tracker.getLastY();
+ paint.setAlpha(128);
+ paint.setColor(0xFFFF0000);
+ canvas.drawCircle(startX, startY, 3, paint);
+ canvas.drawLine(startX, startY, lastX, lastY, paint);
+ paint.setColor(0xFF0000FF);
+ canvas.drawCircle(lastX, lastY, 3, paint);
+ paint.setColor(0xFF00FF00);
+ canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint);
+ }
}
}
@@ -913,105 +825,39 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
mDirtyRect.setEmpty();
}
-
- private void detectAndSendKey(int index, int x, int y, long eventTime) {
- if (index != NOT_A_KEY && index < mKeys.length) {
- final Key key = mKeys[index];
- if (key.text != null) {
- mKeyboardActionListener.onText(key.text);
- mKeyboardActionListener.onRelease(NOT_A_KEY);
- } else {
- int code = key.codes[0];
- //TextEntryState.keyPressedAt(key, x, y);
- int[] codes = mProximityKeyDetector.newCodeArray();
- mProximityKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
- // Multi-tap
- if (mInMultiTap) {
- if (mTapCount != -1) {
- mKeyboardActionListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y);
- } else {
- mTapCount = 0;
- }
- code = key.codes[mTapCount];
- }
- /*
- * Swap the first and second values in the codes array if the primary code is not
- * the first value but the second value in the array. This happens when key
- * debouncing is in effect.
- */
- if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
- codes[1] = codes[0];
- codes[0] = code;
- }
- mKeyboardActionListener.onKey(code, codes, x, y);
- mKeyboardActionListener.onRelease(code);
- }
- mLastSentIndex = index;
- mLastTapTime = eventTime;
- }
+ // TODO: clean up this method.
+ private void dismissKeyPreview() {
+ for (PointerTracker tracker : mPointerTrackers)
+ tracker.updateKey(NOT_A_KEY);
+ showPreview(NOT_A_KEY, null);
}
- /**
- * Handle multi-tap keys by producing the key label for the current multi-tap state.
- */
- private CharSequence getPreviewText(Key key) {
- if (mInMultiTap) {
- // Multi-tap
- mPreviewLabel.setLength(0);
- mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
- return adjustCase(mPreviewLabel);
- } else {
- return adjustCase(key.label);
- }
- }
-
- private void showPreview(int keyIndex) {
- int oldKeyIndex = mCurrentKeyIndex;
- final PopupWindow previewPopup = mPreviewPopup;
-
- mCurrentKeyIndex = keyIndex;
- // Release the old key and press the new key
- final Key[] keys = mKeys;
- if (oldKeyIndex != mCurrentKeyIndex) {
- if (oldKeyIndex != NOT_A_KEY && keys.length > oldKeyIndex) {
- keys[oldKeyIndex].onReleased(mCurrentKeyIndex == NOT_A_KEY);
- invalidateKey(oldKeyIndex);
- }
- if (mCurrentKeyIndex != NOT_A_KEY && keys.length > mCurrentKeyIndex) {
- keys[mCurrentKeyIndex].onPressed();
- invalidateKey(mCurrentKeyIndex);
- }
- }
+ public void showPreview(int keyIndex, PointerTracker tracker) {
+ int oldKeyIndex = mOldPreviewKeyIndex;
+ mOldPreviewKeyIndex = keyIndex;
// If key changed and preview is on ...
- if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) {
+ if (oldKeyIndex != keyIndex && mShowPreview) {
if (keyIndex == NOT_A_KEY) {
mHandler.cancelPopupPreview();
- if (previewPopup.isShowing()) {
- mHandler.dismissPreview(DELAY_AFTER_PREVIEW);
- }
- } else {
- if (previewPopup.isShowing() && mPreviewText.getVisibility() == VISIBLE) {
- // Show right away, if it's already visible and finger is moving around
- showKey(keyIndex);
- } else {
- mHandler.popupPreview(keyIndex, DELAY_BEFORE_PREVIEW);
- }
+ mHandler.dismissPreview(DELAY_AFTER_PREVIEW);
+ } else if (tracker != null) {
+ mHandler.popupPreview(DELAY_BEFORE_PREVIEW, keyIndex, tracker);
}
}
}
- private void showKey(final int keyIndex) {
+ private void showKey(final int keyIndex, PointerTracker tracker) {
+ Key key = tracker.getKey(keyIndex);
+ if (key == null)
+ return;
final PopupWindow previewPopup = mPreviewPopup;
- final Key[] keys = mKeys;
- if (keyIndex < 0 || keyIndex >= mKeys.length) return;
- Key key = keys[keyIndex];
if (key.icon != null) {
mPreviewText.setCompoundDrawables(null, null, null,
key.iconPreview != null ? key.iconPreview : key.icon);
mPreviewText.setText(null);
} else {
mPreviewText.setCompoundDrawables(null, null, null, null);
- mPreviewText.setText(getPreviewText(key));
+ mPreviewText.setText(adjustCase(tracker.getPreviewText(key)));
if (key.label.length() > 1 && key.codes.length < 2) {
mPreviewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, mKeyTextSize);
mPreviewText.setTypeface(Typeface.DEFAULT_BOLD);
@@ -1030,20 +876,20 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
lp.width = popupWidth;
lp.height = popupHeight;
}
- if (!mPreviewCentered) {
- mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft();
- mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
- } else {
+ if (PREVIEW_CENTERED) {
// TODO: Fix this if centering is brought back
mPopupPreviewX = 160 - mPreviewText.getMeasuredWidth() / 2;
mPopupPreviewY = - mPreviewText.getMeasuredHeight();
+ } else {
+ mPopupPreviewX = key.x - mPreviewText.getPaddingLeft() + getPaddingLeft();
+ mPopupPreviewY = key.y - popupHeight + mPreviewOffset;
}
mHandler.cancelDismissPreview();
if (mOffsetInWindow == null) {
mOffsetInWindow = new int[2];
getLocationInWindow(mOffsetInWindow);
- mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
- mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
+ mOffsetInWindow[0] += mPopupPreviewOffsetX; // Offset may be zero
+ mOffsetInWindow[1] += mPopupPreviewOffsetY; // Offset may be zero
int[] mWindowLocation = new int[2];
getLocationOnScreen(mWindowLocation);
mWindowY = mWindowLocation[1];
@@ -1072,7 +918,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
} else {
previewPopup.setWidth(popupWidth);
previewPopup.setHeight(popupHeight);
- previewPopup.showAtLocation(mPopupParent, Gravity.NO_GRAVITY,
+ previewPopup.showAtLocation(mMiniKeyboardParent, Gravity.NO_GRAVITY,
mPopupPreviewX, mPopupPreviewY);
}
mPreviewText.setVisibility(VISIBLE);
@@ -1082,7 +928,7 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
* Requests a redraw of the entire keyboard. Calling {@link #invalidate} is not sufficient
* because the keyboard renders the keys to an off-screen buffer and an invalidate() only
* draws the cached buffer.
- * @see #invalidateKey(int)
+ * @see #invalidateKey(Key)
*/
public void invalidateAllKeys() {
mDirtyRect.union(0, 0, getWidth(), getHeight());
@@ -1094,15 +940,12 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
* Invalidates a key so that it will be redrawn on the next repaint. Use this method if only
* one key is changing it's content. Any changes that affect the position or size of the key
* may not be honored.
- * @param keyIndex the index of the key in the attached {@link Keyboard}.
+ * @param key key in the attached {@link Keyboard}.
* @see #invalidateAllKeys
*/
- public void invalidateKey(int keyIndex) {
- if (mKeys == null) return;
- if (keyIndex < 0 || keyIndex >= mKeys.length) {
+ public void invalidateKey(Key key) {
+ if (key == null)
return;
- }
- final Key key = mKeys[keyIndex];
mInvalidatedKey = key;
mDirtyRect.union(key.x + getPaddingLeft(), key.y + getPaddingTop(),
key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
@@ -1111,24 +954,75 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
key.x + key.width + getPaddingLeft(), key.y + key.height + getPaddingTop());
}
- private boolean openPopupIfRequired(int keyIndex) {
+ private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) {
// Check if we have a popup layout specified first.
if (mPopupLayout == 0) {
return false;
}
- if (keyIndex < 0 || keyIndex >= mKeys.length) {
- return false;
- }
- Key popupKey = mKeys[keyIndex];
+ Key popupKey = tracker.getKey(keyIndex);
+ if (popupKey == null)
+ return false;
boolean result = onLongPress(popupKey);
if (result) {
- mAbortKey = true;
- showPreview(NOT_A_KEY);
+ dismissKeyPreview();
+ tracker.setAlreadyProcessed();
}
return result;
}
+ private View inflateMiniKeyboardContainer(Key popupKey) {
+ int popupKeyboardId = popupKey.popupResId;
+ LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ View container = inflater.inflate(mPopupLayout, null);
+ if (container == null)
+ throw new NullPointerException();
+
+ mMiniKeyboard = (LatinKeyboardBaseView)container.findViewById(R.id.LatinKeyboardBaseView);
+ mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
+ public void onKey(int primaryCode, int[] keyCodes, int x, int y) {
+ mKeyboardActionListener.onKey(primaryCode, keyCodes, x, y);
+ dismissPopupKeyboard();
+ }
+
+ public void onText(CharSequence text) {
+ mKeyboardActionListener.onText(text);
+ dismissPopupKeyboard();
+ }
+
+ public void swipeLeft() {
+ }
+ public void swipeRight() {
+ }
+ public void swipeUp() {
+ }
+ public void swipeDown() {
+ }
+ public void onPress(int primaryCode) {
+ mKeyboardActionListener.onPress(primaryCode);
+ }
+ public void onRelease(int primaryCode) {
+ mKeyboardActionListener.onRelease(primaryCode);
+ }
+ });
+
+ Keyboard keyboard;
+ if (popupKey.popupCharacters != null) {
+ keyboard = new Keyboard(getContext(), popupKeyboardId, popupKey.popupCharacters,
+ -1, getPaddingLeft() + getPaddingRight());
+ } else {
+ keyboard = new Keyboard(getContext(), popupKeyboardId);
+ }
+ mMiniKeyboard.setKeyboard(keyboard);
+ mMiniKeyboard.setPopupParent(this);
+
+ container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
+
+ return container;
+ }
+
/**
* Called when a key is long pressed. By default this will open any popup keyboard associated
* with this key through the attributes popupLayout and popupCharacters.
@@ -1137,123 +1031,99 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
* method on the base class if the subclass doesn't wish to handle the call.
*/
protected boolean onLongPress(Key popupKey) {
- int popupKeyboardId = popupKey.popupResId;
+ // TODO if popupKey.popupCharacters has only one letter, send it as key without opening
+ // mini keyboard.
- if (popupKeyboardId != 0) {
- mMiniKeyboardContainer = mMiniKeyboardCache.get(popupKey);
- if (mMiniKeyboardContainer == null) {
- LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- mMiniKeyboardContainer = inflater.inflate(mPopupLayout, null);
- mMiniKeyboard = (LatinKeyboardBaseView) mMiniKeyboardContainer.findViewById(
- R.id.LatinKeyboardBaseView);
- View closeButton = mMiniKeyboardContainer.findViewById(
- R.id.closeButton);
- if (closeButton != null) closeButton.setOnClickListener(this);
- mMiniKeyboard.setOnKeyboardActionListener(new OnKeyboardActionListener() {
- public void onKey(int primaryCode, int[] keyCodes, int x, int y) {
- mKeyboardActionListener.onKey(primaryCode, keyCodes, x, y);
- dismissPopupKeyboard();
- }
+ if (popupKey.popupResId == 0)
+ return false;
- public void onText(CharSequence text) {
- mKeyboardActionListener.onText(text);
- dismissPopupKeyboard();
- }
+ View container = mMiniKeyboardCache.get(popupKey);
+ if (container == null) {
+ container = inflateMiniKeyboardContainer(popupKey);
+ mMiniKeyboardCache.put(popupKey, container);
+ }
+ mMiniKeyboard = (LatinKeyboardBaseView)container.findViewById(R.id.LatinKeyboardBaseView);
+ if (mWindowOffset == null) {
+ mWindowOffset = new int[2];
+ getLocationInWindow(mWindowOffset);
+ }
+ int popupX = popupKey.x + popupKey.width + getPaddingLeft();
+ int popupY = popupKey.y + getPaddingTop();
+ popupX -= container.getMeasuredWidth();
+ popupY -= container.getMeasuredHeight();
+ popupX += mWindowOffset[0];
+ popupY += mWindowOffset[1];
+ final int x = popupX + container.getPaddingRight();
+ final int y = popupY + container.getPaddingBottom();
+ mMiniKeyboardOriginX = (x < 0 ? 0 : x) + container.getPaddingLeft();
+ mMiniKeyboardOriginY = y + container.getPaddingTop();
+ mMiniKeyboard.setPopupOffset((x < 0) ? 0 : x, y);
+ mMiniKeyboard.setShifted(isShifted());
+ mMiniKeyboard.setPreviewEnabled(isPreviewEnabled());
+ mMiniKeyboardPopup.setContentView(container);
+ mMiniKeyboardPopup.setWidth(container.getMeasuredWidth());
+ mMiniKeyboardPopup.setHeight(container.getMeasuredHeight());
+ mMiniKeyboardPopup.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
+
+ // Inject down event on the key to mini keyboard.
+ long eventTime = System.currentTimeMillis();
+ mMiniKeyboardPopupTime = eventTime;
+ MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN, popupKey.x
+ + popupKey.width / 2, popupKey.y + popupKey.height / 2, eventTime);
+ mMiniKeyboard.onTouchEvent(downEvent);
+ downEvent.recycle();
- public void swipeLeft() { }
- public void swipeRight() { }
- public void swipeUp() { }
- public void swipeDown() { }
- public void onPress(int primaryCode) {
- mKeyboardActionListener.onPress(primaryCode);
- }
- public void onRelease(int primaryCode) {
- mKeyboardActionListener.onRelease(primaryCode);
- }
- });
- //mInputView.setSuggest(mSuggest);
- Keyboard keyboard;
- if (popupKey.popupCharacters != null) {
- keyboard = new Keyboard(getContext(), popupKeyboardId,
- popupKey.popupCharacters, -1, getPaddingLeft() + getPaddingRight());
- } else {
- keyboard = new Keyboard(getContext(), popupKeyboardId);
- }
- mMiniKeyboard.setKeyboard(keyboard);
- mMiniKeyboard.setPopupParent(this);
- mMiniKeyboardContainer.measure(
- MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
- MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
-
- mMiniKeyboardCache.put(popupKey, mMiniKeyboardContainer);
- } else {
- mMiniKeyboard = (LatinKeyboardBaseView) mMiniKeyboardContainer.findViewById(
- R.id.LatinKeyboardBaseView);
- }
- if (mWindowOffset == null) {
- mWindowOffset = new int[2];
- getLocationInWindow(mWindowOffset);
- }
- mPopupX = popupKey.x + getPaddingLeft();
- mPopupY = popupKey.y + getPaddingTop();
- mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
- mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
- final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
- final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
- mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
- mMiniKeyboard.setShifted(isShifted());
- mPopupKeyboard.setContentView(mMiniKeyboardContainer);
- mPopupKeyboard.setWidth(mMiniKeyboardContainer.getMeasuredWidth());
- mPopupKeyboard.setHeight(mMiniKeyboardContainer.getMeasuredHeight());
- mPopupKeyboard.showAtLocation(this, Gravity.NO_GRAVITY, x, y);
- mMiniKeyboardOnScreen = true;
- //mMiniKeyboard.onTouchEvent(getTranslatedEvent(me));
- invalidateAllKeys();
- return true;
- }
- return false;
+ invalidateAllKeys();
+ return true;
}
- private int getTouchX(float x) {
- return (int)x - getPaddingLeft();
+ private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) {
+ return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action,
+ x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0);
}
- private int getTouchY(float y) {
- return (int)y + mVerticalCorrection - getPaddingTop();
+ private PointerTracker getPointerTracker(final int id) {
+ final ArrayList<PointerTracker> pointers = mPointerTrackers;
+ final Key[] keys = mKeys;
+ final OnKeyboardActionListener listener = mKeyboardActionListener;
+
+ // Create pointer trackers until we can get 'id+1'-th tracker, if needed.
+ for (int i = pointers.size(); i <= id; i++) {
+ final PointerTracker tracker =
+ new PointerTracker(i, mHandler, mKeyDetector, this);
+ if (keys != null)
+ tracker.setKeyboard(keys, mDebounceHysteresis);
+ if (listener != null)
+ tracker.setOnKeyboardActionListener(listener);
+ pointers.add(tracker);
+ }
+
+ return pointers.get(id);
}
@Override
public boolean onTouchEvent(MotionEvent me) {
- // Convert multi-pointer up/down events to single up/down events to
- // deal with the typical multi-pointer behavior of two-thumb typing
final int pointerCount = me.getPointerCount();
- final int action = me.getAction();
+ final int action = me.getActionMasked();
final long eventTime = me.getEventTime();
- if (pointerCount > 1 && mOldPointerCount > 1) {
- // Don't do anything when 2 or more pointers are down and moving.
- return true;
- }
-
// Track the last few movements to look for spurious swipes.
mSwipeTracker.addMovement(me);
- // Ignore all motion events until a DOWN.
- if (mAbortKey
- && action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_CANCEL) {
- return true;
- }
-
- if (mGestureDetector.onTouchEvent(me)) {
- showPreview(NOT_A_KEY);
+ // We must disable gesture detector while mini-keyboard is on the screen.
+ if (mMiniKeyboard == null && mGestureDetector.onTouchEvent(me)) {
+ dismissKeyPreview();
mHandler.cancelKeyTimers();
return true;
}
// Needs to be called after the gesture detector gets a turn, as it may have
// displayed the mini keyboard
- if (mMiniKeyboardOnScreen && action != MotionEvent.ACTION_CANCEL) {
+ if (mMiniKeyboard != null) {
+ MotionEvent translated = generateMiniKeyboardMotionEvent(action, (int)me.getX(),
+ (int)me.getY(), eventTime);
+ mMiniKeyboard.onTouchEvent(translated);
+ translated.recycle();
return true;
}
@@ -1268,145 +1138,58 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
// Up event will pass through.
}
- int touchX = getTouchX(me.getX());
- int touchY = getTouchY(me.getY());
- if (pointerCount != mOldPointerCount) {
- if (pointerCount == 1) {
- // Send a down event for the latest pointer
- onDownEvent(touchX, touchY, eventTime);
- // If it's an up action, then deliver the up as well.
- if (action == MotionEvent.ACTION_UP) {
- onUpEvent(touchX, touchY, eventTime);
- }
- } else {
- // Send an up event for the last pointer
- onUpEvent(mOldPointerX, mOldPointerY, eventTime);
+ if (action == MotionEvent.ACTION_MOVE) {
+ for (int index = 0; index < pointerCount; index++) {
+ int x = (int)me.getX(index);
+ int y = (int)me.getY(index);
+ int id = me.getPointerId(index);
+ PointerTracker tracker = getPointerTracker(id);
+ tracker.onMoveEvent(x, y, eventTime);
}
- mOldPointerCount = pointerCount;
- return true;
} else {
- if (pointerCount == 1) {
- onModifiedTouchEvent(action, touchX, touchY, eventTime);
- mOldPointerX = touchX;
- mOldPointerY = touchY;
- return true;
- }
- }
-
- return false;
- }
-
- private void onModifiedTouchEvent(int action, int touchX, int touchY, long eventTime) {
- switch (action) {
+ int index = me.getActionIndex();
+ int x = (int)me.getX(index);
+ int y = (int)me.getY(index);
+ int id = me.getPointerId(index);
+ PointerTracker tracker = getPointerTracker(id);
+ switch (action) {
case MotionEvent.ACTION_DOWN:
- onDownEvent(touchX, touchY, eventTime);
- break;
- case MotionEvent.ACTION_MOVE:
- onMoveEvent(touchX, touchY, eventTime);
+ case MotionEvent.ACTION_POINTER_DOWN:
+ onDownEvent(tracker, x, y, eventTime);
break;
case MotionEvent.ACTION_UP:
- onUpEvent(touchX, touchY, eventTime);
+ case MotionEvent.ACTION_POINTER_UP:
+ onUpEvent(tracker, x, y, eventTime);
break;
case MotionEvent.ACTION_CANCEL:
- onCancelEvent(touchX, touchY, eventTime);
+ onCancelEvent(tracker, x, y, eventTime);
break;
- }
- }
-
- private void onDownEvent(int touchX, int touchY, long eventTime) {
- int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
- mAbortKey = false;
- mCurrentKey = keyIndex;
- mStartX = touchX;
- mStartY = touchY;
- mDebouncer.startMoveDebouncing(touchX, touchY);
- mDebouncer.startTimeDebouncing(eventTime);
- checkMultiTap(eventTime, keyIndex);
- mKeyboardActionListener.onPress(keyIndex != NOT_A_KEY ? mKeys[keyIndex].codes[0] : 0);
- if (keyIndex >= 0 && mKeys[keyIndex].repeatable) {
- repeatKey(keyIndex);
- mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex);
- // Delivering the key could have caused an abort
- if (mAbortKey) {
- mHandler.cancelKeyRepeatTimer();
- return;
}
}
- if (keyIndex != NOT_A_KEY) {
- mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
- }
- showPreview(keyIndex);
- mDebouncer.updateMoveDebouncing(touchX, touchY);
+
+ return true;
}
- private void onMoveEvent(int touchX, int touchY, long eventTime) {
- int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
- if (keyIndex != NOT_A_KEY) {
- if (mCurrentKey == NOT_A_KEY) {
- mDebouncer.updateTimeDebouncing(eventTime);
- mCurrentKey = keyIndex;
- mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
- } else if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) {
- mDebouncer.updateTimeDebouncing(eventTime);
- } else {
- resetMultiTap();
- mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey);
- mDebouncer.resetMoveDebouncing();
- mCurrentKey = keyIndex;
- mHandler.startLongPressTimer(keyIndex, LONGPRESS_TIMEOUT);
- }
- } else {
- mHandler.cancelLongPressTimer();
- }
- /*
- * While time debouncing is in effect, mCurrentKey holds the new key and mDebouncer
- * holds the last key. At ACTION_UP event if time debouncing will be in effect
- * eventually, the last key should be sent as the result. In such case mCurrentKey
- * should not be showed as popup preview.
- */
- showPreview(mDebouncer.isMinorTimeBounce() ? mDebouncer.getLastKey() : mCurrentKey);
- mDebouncer.updateMoveDebouncing(touchX, touchY);
+ private void onDownEvent(PointerTracker tracker, int x, int y, long eventTime) {
+ tracker.onDownEvent(x, y, eventTime);
+ mPointerQueue.add(tracker);
}
- private void onUpEvent(int touchX, int touchY, long eventTime) {
- int keyIndex = mProximityKeyDetector.getKeyIndexAndNearbyCodes(touchX, touchY, null);
- boolean wasInKeyRepeat = mHandler.isInKeyRepeat();
- mHandler.cancelKeyTimers();
- mHandler.cancelPopupPreview();
- if (mDebouncer.isMinorMoveBounce(touchX, touchY, keyIndex, mCurrentKey)) {
- mDebouncer.updateTimeDebouncing(eventTime);
+ private void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) {
+ int index = mPointerQueue.lastIndexOf(tracker);
+ if (index >= 0) {
+ mPointerQueue.releasePointersOlderThan(tracker, eventTime);
} else {
- resetMultiTap();
- mDebouncer.resetTimeDebouncing(eventTime, mCurrentKey);
- mCurrentKey = keyIndex;
- }
- if (mDebouncer.isMinorTimeBounce()) {
- mCurrentKey = mDebouncer.getLastKey();
- touchX = mDebouncer.getLastCodeX();
- touchY = mDebouncer.getLastCodeY();
- }
- showPreview(NOT_A_KEY);
- // If we're not on a repeating key (which sends on a DOWN event)
- if (!wasInKeyRepeat && !mMiniKeyboardOnScreen && !mAbortKey) {
- detectAndSendKey(mCurrentKey, touchX, touchY, eventTime);
+ Log.w(TAG, "onUpEvent: corresponding down event not found for pointer "
+ + tracker.mPointerId);
}
- invalidateKey(keyIndex);
+ tracker.onUpEvent(x, y, eventTime);
+ mPointerQueue.remove(tracker);
}
- private void onCancelEvent(int touchX, int touchY, long eventTime) {
- mHandler.cancelKeyTimers();
- mHandler.cancelPopupPreview();
- dismissPopupKeyboard();
- mAbortKey = true;
- showPreview(NOT_A_KEY);
- invalidateKey(mCurrentKey);
- }
-
- private void repeatKey(int keyIndex) {
- Key key = mKeys[keyIndex];
- // While key is repeating, because there is no need to handle multi-tap key, we can pass
- // -1 as eventTime argument.
- detectAndSendKey(keyIndex, key.x, key.y, -1);
+ private void onCancelEvent(PointerTracker tracker, int x, int y, long eventTime) {
+ tracker.onCancelEvent(x, y, eventTime);
+ mPointerQueue.remove(tracker);
}
protected void swipeRight() {
@@ -1444,44 +1227,20 @@ public class LatinKeyboardBaseView extends View implements View.OnClickListener
}
private void dismissPopupKeyboard() {
- if (mPopupKeyboard.isShowing()) {
- mPopupKeyboard.dismiss();
- mMiniKeyboardOnScreen = false;
+ if (mMiniKeyboardPopup.isShowing()) {
+ mMiniKeyboardPopup.dismiss();
+ mMiniKeyboard = null;
+ mMiniKeyboardOriginX = 0;
+ mMiniKeyboardOriginY = 0;
invalidateAllKeys();
}
}
public boolean handleBack() {
- if (mPopupKeyboard.isShowing()) {
+ if (mMiniKeyboardPopup.isShowing()) {
dismissPopupKeyboard();
return true;
}
return false;
}
-
- private void resetMultiTap() {
- mLastSentIndex = NOT_A_KEY;
- mTapCount = 0;
- mLastTapTime = -1;
- mInMultiTap = false;
- }
-
- private void checkMultiTap(long eventTime, int keyIndex) {
- if (keyIndex == NOT_A_KEY) return;
- Key key = mKeys[keyIndex];
- if (key.codes.length > 1) {
- mInMultiTap = true;
- if (eventTime < mLastTapTime + MULTITAP_INTERVAL
- && keyIndex == mLastSentIndex) {
- mTapCount = (mTapCount + 1) % key.codes.length;
- return;
- } else {
- mTapCount = -1;
- return;
- }
- }
- if (eventTime > mLastTapTime + MULTITAP_INTERVAL || keyIndex != mLastSentIndex) {
- resetMultiTap();
- }
- }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java
index e57abd2c5..71ca8b81a 100644
--- a/java/src/com/android/inputmethod/latin/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/latin/LatinKeyboardView.java
@@ -16,8 +16,6 @@
package com.android.inputmethod.latin;
-import java.util.List;
-
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -27,14 +25,14 @@ import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.AttributeSet;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
-import android.widget.PopupWindow;
+
+import java.util.List;
public class LatinKeyboardView extends LatinKeyboardBaseView {
static final int KEYCODE_OPTIONS = -100;
- static final int KEYCODE_SHIFT_LONGPRESS = -101;
+ static final int KEYCODE_OPTIONS_LONGPRESS = -101;
static final int KEYCODE_VOICE = -102;
static final int KEYCODE_F1 = -103;
static final int KEYCODE_NEXT_LANGUAGE = -104;
@@ -42,21 +40,11 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
private Keyboard mPhoneKeyboard;
- /** Whether the extension of this keyboard is visible */
- private boolean mExtensionVisible;
- /** The view that is shown as an extension of this keyboard view */
- private LatinKeyboardView mExtension;
- /** The popup window that contains the extension of this keyboard */
- private PopupWindow mExtensionPopup;
- /** Whether this view is an extension of another keyboard */
- private boolean mIsExtensionType;
- private boolean mFirstEvent;
/** Whether we've started dropping move events because we found a big jump */
private boolean mDroppingEvents;
- /**
- * Whether multi-touch disambiguation needs to be disabled for any reason. There are 2 reasons
- * for this to happen - (1) if a real multi-touch event has occured and (2) we've opened an
- * extension keyboard.
+ /**
+ * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
+ * occured
*/
private boolean mDisableDisambiguation;
/** The distance threshold at which we start treating the touch session as a multi-touch */
@@ -64,7 +52,8 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
/** The y coordinate of the last row */
private int mLastRowY;
- private int mExtensionLayoutResId = 0;
+ // This is local working variable for onLongPress().
+ private int[] mKeyCodes = new int[1];
public LatinKeyboardView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -78,10 +67,6 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
mPhoneKeyboard = phoneKeyboard;
}
- public void setExtentionLayoutResId (int id) {
- mExtensionLayoutResId = id;
- }
-
@Override
public void setKeyboard(Keyboard k) {
super.setKeyboard(k);
@@ -93,23 +78,37 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
setKeyboardLocal(k);
}
+ private static boolean hasOneDigitAlternate(Key key) {
+ final CharSequence alternates = key.popupCharacters;
+ if (alternates == null)
+ return false;
+ final String altChars = alternates.toString();
+ if (altChars.codePointCount(0, altChars.length()) != 1)
+ return false;
+ final int altCode = altChars.codePointAt(0);
+ return altCode >= '0' && altCode <= '9';
+ }
+
@Override
protected boolean onLongPress(Key key) {
- if (key.codes[0] == Keyboard.KEYCODE_MODE_CHANGE) {
- getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null,
+ int primaryCode = key.codes[0];
+ if (primaryCode == KEYCODE_OPTIONS) {
+ getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS_LONGPRESS, null,
LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE,
LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE);
return true;
- } else if (key.codes[0] == Keyboard.KEYCODE_SHIFT) {
- getOnKeyboardActionListener().onKey(KEYCODE_SHIFT_LONGPRESS, null,
+ } else if (primaryCode == '0' && getKeyboard() == mPhoneKeyboard) {
+ // Long pressing on 0 in phone number keypad gives you a '+'.
+ getOnKeyboardActionListener().onKey(
+ '+', null,
LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE,
LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE);
- invalidateAllKeys();
return true;
- } else if (key.codes[0] == '0' && getKeyboard() == mPhoneKeyboard) {
- // Long pressing on 0 in phone number keypad gives you a '+'.
- getOnKeyboardActionListener().onKey(
- '+', null, LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE,
+ } else if (hasOneDigitAlternate(key)) {
+ mKeyCodes[0] = primaryCode = key.popupCharacters.charAt(0);
+ // when there is only one alternate character, send it as key action.
+ getOnKeyboardActionListener().onKey(primaryCode, mKeyCodes,
+ LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE,
LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE);
return true;
} else {
@@ -145,10 +144,10 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
* that could be due to a multi-touch being treated as a move by the firmware or hardware.
* Once a sudden jump is detected, all subsequent move events are discarded
* until an UP is received.<P>
- * When a sudden jump is detected, an UP event is simulated at the last position and when
+ * When a sudden jump is detected, an UP event is simulated at the last position and when
* the sudden moves subside, a DOWN event is simulated for the second key.
* @param me the motion event
- * @return true if the event was consumed, so that it doesn't continue to be handled by
+ * @return true if the event was consumed, so that it doesn't continue to be handled by
* KeyboardView.
*/
private boolean handleSuddenJump(MotionEvent me) {
@@ -226,11 +225,10 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
invalidate();
}
- // If an extension keyboard is visible or this is an extension keyboard, don't look
- // for sudden jumps. Otherwise, if there was a sudden jump, return without processing the
- // actual motion event.
- if (!mExtensionVisible && !mIsExtensionType
- && handleSuddenJump(me)) return true;
+ // If there was a sudden jump, return without processing the actual motion event.
+ if (handleSuddenJump(me))
+ return true;
+
// Reset any bounding box controls in the keyboard
if (me.getAction() == MotionEvent.ACTION_DOWN) {
keyboard.keyReleased();
@@ -248,154 +246,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
}
}
- // If we don't have an extension keyboard, don't go any further.
- if (keyboard.getExtension() == 0) {
- return super.onTouchEvent(me);
- }
- // If the motion event is above the keyboard and it's not an UP event coming
- // even before the first MOVE event into the extension area
- if (me.getY() < 0 && (mExtensionVisible || me.getAction() != MotionEvent.ACTION_UP)) {
- if (mExtensionVisible) {
- int action = me.getAction();
- if (mFirstEvent) action = MotionEvent.ACTION_DOWN;
- mFirstEvent = false;
- MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
- action,
- me.getX(), me.getY() + mExtension.getHeight(), me.getMetaState());
- boolean result = mExtension.onTouchEvent(translated);
- translated.recycle();
- if (me.getAction() == MotionEvent.ACTION_UP
- || me.getAction() == MotionEvent.ACTION_CANCEL) {
- closeExtension();
- }
- return result;
- } else {
- if (openExtension()) {
- MotionEvent cancel = MotionEvent.obtain(me.getDownTime(), me.getEventTime(),
- MotionEvent.ACTION_CANCEL, me.getX() - 100, me.getY() - 100, 0);
- super.onTouchEvent(cancel);
- cancel.recycle();
- if (mExtension.getHeight() > 0) {
- MotionEvent translated = MotionEvent.obtain(me.getEventTime(),
- me.getEventTime(),
- MotionEvent.ACTION_DOWN,
- me.getX(), me.getY() + mExtension.getHeight(),
- me.getMetaState());
- mExtension.onTouchEvent(translated);
- translated.recycle();
- } else {
- mFirstEvent = true;
- }
- // Stop processing multi-touch errors
- mDisableDisambiguation = true;
- }
- return true;
- }
- } else if (mExtensionVisible) {
- closeExtension();
- // Send a down event into the main keyboard first
- MotionEvent down = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
- MotionEvent.ACTION_DOWN,
- me.getX(), me.getY(), me.getMetaState());
- super.onTouchEvent(down);
- down.recycle();
- // Send the actual event
- return super.onTouchEvent(me);
- } else {
- return super.onTouchEvent(me);
- }
- }
-
- private void setExtensionType(boolean isExtensionType) {
- mIsExtensionType = isExtensionType;
- }
-
- private boolean openExtension() {
- // If the current keyboard is not visible, don't show the popup
- if (!isShown()) {
- return false;
- }
- if (((LatinKeyboard) getKeyboard()).getExtension() == 0) return false;
- makePopupWindow();
- mExtensionVisible = true;
- return true;
- }
-
- private void makePopupWindow() {
- if (mExtensionPopup == null) {
- int[] windowLocation = new int[2];
- mExtensionPopup = new PopupWindow(getContext());
- mExtensionPopup.setBackgroundDrawable(null);
- LayoutInflater li = (LayoutInflater) getContext().getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- mExtension = (LatinKeyboardView) li.inflate(mExtensionLayoutResId == 0 ?
- R.layout.input_trans : mExtensionLayoutResId, null);
- mExtension.setExtensionType(true);
- mExtension.setOnKeyboardActionListener(
- new ExtensionKeyboardListener(getOnKeyboardActionListener()));
- mExtension.setPopupParent(this);
- mExtension.setPopupOffset(0, -windowLocation[1]);
- Keyboard keyboard;
- mExtension.setKeyboard(keyboard = new LatinKeyboard(getContext(),
- ((LatinKeyboard) getKeyboard()).getExtension()));
- mExtensionPopup.setContentView(mExtension);
- mExtensionPopup.setWidth(getWidth());
- mExtensionPopup.setHeight(keyboard.getHeight());
- mExtensionPopup.setAnimationStyle(-1);
- getLocationInWindow(windowLocation);
- // TODO: Fix the "- 30".
- mExtension.setPopupOffset(0, -windowLocation[1] - 30);
- mExtensionPopup.showAtLocation(this, 0, 0, -keyboard.getHeight()
- + windowLocation[1]);
- } else {
- mExtension.setVisibility(VISIBLE);
- }
- }
-
- @Override
- public void closing() {
- super.closing();
- if (mExtensionPopup != null && mExtensionPopup.isShowing()) {
- mExtensionPopup.dismiss();
- mExtensionPopup = null;
- }
- }
-
- private void closeExtension() {
- mExtension.closing();
- mExtension.setVisibility(INVISIBLE);
- mExtensionVisible = false;
- }
-
- private static class ExtensionKeyboardListener implements OnKeyboardActionListener {
- private OnKeyboardActionListener mTarget;
- ExtensionKeyboardListener(OnKeyboardActionListener target) {
- mTarget = target;
- }
- public void onKey(int primaryCode, int[] keyCodes, int x, int y) {
- mTarget.onKey(primaryCode, keyCodes, x, y);
- }
- public void onPress(int primaryCode) {
- mTarget.onPress(primaryCode);
- }
- public void onRelease(int primaryCode) {
- mTarget.onRelease(primaryCode);
- }
- public void onText(CharSequence text) {
- mTarget.onText(text);
- }
- public void swipeDown() {
- // Don't pass through
- }
- public void swipeLeft() {
- // Don't pass through
- }
- public void swipeRight() {
- // Don't pass through
- }
- public void swipeUp() {
- // Don't pass through
- }
+ return super.onTouchEvent(me);
}
/**************************** INSTRUMENTATION *******************************/
@@ -404,9 +255,9 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
static final boolean DEBUG_LINE = false;
private static final int MSG_TOUCH_DOWN = 1;
private static final int MSG_TOUCH_UP = 2;
-
+
Handler mHandler2;
-
+
private String mStringToPlay;
private int mStringIndex;
private boolean mDownDelivered;
@@ -426,7 +277,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
removeMessages(MSG_TOUCH_DOWN);
removeMessages(MSG_TOUCH_UP);
if (mPlaying == false) return;
-
+
switch (msg.what) {
case MSG_TOUCH_DOWN:
if (mStringIndex >= mStringToPlay.length()) {
@@ -434,7 +285,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
return;
}
char c = mStringToPlay.charAt(mStringIndex);
- while (c > 255 || mAsciiKeys[(int) c] == null) {
+ while (c > 255 || mAsciiKeys[c] == null) {
mStringIndex++;
if (mStringIndex >= mStringToPlay.length()) {
mPlaying = false;
@@ -444,8 +295,8 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
}
int x = mAsciiKeys[c].x + 10;
int y = mAsciiKeys[c].y + 26;
- MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(),
- SystemClock.uptimeMillis(),
+ MotionEvent me = MotionEvent.obtain(SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
MotionEvent.ACTION_DOWN, x, y, 0);
LatinKeyboardView.this.dispatchTouchEvent(me);
me.recycle();
@@ -458,9 +309,9 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
int x2 = mAsciiKeys[cUp].x + 10;
int y2 = mAsciiKeys[cUp].y + 26;
mStringIndex++;
-
- MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(),
- SystemClock.uptimeMillis(),
+
+ MotionEvent me2 = MotionEvent.obtain(SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
MotionEvent.ACTION_UP, x2, y2, 0);
LatinKeyboardView.this.dispatchTouchEvent(me2);
me2.recycle();
@@ -481,7 +332,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
// Get the keys on this keyboard
for (int i = 0; i < keys.size(); i++) {
int code = keys.get(i).codes[0];
- if (code >= 0 && code <= 255) {
+ if (code >= 0 && code <= 255) {
mAsciiKeys[code] = keys.get(i);
}
}
diff --git a/java/src/com/android/inputmethod/latin/ModifierKeyState.java b/java/src/com/android/inputmethod/latin/ModifierKeyState.java
new file mode 100644
index 000000000..097e87abe
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/ModifierKeyState.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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;
+
+class ModifierKeyState {
+ private static final int RELEASING = 0;
+ private static final int PRESSING = 1;
+ private static final int MOMENTARY = 2;
+
+ private int mState = RELEASING;
+
+ public void onPress() {
+ mState = PRESSING;
+ }
+
+ public void onRelease() {
+ mState = RELEASING;
+ }
+
+ public void onOtherKeyPressed() {
+ if (mState == PRESSING)
+ mState = MOMENTARY;
+ }
+
+ public boolean isMomentary() {
+ return mState == MOMENTARY;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/PointerTracker.java b/java/src/com/android/inputmethod/latin/PointerTracker.java
new file mode 100644
index 000000000..2685e87c6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/PointerTracker.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * 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;
+
+import com.android.inputmethod.latin.LatinKeyboardBaseView.OnKeyboardActionListener;
+import com.android.inputmethod.latin.LatinKeyboardBaseView.UIHandler;
+
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.Keyboard.Key;
+import android.util.Log;
+import android.view.ViewConfiguration;
+
+public class PointerTracker {
+ private static final String TAG = "PointerTracker";
+ private static final boolean DEBUG = false;
+ private static final boolean DEBUG_MOVE = DEBUG && true;
+
+ public interface UIProxy {
+ public void invalidateKey(Key key);
+ public void showPreview(int keyIndex, PointerTracker tracker);
+ }
+
+ public final int mPointerId;
+
+ // Timing constants
+ private static final int REPEAT_START_DELAY = 400;
+ /* package */ static final int REPEAT_INTERVAL = 50; // ~20 keys per second
+ private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
+ private static final int MULTITAP_INTERVAL = 800; // milliseconds
+ private static final int KEY_DEBOUNCE_TIME = 70;
+
+ // Miscellaneous constants
+ private static final int NOT_A_KEY = LatinKeyboardBaseView.NOT_A_KEY;
+ private static final int[] KEY_DELETE = { Keyboard.KEYCODE_DELETE };
+
+ private final UIProxy mProxy;
+ private final UIHandler mHandler;
+ private final KeyDetector mKeyDetector;
+ private OnKeyboardActionListener mListener;
+
+ private Key[] mKeys;
+ private int mKeyDebounceThresholdSquared = -1;
+
+ private int mCurrentKey = NOT_A_KEY;
+ private int mStartX;
+ private int mStartY;
+ private long mDownTime;
+
+ // true if event is already translated to a key action (long press or mini-keyboard)
+ private boolean mKeyAlreadyProcessed;
+
+ // for move de-bouncing
+ private int mLastCodeX;
+ private int mLastCodeY;
+ private int mLastX;
+ private int mLastY;
+
+ // for time de-bouncing
+ private int mLastKey;
+ private long mLastKeyTime;
+ private long mLastMoveTime;
+ private long mCurrentKeyTime;
+
+ // For multi-tap
+ private int mLastSentIndex;
+ private int mTapCount;
+ private long mLastTapTime;
+ private boolean mInMultiTap;
+ private final StringBuilder mPreviewLabel = new StringBuilder(1);
+
+ // pressed key
+ private int mPreviousKey = NOT_A_KEY;
+
+ public PointerTracker(int id, UIHandler handler, KeyDetector keyDetector, UIProxy proxy) {
+ if (proxy == null || handler == null || keyDetector == null)
+ throw new NullPointerException();
+ mPointerId = id;
+ mProxy = proxy;
+ mHandler = handler;
+ mKeyDetector = keyDetector;
+ resetMultiTap();
+ }
+
+ public void setOnKeyboardActionListener(OnKeyboardActionListener listener) {
+ mListener = listener;
+ }
+
+ public void setKeyboard(Key[] keys, float hysteresisPixel) {
+ if (keys == null || hysteresisPixel < 1.0f)
+ throw new IllegalArgumentException();
+ mKeys = keys;
+ mKeyDebounceThresholdSquared = (int)(hysteresisPixel * hysteresisPixel);
+ }
+
+ private boolean isValidKeyIndex(int keyIndex) {
+ return keyIndex >= 0 && keyIndex < mKeys.length;
+ }
+
+ public Key getKey(int keyIndex) {
+ return isValidKeyIndex(keyIndex) ? mKeys[keyIndex] : null;
+ }
+
+ public boolean isModifier() {
+ Key key = getKey(mCurrentKey);
+ if (key == null)
+ return false;
+ int primaryCode = key.codes[0];
+ // TODO: KEYCODE_MODE_CHANGE (symbol) will be also a modifier key
+ return primaryCode == Keyboard.KEYCODE_SHIFT;
+ }
+
+ public void updateKey(int keyIndex) {
+ if (mKeyAlreadyProcessed)
+ return;
+ int oldKeyIndex = mPreviousKey;
+ mPreviousKey = keyIndex;
+ if (keyIndex != oldKeyIndex) {
+ if (isValidKeyIndex(oldKeyIndex)) {
+ // if new key index is not a key, old key was just released inside of the key.
+ final boolean inside = (keyIndex == NOT_A_KEY);
+ mKeys[oldKeyIndex].onReleased(inside);
+ mProxy.invalidateKey(mKeys[oldKeyIndex]);
+ }
+ if (isValidKeyIndex(keyIndex)) {
+ mKeys[keyIndex].onPressed();
+ mProxy.invalidateKey(mKeys[keyIndex]);
+ }
+ }
+ }
+
+ public void setAlreadyProcessed() {
+ mKeyAlreadyProcessed = true;
+ }
+
+ public void onDownEvent(int x, int y, long eventTime) {
+ int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+ mCurrentKey = keyIndex;
+ mStartX = x;
+ mStartY = y;
+ mDownTime = eventTime;
+ mKeyAlreadyProcessed = false;
+ startMoveDebouncing(x, y);
+ startTimeDebouncing(eventTime);
+ checkMultiTap(eventTime, keyIndex);
+ if (mListener != null) {
+ int primaryCode = isValidKeyIndex(keyIndex) ? mKeys[keyIndex].codes[0] : 0;
+ mListener.onPress(primaryCode);
+ }
+ if (isValidKeyIndex(keyIndex)) {
+ if (mKeys[keyIndex].repeatable) {
+ repeatKey(keyIndex);
+ mHandler.startKeyRepeatTimer(REPEAT_START_DELAY, keyIndex, this);
+ }
+ mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
+ }
+ showKeyPreviewAndUpdateKey(keyIndex);
+ updateMoveDebouncing(x, y);
+ if (DEBUG)
+ debugLog("onDownEvent:", x, y);
+ }
+
+ public void onMoveEvent(int x, int y, long eventTime) {
+ if (mKeyAlreadyProcessed)
+ return;
+ int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+ if (isValidKeyIndex(keyIndex)) {
+ if (mCurrentKey == NOT_A_KEY) {
+ updateTimeDebouncing(eventTime);
+ mCurrentKey = keyIndex;
+ mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
+ } else if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) {
+ updateTimeDebouncing(eventTime);
+ } else {
+ resetMultiTap();
+ resetTimeDebouncing(eventTime, mCurrentKey);
+ resetMoveDebouncing();
+ mCurrentKey = keyIndex;
+ mHandler.startLongPressTimer(LONGPRESS_TIMEOUT, keyIndex, this);
+ }
+ } else {
+ if (mCurrentKey != NOT_A_KEY) {
+ updateTimeDebouncing(eventTime);
+ mCurrentKey = keyIndex;
+ mHandler.cancelLongPressTimer();
+ } else if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) {
+ updateTimeDebouncing(eventTime);
+ } else {
+ resetMultiTap();
+ resetTimeDebouncing(eventTime, mCurrentKey);
+ resetMoveDebouncing();
+ mCurrentKey = keyIndex;
+ mHandler.cancelLongPressTimer();
+ }
+ }
+ /*
+ * While time debouncing is in effect, mCurrentKey holds the new key and this tracker
+ * holds the last key. At ACTION_UP event if time debouncing will be in effect
+ * eventually, the last key should be sent as the result. In such case mCurrentKey
+ * should not be showed as popup preview.
+ */
+ showKeyPreviewAndUpdateKey(isMinorTimeBounce() ? mLastKey : mCurrentKey);
+ updateMoveDebouncing(x, y);
+ if (DEBUG_MOVE)
+ debugLog("onMoveEvent:", x, y);
+ }
+
+ public void onUpEvent(int x, int y, long eventTime) {
+ if (mKeyAlreadyProcessed)
+ return;
+ if (DEBUG)
+ debugLog("onUpEvent :", x, y);
+ int keyIndex = mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
+ boolean wasInKeyRepeat = mHandler.isInKeyRepeat();
+ mHandler.cancelKeyTimers();
+ mHandler.cancelPopupPreview();
+ if (isMinorMoveBounce(x, y, keyIndex, mCurrentKey)) {
+ updateTimeDebouncing(eventTime);
+ } else {
+ resetMultiTap();
+ resetTimeDebouncing(eventTime, mCurrentKey);
+ mCurrentKey = keyIndex;
+ }
+ if (isMinorTimeBounce()) {
+ mCurrentKey = mLastKey;
+ x = mLastCodeX;
+ y = mLastCodeY;
+ }
+ showKeyPreviewAndUpdateKey(NOT_A_KEY);
+ // If we're not on a repeating key (which sends on a DOWN event)
+ if (!wasInKeyRepeat) {
+ detectAndSendKey(mCurrentKey, (int)x, (int)y, eventTime);
+ }
+ if (isValidKeyIndex(keyIndex))
+ mProxy.invalidateKey(mKeys[keyIndex]);
+ }
+
+ public void onCancelEvent(int x, int y, long eventTime) {
+ if (DEBUG)
+ debugLog("onCancelEvt:", x, y);
+ mHandler.cancelKeyTimers();
+ mHandler.cancelPopupPreview();
+ showKeyPreviewAndUpdateKey(NOT_A_KEY);
+ int keyIndex = mCurrentKey;
+ if (isValidKeyIndex(keyIndex))
+ mProxy.invalidateKey(mKeys[keyIndex]);
+ }
+
+ public void repeatKey(int keyIndex) {
+ Key key = getKey(keyIndex);
+ if (key != null) {
+ // While key is repeating, because there is no need to handle multi-tap key, we can
+ // pass -1 as eventTime argument.
+ detectAndSendKey(keyIndex, key.x, key.y, -1);
+ }
+ }
+
+ public int getLastX() {
+ return mLastX;
+ }
+
+ public int getLastY() {
+ return mLastY;
+ }
+
+ public long getDownTime() {
+ return mDownTime;
+ }
+
+ // These package scope methods are only for debugging purpose.
+ /* package */ int getStartX() {
+ return mStartX;
+ }
+
+ /* package */ int getStartY() {
+ return mStartY;
+ }
+
+ private void startMoveDebouncing(int x, int y) {
+ mLastCodeX = x;
+ mLastCodeY = y;
+ }
+
+ private void updateMoveDebouncing(int x, int y) {
+ mLastX = x;
+ mLastY = y;
+ }
+
+ private void resetMoveDebouncing() {
+ mLastCodeX = mLastX;
+ mLastCodeY = mLastY;
+ }
+
+ private boolean isMinorMoveBounce(int x, int y, int newKey, int curKey) {
+ if (mKeys == null || mKeyDebounceThresholdSquared < 0)
+ throw new IllegalStateException("keyboard and/or hysteresis not set");
+ if (newKey == curKey) {
+ return true;
+ } else if (isValidKeyIndex(curKey)) {
+ return getSquareDistanceToKeyEdge(x, y, mKeys[curKey])
+ < mKeyDebounceThresholdSquared;
+ } else {
+ return false;
+ }
+ }
+
+ private static int getSquareDistanceToKeyEdge(int x, int y, Key key) {
+ final int left = key.x;
+ final int right = key.x + key.width;
+ final int top = key.y;
+ final int bottom = key.y + key.height;
+ final int edgeX = x < left ? left : (x > right ? right : x);
+ final int edgeY = y < top ? top : (y > bottom ? bottom : y);
+ final int dx = x - edgeX;
+ final int dy = y - edgeY;
+ return dx * dx + dy * dy;
+ }
+
+ private void startTimeDebouncing(long eventTime) {
+ mLastKey = NOT_A_KEY;
+ mLastKeyTime = 0;
+ mCurrentKeyTime = 0;
+ mLastMoveTime = eventTime;
+ }
+
+ private void updateTimeDebouncing(long eventTime) {
+ mCurrentKeyTime += eventTime - mLastMoveTime;
+ mLastMoveTime = eventTime;
+ }
+
+ private void resetTimeDebouncing(long eventTime, int currentKey) {
+ mLastKey = currentKey;
+ mLastKeyTime = mCurrentKeyTime + eventTime - mLastMoveTime;
+ mCurrentKeyTime = 0;
+ mLastMoveTime = eventTime;
+ }
+
+ private boolean isMinorTimeBounce() {
+ return mCurrentKeyTime < mLastKeyTime && mCurrentKeyTime < KEY_DEBOUNCE_TIME
+ && mLastKey != NOT_A_KEY;
+ }
+
+ private void showKeyPreviewAndUpdateKey(int keyIndex) {
+ updateKey(keyIndex);
+ if (!isModifier())
+ mProxy.showPreview(keyIndex, this);
+ }
+
+ private void detectAndSendKey(int index, int x, int y, long eventTime) {
+ if (isValidKeyIndex(index)) {
+ final Key key = mKeys[index];
+ OnKeyboardActionListener listener = mListener;
+ if (key.text != null) {
+ if (listener != null) {
+ listener.onText(key.text);
+ listener.onRelease(NOT_A_KEY);
+ }
+ } else {
+ int code = key.codes[0];
+ //TextEntryState.keyPressedAt(key, x, y);
+ int[] codes = mKeyDetector.newCodeArray();
+ mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
+ // Multi-tap
+ if (mInMultiTap) {
+ if (mTapCount != -1) {
+ mListener.onKey(Keyboard.KEYCODE_DELETE, KEY_DELETE, x, y);
+ } else {
+ mTapCount = 0;
+ }
+ code = key.codes[mTapCount];
+ }
+ /*
+ * Swap the first and second values in the codes array if the primary code is not
+ * the first value but the second value in the array. This happens when key
+ * debouncing is in effect.
+ */
+ if (codes.length >= 2 && codes[0] != code && codes[1] == code) {
+ codes[1] = codes[0];
+ codes[0] = code;
+ }
+ if (listener != null) {
+ listener.onKey(code, codes, x, y);
+ listener.onRelease(code);
+ }
+ }
+ mLastSentIndex = index;
+ mLastTapTime = eventTime;
+ }
+ }
+
+ /**
+ * Handle multi-tap keys by producing the key label for the current multi-tap state.
+ */
+ public CharSequence getPreviewText(Key key) {
+ if (mInMultiTap) {
+ // Multi-tap
+ mPreviewLabel.setLength(0);
+ mPreviewLabel.append((char) key.codes[mTapCount < 0 ? 0 : mTapCount]);
+ return mPreviewLabel;
+ } else {
+ return key.label;
+ }
+ }
+
+ private void resetMultiTap() {
+ mLastSentIndex = NOT_A_KEY;
+ mTapCount = 0;
+ mLastTapTime = -1;
+ mInMultiTap = false;
+ }
+
+ private void checkMultiTap(long eventTime, int keyIndex) {
+ Key key = getKey(keyIndex);
+ if (key == null)
+ return;
+
+ final boolean isMultiTap =
+ (eventTime < mLastTapTime + MULTITAP_INTERVAL && keyIndex == mLastSentIndex);
+ if (key.codes.length > 1) {
+ mInMultiTap = true;
+ if (isMultiTap) {
+ mTapCount = (mTapCount + 1) % key.codes.length;
+ return;
+ } else {
+ mTapCount = -1;
+ return;
+ }
+ }
+ if (!isMultiTap) {
+ resetMultiTap();
+ }
+ }
+
+ private void debugLog(String title, int x, int y) {
+ Key key = getKey(mCurrentKey);
+ final String code;
+ if (key == null) {
+ code = "----";
+ } else {
+ int primaryCode = key.codes[0];
+ code = String.format((primaryCode < 0) ? "%4d" : "0x%02x", primaryCode);
+ }
+ Log.d(TAG, String.format("%s [%d] %3d,%3d %s %s", title, mPointerId, x, y, code,
+ isModifier() ? "modifier" : ""));
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java
index eae2d7f08..d17bedb56 100644
--- a/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java
+++ b/java/src/com/android/inputmethod/latin/ProximityKeyDetector.java
@@ -16,70 +16,43 @@
package com.android.inputmethod.latin;
-import android.inputmethodservice.Keyboard;
import android.inputmethodservice.Keyboard.Key;
import java.util.Arrays;
-class ProximityKeyDetector {
+class ProximityKeyDetector extends KeyDetector {
private static final int MAX_NEARBY_KEYS = 12;
- private Keyboard mKeyboard;
- private Key[] mKeys;
-
- private boolean mProximityCorrectOn;
- private int mProximityThresholdSquare;
-
// working area
private int[] mDistances = new int[MAX_NEARBY_KEYS];
- public void setKeyboard(Keyboard keyboard, Key[] keys) {
- if (keyboard == null || keys == null)
- throw new NullPointerException();
- mKeyboard = keyboard;
- mKeys = keys;
- }
-
- public void setProximityCorrectionEnabled(boolean enabled) {
- mProximityCorrectOn = enabled;
- }
-
- public boolean isProximityCorrectionEnabled() {
- return mProximityCorrectOn;
- }
-
- public void setProximityThreshold(int threshold) {
- mProximityThresholdSquare = threshold * threshold;
- }
-
- public int[] newCodeArray() {
- int[] codes = new int[MAX_NEARBY_KEYS];
- Arrays.fill(codes, LatinKeyboardBaseView.NOT_A_KEY);
- return codes;
+ @Override
+ protected int getMaxNearbyKeys() {
+ return MAX_NEARBY_KEYS;
}
+ @Override
public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) {
- final Key[] keys = mKeys;
- if (keys == null)
- throw new IllegalStateException("keyboard isn't set");
- // mKeyboard is guaranteed not null at setKeybaord() method
+ final Key[] keys = getKeys();
+ final int touchX = getTouchX(x);
+ final int touchY = getTouchY(y);
int primaryIndex = LatinKeyboardBaseView.NOT_A_KEY;
int closestKey = LatinKeyboardBaseView.NOT_A_KEY;
int closestKeyDist = mProximityThresholdSquare + 1;
int[] distances = mDistances;
Arrays.fill(distances, Integer.MAX_VALUE);
- int [] nearestKeyIndices = mKeyboard.getNearestKeys(x, y);
+ int [] nearestKeyIndices = mKeyboard.getNearestKeys(touchX, touchY);
final int keyCount = nearestKeyIndices.length;
for (int i = 0; i < keyCount; i++) {
final Key key = keys[nearestKeyIndices[i]];
int dist = 0;
- boolean isInside = key.isInside(x,y);
+ boolean isInside = key.isInside(touchX, touchY);
if (isInside) {
primaryIndex = nearestKeyIndices[i];
}
if (((mProximityCorrectOn
- && (dist = key.squaredDistanceFrom(x, y)) < mProximityThresholdSquare)
+ && (dist = key.squaredDistanceFrom(touchX, touchY)) < mProximityThresholdSquare)
|| isInside)
&& key.codes[0] > 32) {
// Find insertion point
diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java
index f24c180d0..4c54dd3c5 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInput.java
+++ b/java/src/com/android/inputmethod/voice/VoiceInput.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.voice;
+import com.android.inputmethod.latin.EditingUtil;
import com.android.inputmethod.latin.R;
import android.content.ContentResolver;
@@ -30,6 +31,7 @@ import android.speech.RecognitionListener;
import android.speech.SpeechRecognizer;
import android.speech.RecognizerIntent;
import android.util.Log;
+import android.view.inputmethod.InputConnection;
import android.view.View;
import android.view.View.OnClickListener;
@@ -423,8 +425,14 @@ public class VoiceInput implements OnClickListener {
mLogger.textModifiedByTypingDeletion(length);
}
- public void logTextModifiedByChooseSuggestion(int length) {
- mLogger.textModifiedByChooseSuggestion(length);
+ public void logTextModifiedByChooseSuggestion(String suggestion, int index,
+ String wordSeparators, InputConnection ic) {
+ EditingUtil.Range range = new EditingUtil.Range();
+ String wordToBeReplaced = EditingUtil.getWordAtCursor(ic, wordSeparators, range);
+ // If we enable phrase-based alternatives, only send up the first word
+ // in suggestion and wordToBeReplaced.
+ mLogger.textModifiedByChooseSuggestion(suggestion.length(), wordToBeReplaced.length(),
+ index, wordToBeReplaced, suggestion);
}
public void logKeyboardWarningDialogShown() {
@@ -455,10 +463,6 @@ public class VoiceInput implements OnClickListener {
mLogger.voiceInputDelivered(length);
}
- public void logNBestChoose(int index) {
- mLogger.nBestChoose(index);
- }
-
public void logInputEnded() {
mLogger.inputEnded();
}
diff --git a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java
index 9d3a92037..4d50f5ee8 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInputLogger.java
+++ b/java/src/com/android/inputmethod/voice/VoiceInputLogger.java
@@ -178,20 +178,19 @@ public class VoiceInputLogger {
mContext.sendBroadcast(i);
}
- public void textModifiedByChooseSuggestion(int length) {
+ public void textModifiedByChooseSuggestion(int suggestionLength, int replacedPhraseLength,
+ int index, String before, String after) {
Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.TEXT_MODIFIED);
- i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, length);
+ i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_LENGTH, suggestionLength);
+ i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_REPLACED_LENGTH, replacedPhraseLength);
i.putExtra(LoggingEvents.VoiceIme.EXTRA_TEXT_MODIFIED_TYPE,
LoggingEvents.VoiceIme.TEXT_MODIFIED_TYPE_CHOOSE_SUGGESTION);
- mContext.sendBroadcast(i);
- }
-
- public void nBestChoose(int index) {
- Intent i = newLoggingBroadcast(LoggingEvents.VoiceIme.N_BEST_CHOOSE);
i.putExtra(LoggingEvents.VoiceIme.EXTRA_N_BEST_CHOOSE_INDEX, index);
+ i.putExtra(LoggingEvents.VoiceIme.EXTRA_BEFORE_N_BEST_CHOOSE, before);
+ i.putExtra(LoggingEvents.VoiceIme.EXTRA_AFTER_N_BEST_CHOOSE, after);
mContext.sendBroadcast(i);
}
-
+
public void inputEnded() {
mContext.sendBroadcast(newLoggingBroadcast(LoggingEvents.VoiceIme.INPUT_ENDED));
}