aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
authorsatok <satok@google.com>2011-02-10 17:31:37 +0900
committersatok <satok@google.com>2011-02-10 17:31:37 +0900
commita7ea6cad8269c2b621dbf585ed90121b495f6ebc (patch)
treeefe9106dacb45cf2cf4f17d80f870a030436ca53 /java/src
parentb7b83125544964bf128e07456edfd0170d889dc4 (diff)
parent03b5579b3b7380f8e12519cca1662317282c6bd9 (diff)
downloadlatinime-a7ea6cad8269c2b621dbf585ed90121b495f6ebc.tar.gz
latinime-a7ea6cad8269c2b621dbf585ed90121b495f6ebc.tar.xz
latinime-a7ea6cad8269c2b621dbf585ed90121b495f6ebc.zip
Merge remote branch 'goog/master' into merge
Conflicts: java/res/xml/method.xml java/src/com/android/inputmethod/latin/LatinIME.java java/src/com/android/inputmethod/latin/SubtypeSwitcher.java java/src/com/android/inputmethod/voice/VoiceIMEConnector.java Change-Id: I9b62da120dcc08327fde92459ee5c7564a5eb6b8
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java28
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardParser.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java7
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java297
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboard.java76
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java17
-rw-r--r--java/src/com/android/inputmethod/keyboard/MiniKeyboard.java35
-rw-r--r--java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java201
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java55
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java12
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java53
-rw-r--r--java/src/com/android/inputmethod/latin/CandidateView.java38
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettings.java10
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java184
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java16
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java108
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java73
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java96
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java30
-rw-r--r--java/src/com/android/inputmethod/voice/RecognitionView.java27
-rw-r--r--java/src/com/android/inputmethod/voice/VoiceIMEConnector.java160
-rw-r--r--java/src/com/android/inputmethod/voice/VoiceInput.java49
24 files changed, 1018 insertions, 578 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 35bafea80..23886ad97 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -95,6 +95,8 @@ public class Key {
public boolean mPressed;
/** If this is a sticky key, is it on? */
public boolean mOn;
+ /** Key is enabled or not. */
+ public boolean mEnabled = true;
private final static int[] KEY_STATE_NORMAL_ON = {
android.R.attr.state_checkable,
@@ -385,8 +387,9 @@ public class Key {
* @see android.graphics.drawable.StateListDrawable#setState(int[])
*/
public int[] getCurrentDrawableState() {
+ final boolean pressed = mEnabled && mPressed;
if (isFunctionalKey()) {
- if (mPressed) {
+ if (pressed) {
return KEY_STATE_FUNCTIONAL_PRESSED;
} else {
return KEY_STATE_FUNCTIONAL_NORMAL;
@@ -396,20 +399,20 @@ public class Key {
int[] states = KEY_STATE_NORMAL;
if (mOn) {
- if (mPressed) {
+ if (pressed) {
states = KEY_STATE_PRESSED_ON;
} else {
states = KEY_STATE_NORMAL_ON;
}
} else {
if (mSticky) {
- if (mPressed) {
+ if (pressed) {
states = KEY_STATE_PRESSED_OFF;
} else {
states = KEY_STATE_NORMAL_OFF;
}
} else {
- if (mPressed) {
+ if (pressed) {
states = KEY_STATE_PRESSED;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index e7a9d8513..3979fb53e 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
public abstract class KeyDetector {
@@ -108,4 +109,31 @@ public abstract class KeyDetector {
* @return The nearest key index
*/
abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys);
+
+ /**
+ * Compute the most common key width in order to use it as proximity key detection threshold.
+ *
+ * @param keyboard The keyboard to compute the most common key width
+ * @return The most common key width in the keyboard
+ */
+ public static int getMostCommonKeyWidth(Keyboard keyboard) {
+ if (keyboard == null) return 0;
+ final List<Key> keys = keyboard.getKeys();
+ if (keys == null || keys.size() == 0) return 0;
+ final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
+ int maxCount = 0;
+ int mostCommonWidth = 0;
+ for (Key key : keys) {
+ final Integer width = key.mWidth + key.mGap;
+ Integer count = histogram.get(width);
+ if (count == null)
+ count = 0;
+ histogram.put(width, ++count);
+ if (count > maxCount) {
+ maxCount = count;
+ mostCommonWidth = width;
+ }
+ }
+ return mostCommonWidth;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 863421f18..3a0bf53ab 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -107,11 +107,6 @@ public class Keyboard {
private final HashSet<Key> mShiftLockEnabled = new HashSet<Key>();
private final KeyboardShiftState mShiftState = new KeyboardShiftState();
- /** Space key and its icons */
- protected Key mSpaceKey;
- protected Drawable mSpaceIcon;
- protected Drawable mSpacePreviewIcon;
-
/** Total height of the keyboard, including the padding and keys */
private int mTotalHeight;
@@ -350,12 +345,6 @@ public class Keyboard {
return mId != null && mId.isNumberKeyboard();
}
- public void setSpaceKey(Key space) {
- mSpaceKey = space;
- mSpaceIcon = space.getIcon();
- mSpacePreviewIcon = space.getPreviewIcon();
- }
-
private void computeNearestNeighbors() {
// Round-up so we don't have any pixels outside the grid
mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
index c41d57075..e8324e5fd 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
@@ -286,8 +286,6 @@ public class KeyboardParser {
keys.add(key);
if (key.mCode == Keyboard.CODE_SHIFT)
mKeyboard.getShiftKeys().add(key);
- if (key.mCode == Keyboard.CODE_SPACE)
- mKeyboard.setSpaceKey(key);
endKey(key);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 558de66a4..2648ff3d4 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -17,11 +17,11 @@
package com.android.inputmethod.keyboard;
import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Settings;
import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.Utils;
import android.content.Context;
import android.content.SharedPreferences;
@@ -222,8 +222,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
keyboard.setShifted(false);
// If the cached keyboard had been switched to another keyboard while the language was
// displayed on its spacebar, it might have had arbitrary text fade factor. In such case,
- // we should reset the text fade factor.
+ // we should reset the text fade factor. It is also applicable to shortcut key.
keyboard.setSpacebarTextFadeFactor(0.0f, null);
+ keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutAvailable(), null);
return keyboard;
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 766fdf0e6..d4c5e579b 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -68,8 +68,7 @@ import java.util.WeakHashMap;
* @attr ref R.styleable#KeyboardView_popupLayout
*/
public class KeyboardView extends View implements PointerTracker.UIProxy {
- private static final String TAG = "KeyboardView";
- private static final boolean DEBUG = false;
+ private static final String TAG = KeyboardView.class.getSimpleName();
private static final boolean DEBUG_SHOW_ALIGN = false;
private static final boolean DEBUG_KEYBOARD_GRID = false;
@@ -115,7 +114,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
private int[] mOffsetInWindow;
private int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY;
private boolean mShowPreview = true;
- private boolean mShowTouchPoints = true;
private int mPopupPreviewOffsetX;
private int mPopupPreviewOffsetY;
private int mWindowY;
@@ -125,7 +123,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
// Popup mini keyboard
private PopupWindow mMiniKeyboardPopup;
- private KeyboardView mMiniKeyboard;
+ private KeyboardView mMiniKeyboardView;
private View mMiniKeyboardParent;
private final WeakHashMap<Key, View> mMiniKeyboardCache = new WeakHashMap<Key, View>();
private int mMiniKeyboardOriginX;
@@ -134,6 +132,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
private int[] mWindowOffset;
private final float mMiniKeyboardSlideAllowance;
private int mMiniKeyboardTrackerId;
+ private final boolean mConfigShowMiniKeyboardAtTouchedPoint;
/** Listener for {@link KeyboardActionListener}. */
private KeyboardActionListener mKeyboardActionListener;
@@ -157,18 +156,20 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
// Drawing
/** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
private boolean mDrawPending;
+ /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
+ private boolean mKeyboardChanged;
/** The dirty region in the keyboard bitmap */
private final Rect mDirtyRect = new Rect();
+ /** The key to invalidate. */
+ private Key mInvalidatedKey;
+ /** The dirty region for single key drawing */
+ private final Rect mInvalidatedKeyRect = 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);
// This map caches key label text height in pixel as value and key label text size as map key.
private final HashMap<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>();
// Distance from horizontal center of the key, proportional to key label text height and width.
@@ -296,7 +297,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- TypedArray a = context.obtainStyledAttributes(
+ final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
int previewLayout = 0;
int keyTextSize = 0;
@@ -381,6 +382,8 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
mMiniKeyboardPopup = new PopupWindow(context);
mMiniKeyboardPopup.setBackgroundDrawable(null);
mMiniKeyboardPopup.setAnimationStyle(R.style.MiniKeyboardAnimation);
+ // Allow popup window to be drawn off the screen.
+ mMiniKeyboardPopup.setClippingEnabled(false);
mPaint = new Paint();
mPaint.setAntiAlias(true);
@@ -395,10 +398,12 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
// TODO: Refer frameworks/base/core/res/res/values/config.xml
mDisambiguateSwipe = res.getBoolean(R.bool.config_swipeDisambiguation);
mMiniKeyboardSlideAllowance = res.getDimension(R.dimen.mini_keyboard_slide_allowance);
+ mConfigShowMiniKeyboardAtTouchedPoint = res.getBoolean(
+ R.bool.config_show_mini_keyboard_at_touched_point);
GestureDetector.SimpleOnGestureListener listener =
new GestureDetector.SimpleOnGestureListener() {
- private boolean mProcessingDoubleTapEvent = false;
+ private boolean mProcessingShiftDoubleTapEvent = false;
@Override
public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX,
@@ -419,25 +424,39 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
}
@Override
- public boolean onDoubleTap(MotionEvent e) {
+ public boolean onDoubleTap(MotionEvent firstDown) {
if (ENABLE_CAPSLOCK_BY_DOUBLETAP && mKeyboard instanceof LatinKeyboard
&& ((LatinKeyboard) mKeyboard).isAlphaKeyboard()) {
- final int pointerIndex = e.getActionIndex();
- final int id = e.getPointerId(pointerIndex);
+ final int pointerIndex = firstDown.getActionIndex();
+ final int id = firstDown.getPointerId(pointerIndex);
final PointerTracker tracker = getPointerTracker(id);
- if (tracker.isOnShiftKey((int)e.getX(), (int)e.getY())) {
- onDoubleTapShiftKey(tracker);
- mProcessingDoubleTapEvent = true;
+ // If the first down event is on shift key.
+ if (tracker.isOnShiftKey((int)firstDown.getX(), (int)firstDown.getY())) {
+ mProcessingShiftDoubleTapEvent = true;
return true;
}
}
- mProcessingDoubleTapEvent = false;
+ mProcessingShiftDoubleTapEvent = false;
return false;
}
@Override
- public boolean onDoubleTapEvent(MotionEvent e) {
- return mProcessingDoubleTapEvent;
+ public boolean onDoubleTapEvent(MotionEvent secondTap) {
+ if (mProcessingShiftDoubleTapEvent
+ && secondTap.getAction() == MotionEvent.ACTION_DOWN) {
+ final MotionEvent secondDown = secondTap;
+ final int pointerIndex = secondDown.getActionIndex();
+ final int id = secondDown.getPointerId(pointerIndex);
+ final PointerTracker tracker = getPointerTracker(id);
+ // If the second down event is also on shift key.
+ if (tracker.isOnShiftKey((int)secondDown.getX(), (int)secondDown.getY())) {
+ onDoubleTapShiftKey(tracker);
+ return true;
+ }
+ // Otherwise these events should not be handled as double tap.
+ mProcessingShiftDoubleTapEvent = false;
+ }
+ return mProcessingShiftDoubleTapEvent;
}
};
@@ -487,10 +506,9 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance);
}
requestLayout();
- // Hint to reallocate the buffer if the size changed
mKeyboardChanged = true;
invalidateAllKeys();
- computeProximityThreshold(keyboard, mKeys);
+ mKeyDetector.setProximityThreshold(KeyDetector.getMostCommonKeyWidth(keyboard));
mMiniKeyboardCache.clear();
}
@@ -535,10 +553,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
return mColorScheme;
}
- public void setPopupParent(View v) {
- mMiniKeyboardParent = v;
- }
-
public void setPopupOffset(int x, int y) {
mPopupPreviewOffsetX = x;
mPopupPreviewOffsetY = y;
@@ -586,37 +600,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
}
}
- /**
- * Compute the most common key width and use it as proximity key detection threshold.
- * @param keyboard
- * @param keys
- */
- private void computeProximityThreshold(Keyboard keyboard, Key[] keys) {
- if (keyboard == null || keys == null || keys.length == 0) return;
- final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
- int maxCount = 0;
- int mostCommonWidth = 0;
- for (Key key : keys) {
- final Integer width = key.mWidth + key.mGap;
- Integer count = histogram.get(width);
- if (count == null)
- count = 0;
- histogram.put(width, ++count);
- if (count > maxCount) {
- maxCount = count;
- mostCommonWidth = width;
- }
- }
- mKeyDetector.setProximityThreshold(mostCommonWidth);
- }
-
- @Override
- public void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- // Release the buffer, if any and it will be reallocated on the next draw
- mBuffer = null;
- }
-
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -626,19 +609,18 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
canvas.drawBitmap(mBuffer, 0, 0, null);
}
- @SuppressWarnings("unused")
private void onBufferDraw() {
+ final int width = getWidth();
+ final int height = getHeight();
+ if (width == 0 || height == 0)
+ return;
if (mBuffer == null || mKeyboardChanged) {
- if (mBuffer == null || mKeyboardChanged &&
- (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
- // Make sure our bitmap is at least 1x1
- final int width = Math.max(1, getWidth());
- final int height = Math.max(1, getHeight());
- mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- mCanvas = new Canvas(mBuffer);
- }
- invalidateAllKeys();
mKeyboardChanged = false;
+ mDirtyRect.union(0, 0, width, height);
+ }
+ if (mBuffer == null || mBuffer.getWidth() != width || mBuffer.getHeight() != height) {
+ mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBuffer);
}
final Canvas canvas = mCanvas;
canvas.clipRect(mDirtyRect, Op.REPLACE);
@@ -647,30 +629,19 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
final Paint paint = mPaint;
final Drawable keyBackground = mKeyBackground;
- final Rect clipRegion = mClipRegion;
final Rect padding = mPadding;
final int kbdPaddingLeft = getPaddingLeft();
final int kbdPaddingTop = getPaddingTop();
final Key[] keys = mKeys;
- final Key invalidKey = mInvalidatedKey;
final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
+ final boolean drawSingleKey = (mInvalidatedKey != null
+ && mInvalidatedKeyRect.contains(mDirtyRect));
- boolean drawSingleKey = false;
- if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
- // TODO we should use Rect.inset and Rect.contains here.
- // Is clipRegion completely contained within the invalidated key?
- if (invalidKey.mX + kbdPaddingLeft - 1 <= clipRegion.left &&
- invalidKey.mY + kbdPaddingTop - 1 <= clipRegion.top &&
- invalidKey.mX + invalidKey.mWidth + kbdPaddingLeft + 1 >= clipRegion.right &&
- invalidKey.mY + invalidKey.mHeight + kbdPaddingTop + 1 >= clipRegion.bottom) {
- drawSingleKey = true;
- }
- }
canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
final int keyCount = keys.length;
for (int i = 0; i < keyCount; i++) {
final Key key = keys[i];
- if (drawSingleKey && invalidKey != key) {
+ if (drawSingleKey && key != mInvalidatedKey) {
continue;
}
int[] drawableState = key.getCurrentDrawableState();
@@ -724,8 +695,10 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
} else {
positionX = (key.mWidth + padding.left - padding.right) / 2;
paint.setTextAlign(Align.CENTER);
- if (DEBUG_SHOW_ALIGN && label.length() > 1)
- drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
+ if (DEBUG_SHOW_ALIGN) {
+ if (label.length() > 1)
+ drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
+ }
}
if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) {
paint.setColor(mKeyTextColorDisabled);
@@ -796,32 +769,13 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
canvas.drawLine(0, i * ch, cw * mKeyboard.GRID_WIDTH, i * ch, p);
}
- mInvalidatedKey = null;
// Overlay a dark rectangle to dim the keyboard
- if (mMiniKeyboard != null) {
+ if (mMiniKeyboardView != null) {
paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
- canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
- }
-
- if (DEBUG) {
- if (mShowTouchPoints) {
- 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);
- }
- }
+ canvas.drawRect(0, 0, width, height, paint);
}
+ mInvalidatedKey = null;
mDrawPending = false;
mDirtyRect.setEmpty();
}
@@ -1035,12 +989,11 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
if (key == null)
return;
mInvalidatedKey = key;
- // TODO we should clean up this and record key's region to use in onBufferDraw.
- mDirtyRect.union(key.mX + getPaddingLeft(), key.mY + getPaddingTop(),
- key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop());
+ mInvalidatedKeyRect.set(0, 0, key.mWidth, key.mHeight);
+ mInvalidatedKeyRect.offset(key.mX + getPaddingLeft(), key.mY + getPaddingTop());
+ mDirtyRect.union(mInvalidatedKeyRect);
onBufferDraw();
- invalidate(key.mX + getPaddingLeft(), key.mY + getPaddingTop(),
- key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop());
+ invalidate(mInvalidatedKeyRect);
}
private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) {
@@ -1052,7 +1005,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
Key popupKey = tracker.getKey(keyIndex);
if (popupKey == null)
return false;
- boolean result = onLongPress(popupKey);
+ boolean result = onLongPress(popupKey, tracker);
if (result) {
dismissKeyPreview();
mMiniKeyboardTrackerId = tracker.mPointerId;
@@ -1077,14 +1030,13 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
}
private View inflateMiniKeyboardContainer(Key popupKey) {
- int popupKeyboardResId = mKeyboard.getPopupKeyboardResId();
- View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null);
+ final View container = LayoutInflater.from(getContext()).inflate(mPopupLayout, null);
if (container == null)
throw new NullPointerException();
- KeyboardView miniKeyboard =
+ final KeyboardView miniKeyboardView =
(KeyboardView)container.findViewById(R.id.KeyboardView);
- miniKeyboard.setOnKeyboardActionListener(new KeyboardActionListener() {
+ miniKeyboardView.setOnKeyboardActionListener(new KeyboardActionListener() {
@Override
public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
mKeyboardActionListener.onCodeInput(primaryCode, keyCodes, x, y);
@@ -1117,14 +1069,14 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
}
});
// Override default ProximityKeyDetector.
- miniKeyboard.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance);
+ miniKeyboardView.mKeyDetector = new MiniKeyboardKeyDetector(mMiniKeyboardSlideAllowance);
// Remove gesture detector on mini-keyboard
- miniKeyboard.mGestureDetector = null;
+ miniKeyboardView.mGestureDetector = null;
- Keyboard keyboard = new MiniKeyboardBuilder(this, popupKeyboardResId, popupKey)
- .build();
- miniKeyboard.setKeyboard(keyboard);
- miniKeyboard.setPopupParent(this);
+ final Keyboard keyboard = new MiniKeyboardBuilder(this, mKeyboard.getPopupKeyboardResId(),
+ popupKey).build();
+ miniKeyboardView.setKeyboard(keyboard);
+ miniKeyboardView.mMiniKeyboardParent = this;
container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST));
@@ -1152,7 +1104,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
* @return true if the long press is handled, false otherwise. Subclasses should call the
* method on the base class if the subclass doesn't wish to handle the call.
*/
- protected boolean onLongPress(Key popupKey) {
+ protected boolean onLongPress(Key popupKey, PointerTracker tracker) {
if (popupKey.mPopupCharacters == null)
return false;
@@ -1161,93 +1113,51 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
container = inflateMiniKeyboardContainer(popupKey);
mMiniKeyboardCache.put(popupKey, container);
}
- mMiniKeyboard = (KeyboardView)container.findViewById(R.id.KeyboardView);
+ mMiniKeyboardView = (KeyboardView)container.findViewById(R.id.KeyboardView);
+ final MiniKeyboard miniKeyboard = (MiniKeyboard)mMiniKeyboardView.getKeyboard();
+
if (mWindowOffset == null) {
mWindowOffset = new int[2];
getLocationInWindow(mWindowOffset);
}
-
- // Get width of a key in the mini popup keyboard = "miniKeyWidth".
- // On the other hand, "popupKey.width" is width of the pressed key on the main keyboard.
- // We adjust the position of mini popup keyboard with the edge key in it:
- // a) When we have the leftmost key in popup keyboard directly above the pressed key
- // Right edges of both keys should be aligned for consistent default selection
- // b) When we have the rightmost key in popup keyboard directly above the pressed key
- // Left edges of both keys should be aligned for consistent default selection
- final List<Key> miniKeys = mMiniKeyboard.getKeyboard().getKeys();
- final int miniKeyWidth = miniKeys.size() > 0 ? miniKeys.get(0).mWidth : 0;
-
- // HACK: Have the leftmost number in the popup characters right above the key
- boolean isNumberAtLeftmost =
- hasMultiplePopupChars(popupKey) && isNumberAtLeftmostPopupChar(popupKey);
- int popupX = popupKey.mX + mWindowOffset[0];
- popupX += getPaddingLeft();
- if (isNumberAtLeftmost) {
- popupX += popupKey.mWidth - miniKeyWidth; // adjustment for a) described above
- popupX -= container.getPaddingLeft();
- } else {
- popupX += miniKeyWidth; // adjustment for b) described above
- popupX -= container.getMeasuredWidth();
- popupX += container.getPaddingRight();
- }
- int popupY = popupKey.mY + mWindowOffset[1];
- popupY += getPaddingTop();
- popupY -= container.getMeasuredHeight();
- popupY += container.getPaddingBottom();
+ final int pointX = (mConfigShowMiniKeyboardAtTouchedPoint) ? tracker.getLastX()
+ : popupKey.mX + popupKey.mWidth / 2;
+ final int popupX = pointX - miniKeyboard.getDefaultCoordX()
+ - container.getPaddingLeft()
+ + getPaddingLeft() + mWindowOffset[0];
+ final int popupY = popupKey.mY - mKeyboard.getVerticalGap()
+ - (container.getMeasuredHeight() - container.getPaddingBottom())
+ + getPaddingTop() + mWindowOffset[1];
final int x = popupX;
- final int y = mShowPreview && isOneRowKeys(miniKeys) ? mPopupPreviewDisplayedY : popupY;
+ final int y = mShowPreview && isOneRowKeys(miniKeyboard.getKeys())
+ ? mPopupPreviewDisplayedY : popupY;
- int adjustedX = x;
- if (x < 0) {
- adjustedX = 0;
- } else if (x > (getMeasuredWidth() - container.getMeasuredWidth())) {
- adjustedX = getMeasuredWidth() - container.getMeasuredWidth();
- }
- mMiniKeyboardOriginX = adjustedX + container.getPaddingLeft() - mWindowOffset[0];
+ mMiniKeyboardOriginX = x + container.getPaddingLeft() - mWindowOffset[0];
mMiniKeyboardOriginY = y + container.getPaddingTop() - mWindowOffset[1];
- mMiniKeyboard.setPopupOffset(adjustedX, y);
- Keyboard baseMiniKeyboard = mMiniKeyboard.getKeyboard();
- if (baseMiniKeyboard != null && baseMiniKeyboard.setShifted(mKeyboard == null
- ? false : mKeyboard.isShiftedOrShiftLocked())) {
- mMiniKeyboard.invalidateAllKeys();
+ mMiniKeyboardView.setPopupOffset(x, y);
+ if (miniKeyboard.setShifted(
+ mKeyboard == null ? false : mKeyboard.isShiftedOrShiftLocked())) {
+ mMiniKeyboardView.invalidateAllKeys();
}
// Mini keyboard needs no pop-up key preview displayed.
- mMiniKeyboard.setPreviewEnabled(false);
+ mMiniKeyboardView.setPreviewEnabled(false);
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 = SystemClock.uptimeMillis();
+ final long eventTime = SystemClock.uptimeMillis();
mMiniKeyboardPopupTime = eventTime;
- MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN, popupKey.mX
- + popupKey.mWidth / 2, popupKey.mY + popupKey.mHeight / 2, eventTime);
- mMiniKeyboard.onTouchEvent(downEvent);
+ final MotionEvent downEvent = generateMiniKeyboardMotionEvent(MotionEvent.ACTION_DOWN,
+ pointX, popupKey.mY + popupKey.mHeight / 2, eventTime);
+ mMiniKeyboardView.onTouchEvent(downEvent);
downEvent.recycle();
invalidateAllKeys();
return true;
}
- private static boolean hasMultiplePopupChars(Key key) {
- if (key.mPopupCharacters != null && key.mPopupCharacters.length > 1) {
- return true;
- }
- return false;
- }
-
- private static boolean isNumberAtLeftmostPopupChar(Key key) {
- if (key.mPopupCharacters != null && isAsciiDigit(key.mPopupCharacters[0].charAt(0))) {
- return true;
- }
- return false;
- }
-
- private static boolean isAsciiDigit(char c) {
- return (c < 0x80) && Character.isDigit(c);
- }
-
private MotionEvent generateMiniKeyboardMotionEvent(int action, int x, int y, long eventTime) {
return MotionEvent.obtain(mMiniKeyboardPopupTime, eventTime, action,
x - mMiniKeyboardOriginX, y - mMiniKeyboardOriginY, 0);
@@ -1273,8 +1183,8 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
}
public boolean isInSlidingKeyInput() {
- if (mMiniKeyboard != null) {
- return mMiniKeyboard.isInSlidingKeyInput();
+ if (mMiniKeyboardView != null) {
+ return mMiniKeyboardView.isInSlidingKeyInput();
} else {
return mPointerQueue.isInSlidingKeyInput();
}
@@ -1302,7 +1212,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
mSwipeTracker.addMovement(me);
// Gesture detector must be enabled only when mini-keyboard is not on the screen.
- if (mMiniKeyboard == null
+ if (mMiniKeyboardView == null
&& mGestureDetector != null && mGestureDetector.onTouchEvent(me)) {
dismissKeyPreview();
mHandler.cancelKeyTimers();
@@ -1317,14 +1227,14 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
// Needs to be called after the gesture detector gets a turn, as it may have
// displayed the mini keyboard
- if (mMiniKeyboard != null) {
+ if (mMiniKeyboardView != null) {
final int miniKeyboardPointerIndex = me.findPointerIndex(mMiniKeyboardTrackerId);
if (miniKeyboardPointerIndex >= 0 && miniKeyboardPointerIndex < pointerCount) {
final int miniKeyboardX = (int)me.getX(miniKeyboardPointerIndex);
final int miniKeyboardY = (int)me.getY(miniKeyboardPointerIndex);
MotionEvent translated = generateMiniKeyboardMotionEvent(action,
miniKeyboardX, miniKeyboardY, eventTime);
- mMiniKeyboard.onTouchEvent(translated);
+ mMiniKeyboardView.onTouchEvent(translated);
translated.recycle();
}
return true;
@@ -1402,8 +1312,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
mHandler.cancelAllMessages();
dismissPopupKeyboard();
- mBuffer = null;
- mCanvas = null;
+ mDirtyRect.union(0, 0, getWidth(), getHeight());
mMiniKeyboardCache.clear();
requestLayout();
}
@@ -1422,7 +1331,7 @@ public class KeyboardView extends View implements PointerTracker.UIProxy {
private void dismissPopupKeyboard() {
if (mMiniKeyboardPopup.isShowing()) {
mMiniKeyboardPopup.dismiss();
- mMiniKeyboard = null;
+ mMiniKeyboardView = null;
mMiniKeyboardOriginX = 0;
mMiniKeyboardOriginY = 0;
invalidateAllKeys();
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 888375b93..ffb8d6410 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -45,18 +45,30 @@ public class LatinKeyboard extends Keyboard {
public static final int OPACITY_FULLY_OPAQUE = 255;
private static final int SPACE_LED_LENGTH_PERCENT = 80;
+ private final Context mContext;
+
+ /* Space key and its icons, drawables and colors. */
+ private final Key mSpaceKey;
+ private final Drawable mSpaceIcon;
+ private final Drawable mSpacePreviewIcon;
+ private final int[] mSpaceKeyIndexArray;
private final Drawable mSpaceAutoCorrectionIndicator;
private final Drawable mButtonArrowLeftIcon;
private final Drawable mButtonArrowRightIcon;
private final int mSpacebarTextColor;
private final int mSpacebarTextShadowColor;
+ private final int mSpacebarVerticalCorrection;
private float mSpacebarTextFadeFactor = 0.0f;
- private final int[] mSpaceKeyIndexArray;
private int mSpaceDragStartX;
private int mSpaceDragLastDiff;
- private final Context mContext;
private boolean mCurrentlyInSpace;
private SlidingLocaleDrawable mSlidingLocaleIcon;
+
+ /* Shortcut key and its icons if available */
+ private final Key mShortcutKey;
+ private final Drawable mEnabledShortcutIcon;
+ private final Drawable mDisabledShortcutIcon;
+
private int[] mPrefLetterFrequencies;
private int mPrefLetter;
private int mPrefLetterX;
@@ -74,8 +86,6 @@ public class LatinKeyboard extends Keyboard {
// its short language name will be used instead.
private static final float MINIMUM_SCALE_OF_LANGUAGE_NAME = 0.8f;
- private static int sSpacebarVerticalCorrection;
-
private static final String SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "small";
private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium";
@@ -83,21 +93,47 @@ public class LatinKeyboard extends Keyboard {
super(context, id.getXmlId(), id);
final Resources res = context.getResources();
mContext = context;
+
+ final List<Key> keys = getKeys();
+ int spaceKeyIndex = -1;
+ int shortcutKeyIndex = -1;
+ final int keyCount = keys.size();
+ for (int index = 0; index < keyCount; index++) {
+ // For now, assuming there are up to one space key and one shortcut key respectively.
+ switch (keys.get(index).mCode) {
+ case CODE_SPACE:
+ spaceKeyIndex = index;
+ break;
+ case CODE_VOICE:
+ shortcutKeyIndex = index;
+ break;
+ }
+ }
+
+ // The index of space key is available only after Keyboard constructor has finished.
+ mSpaceKey = (spaceKeyIndex >= 0) ? keys.get(spaceKeyIndex) : null;
+ mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null;
+ mSpacePreviewIcon = (mSpaceKey != null) ? mSpaceKey.getPreviewIcon() : null;
+ mSpaceKeyIndexArray = new int[] { spaceKeyIndex };
+
+ mShortcutKey = (shortcutKeyIndex >= 0) ? keys.get(shortcutKeyIndex) : null;
+ mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null;
+
mSpacebarTextColor = res.getColor(R.color.latinkeyboard_bar_language_text);
if (id.mColorScheme == KeyboardView.COLOR_SCHEME_BLACK) {
mSpacebarTextShadowColor = res.getColor(
R.color.latinkeyboard_bar_language_shadow_black);
+ mDisabledShortcutIcon = res.getDrawable(R.drawable.sym_bkeyboard_voice_off);
} else { // default color scheme is KeyboardView.COLOR_SCHEME_WHITE
mSpacebarTextShadowColor = res.getColor(
R.color.latinkeyboard_bar_language_shadow_white);
+ mDisabledShortcutIcon = res.getDrawable(R.drawable.sym_keyboard_voice_off_holo);
}
mSpaceAutoCorrectionIndicator = res.getDrawable(R.drawable.sym_keyboard_space_led);
mButtonArrowLeftIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_left);
mButtonArrowRightIcon = res.getDrawable(R.drawable.sym_keyboard_language_arrows_right);
- sSpacebarVerticalCorrection = res.getDimensionPixelOffset(
+ mSpacebarVerticalCorrection = res.getDimensionPixelOffset(
R.dimen.spacebar_vertical_correction);
- // The index of space key is available only after Keyboard constructor has finished.
- mSpaceKeyIndexArray = new int[] { indexOf(CODE_SPACE) };
}
public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) {
@@ -113,6 +149,15 @@ public class LatinKeyboard extends Keyboard {
return newColor;
}
+ public void updateShortcutKey(boolean available, LatinKeyboardView view) {
+ if (mShortcutKey == null)
+ return;
+ mShortcutKey.mEnabled = available;
+ mShortcutKey.setIcon(available ? mEnabledShortcutIcon : mDisabledShortcutIcon);
+ if (view != null)
+ view.invalidateKey(mShortcutKey);
+ }
+
/**
* @return a key which should be invalidated.
*/
@@ -315,12 +360,8 @@ public class LatinKeyboard extends Keyboard {
int x = pointX;
int y = pointY;
final int code = key.mCode;
- if (code == CODE_SHIFT || code == CODE_DELETE) {
- y -= key.mHeight / 10;
- if (code == CODE_SHIFT) x += key.mWidth / 6;
- if (code == CODE_DELETE) x -= key.mWidth / 6;
- } else if (code == CODE_SPACE) {
- y += LatinKeyboard.sSpacebarVerticalCorrection;
+ if (code == CODE_SPACE) {
+ y += mSpacebarVerticalCorrection;
if (SubtypeSwitcher.getInstance().useSpacebarLanguageSwitcher()
&& SubtypeSwitcher.getInstance().getEnabledKeyboardLocaleCount() > 1) {
if (mCurrentlyInSpace) {
@@ -446,15 +487,6 @@ public class LatinKeyboard extends Keyboard {
}
}
- private int indexOf(int code) {
- List<Key> keys = getKeys();
- int count = keys.size();
- for (int i = 0; i < count; i++) {
- if (keys.get(i).mCode == code) return i;
- }
- return -1;
- }
-
private int getTextSizeFromTheme(int style, int defValue) {
TypedArray array = mContext.getTheme().obtainStyledAttributes(
style, new int[] { android.R.attr.textSize });
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index e9d5580e8..e7246dd6e 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.keyboard;
+import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.Utils;
import com.android.inputmethod.voice.VoiceIMEConnector;
@@ -23,10 +24,13 @@ import android.content.Context;
import android.graphics.Canvas;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MotionEvent;
// TODO: We should remove this class
public class LatinKeyboardView extends KeyboardView {
+ private static final String TAG = LatinKeyboardView.class.getSimpleName();
+ private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
/** Whether we've started dropping move events because we found a big jump */
private boolean mDroppingEvents;
@@ -94,7 +98,7 @@ public class LatinKeyboardView extends KeyboardView {
}
@Override
- protected boolean onLongPress(Key key) {
+ protected boolean onLongPress(Key key, PointerTracker tracker) {
int primaryCode = key.mCode;
if (primaryCode == Keyboard.CODE_SETTINGS) {
return invokeOnKey(Keyboard.CODE_SETTINGS_LONGPRESS);
@@ -102,7 +106,7 @@ public class LatinKeyboardView extends KeyboardView {
// Long pressing on 0 in phone number keypad gives you a '+'.
return invokeOnKey('+');
} else {
- return super.onLongPress(key);
+ return super.onLongPress(key, tracker);
}
}
@@ -137,6 +141,9 @@ public class LatinKeyboardView extends KeyboardView {
* KeyboardView.
*/
private boolean handleSuddenJump(MotionEvent me) {
+ // If device has distinct multi touch panel, there is no need to check sudden jump.
+ if (hasDistinctMultitouch())
+ return false;
final int action = me.getAction();
final int x = (int) me.getX();
final int y = (int) me.getY();
@@ -208,7 +215,11 @@ public class LatinKeyboardView extends KeyboardView {
if (keyboard == null) return true;
// If there was a sudden jump, return without processing the actual motion event.
- if (handleSuddenJump(me)) return true;
+ if (handleSuddenJump(me)) {
+ if (DEBUG_MODE)
+ Log.w(TAG, "onTouchEvent: ignore sudden jump " + me);
+ return true;
+ }
// Reset any bounding box controls in the keyboard
if (me.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
new file mode 100644
index 000000000..3b1408ccf
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 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.keyboard;
+
+import android.content.Context;
+
+public class MiniKeyboard extends Keyboard {
+ private int mDefaultKeyCoordX;
+
+ public MiniKeyboard(Context context, int xmlLayoutResId, KeyboardId id) {
+ super(context, xmlLayoutResId, id);
+ }
+
+ public void setDefaultCoordX(int pos) {
+ mDefaultKeyCoordX = pos;
+ }
+
+ public int getDefaultCoordX() {
+ return mDefaultKeyCoordX;
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java
index c150baadb..53dab9440 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardBuilder.java
@@ -27,44 +27,141 @@ import java.util.List;
public class MiniKeyboardBuilder {
private final Resources mRes;
- private final Keyboard mKeyboard;
+ private final MiniKeyboard mKeyboard;
private final CharSequence[] mPopupCharacters;
- private final int mMiniKeyboardKeyHorizontalPadding;
- private final int mKeyWidth;
- private final int mMaxColumns;
- private final int mNumRows;
- private int mColPos;
- private int mRowPos;
- private int mX;
- private int mY;
+ private final MiniKeyboardLayoutParams mParams;
+
+ /* package */ static class MiniKeyboardLayoutParams {
+ public final int mKeyWidth;
+ public final int mRowHeight;
+ /* package */ final boolean mTopRowNeedsCentering;
+ public final int mNumRows;
+ public final int mNumColumns;
+ public final int mLeftKeys;
+ public final int mRightKeys; // includes default key.
+
+ /**
+ * The object holding mini keyboard layout parameters.
+ *
+ * @param numKeys number of keys in this mini keyboard.
+ * @param maxColumns number of maximum columns of this mini keyboard.
+ * @param keyWidth mini keyboard key width in pixel, including horizontal gap.
+ * @param rowHeight mini keyboard row height in pixel, including vertical gap.
+ * @param coordXInParent coordinate x of the popup key in parent keyboard.
+ * @param parentKeyboardWidth parent keyboard width in pixel.
+ */
+ public MiniKeyboardLayoutParams(int numKeys, int maxColumns, int keyWidth, int rowHeight,
+ int coordXInParent, int parentKeyboardWidth) {
+ if (parentKeyboardWidth / keyWidth < maxColumns)
+ throw new IllegalArgumentException("Keyboard is too small to hold mini keyboard: "
+ + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
+ final int numRows = (numKeys + maxColumns - 1) / maxColumns;
+ mKeyWidth = keyWidth;
+ mRowHeight = rowHeight;
+ mNumRows = numRows;
+
+ final int numColumns = Math.min(numKeys, maxColumns);
+ final int topRowKeys = numKeys % numColumns;
+ mNumColumns = numColumns;
+ mTopRowNeedsCentering = topRowKeys != 0 && (numColumns - topRowKeys) % 2 != 0;
+
+ final int numLeftKeys = (numColumns - 1) / 2;
+ final int numRightKeys = numColumns - numLeftKeys; // including default key.
+ final int maxLeftKeys = coordXInParent / keyWidth;
+ final int maxRightKeys = Math.max(1, (parentKeyboardWidth - coordXInParent) / keyWidth);
+ if (numLeftKeys > maxLeftKeys) {
+ mLeftKeys = maxLeftKeys;
+ mRightKeys = numColumns - maxLeftKeys;
+ } else if (numRightKeys > maxRightKeys) {
+ mLeftKeys = numColumns - maxRightKeys;
+ mRightKeys = maxRightKeys;
+ } else {
+ mLeftKeys = numLeftKeys;
+ mRightKeys = numRightKeys;
+ }
+ }
+
+ // Return key position according to column count (0 is default).
+ /* package */ int getColumnPos(int n) {
+ final int col = n % mNumColumns;
+ if (col == 0) {
+ // default position.
+ return 0;
+ }
+ int pos = 0;
+ int right = 1; // include default position key.
+ int left = 0;
+ int i = 0;
+ while (true) {
+ // Assign right key if available.
+ if (right < mRightKeys) {
+ pos = right;
+ right++;
+ i++;
+ }
+ if (i >= col)
+ break;
+ // Assign left key if available.
+ if (left < mLeftKeys) {
+ left++;
+ pos = -left;
+ i++;
+ }
+ if (i >= col)
+ break;
+ }
+ return pos;
+ }
+
+ public int getDefaultKeyCoordX() {
+ return mLeftKeys * mKeyWidth;
+ }
+
+ public int getX(int n, int row) {
+ final int x = getColumnPos(n) * mKeyWidth + getDefaultKeyCoordX();
+ if (isLastRow(row) && mTopRowNeedsCentering)
+ return x - mKeyWidth / 2;
+ return x;
+ }
+
+ public int getY(int row) {
+ return (mNumRows - 1 - row) * mRowHeight;
+ }
+
+ public int getRowFlags(int row) {
+ int rowFlags = 0;
+ if (row == 0) rowFlags |= Keyboard.EDGE_TOP;
+ if (isLastRow(row)) rowFlags |= Keyboard.EDGE_BOTTOM;
+ return rowFlags;
+ }
+
+ private boolean isLastRow(int rowCount) {
+ return rowCount == mNumRows - 1;
+ }
+ }
public MiniKeyboardBuilder(KeyboardView view, int layoutTemplateResId, Key popupKey) {
final Context context = view.getContext();
mRes = context.getResources();
- final Keyboard keyboard = new Keyboard(context, layoutTemplateResId, null);
+ final MiniKeyboard keyboard = new MiniKeyboard(context, layoutTemplateResId, null);
mKeyboard = keyboard;
mPopupCharacters = popupKey.mPopupCharacters;
- mMiniKeyboardKeyHorizontalPadding = (int)mRes.getDimension(
- R.dimen.mini_keyboard_key_horizontal_padding);
- mKeyWidth = getMaxKeyWidth(view, mPopupCharacters, mKeyboard.getKeyWidth());
- final int maxColumns = popupKey.mMaxPopupColumn;
- mMaxColumns = maxColumns;
- final int numKeys = mPopupCharacters.length;
- int numRows = numKeys / maxColumns;
- if (numKeys % maxColumns != 0) numRows++;
- mNumRows = numRows;
- keyboard.setHeight((keyboard.getRowHeight() + keyboard.getVerticalGap()) * numRows
- - keyboard.getVerticalGap());
- if (numRows > 1) {
- mColPos = numKeys % maxColumns;
- if (mColPos > 0) mColPos = maxColumns - mColPos;
- // Centering top-row keys.
- mX = mColPos * (mKeyWidth + keyboard.getHorizontalGap()) / 2;
- }
- mKeyboard.setMinWidth(0);
+
+ final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, keyboard.getKeyWidth());
+ final MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
+ mPopupCharacters.length, popupKey.mMaxPopupColumn,
+ keyWidth, keyboard.getRowHeight(),
+ popupKey.mX + (popupKey.mWidth + popupKey.mGap) / 2 - keyWidth / 2,
+ view.getMeasuredWidth());
+ mParams = params;
+
+ keyboard.setHeight(params.mNumRows * params.mRowHeight - keyboard.getVerticalGap());
+ keyboard.setMinWidth(params.mNumColumns * params.mKeyWidth);
+ keyboard.setDefaultCoordX(params.getDefaultKeyCoordX() + params.mKeyWidth / 2);
}
- private int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters, int minKeyWidth) {
+ private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters,
+ int minKeyWidth) {
Paint paint = null;
Rect bounds = null;
int maxWidth = 0;
@@ -84,46 +181,22 @@ public class MiniKeyboardBuilder {
maxWidth = bounds.width();
}
}
- return Math.max(minKeyWidth, maxWidth + mMiniKeyboardKeyHorizontalPadding);
+ final int horizontalPadding = (int)view.getContext().getResources().getDimension(
+ R.dimen.mini_keyboard_key_horizontal_padding);
+ return Math.max(minKeyWidth, maxWidth + horizontalPadding);
}
- public Keyboard build() {
- final Keyboard keyboard = mKeyboard;
+ public MiniKeyboard build() {
+ final MiniKeyboard keyboard = mKeyboard;
final List<Key> keys = keyboard.getKeys();
- for (CharSequence label : mPopupCharacters) {
- refresh();
- final Key key = new Key(mRes, keyboard, label, mX, mY, mKeyWidth, getRowFlags());
+ final MiniKeyboardLayoutParams params = mParams;
+ for (int n = 0; n < mPopupCharacters.length; n++) {
+ final CharSequence label = mPopupCharacters[n];
+ final int row = n / params.mNumColumns;
+ final Key key = new Key(mRes, keyboard, label, params.getX(n, row), params.getY(row),
+ params.mKeyWidth, params.getRowFlags(row));
keys.add(key);
- advance();
}
return keyboard;
}
-
- private int getRowFlags() {
- final int rowPos = mRowPos;
- int rowFlags = 0;
- if (rowPos == 0) rowFlags |= Keyboard.EDGE_TOP;
- if (rowPos == mNumRows - 1) rowFlags |= Keyboard.EDGE_BOTTOM;
- return rowFlags;
- }
-
- private void refresh() {
- if (mColPos >= mMaxColumns) {
- final Keyboard keyboard = mKeyboard;
- // TODO: Allocate key position depending the precedence of popup characters.
- mX = 0;
- mY += keyboard.getRowHeight() + keyboard.getVerticalGap();
- mColPos = 0;
- mRowPos++;
- }
- }
-
- private void advance() {
- final Keyboard keyboard = mKeyboard;
- // TODO: Allocate key position depending the precedence of popup characters.
- mX += mKeyWidth + keyboard.getHorizontalGap();
- if (mX > keyboard.getMinWidth())
- keyboard.setMinWidth(mX);
- mColPos++;
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 49f29f923..4c90e2c3f 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard;
import com.android.inputmethod.keyboard.KeyboardView.UIHandler;
+import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import android.content.res.Resources;
@@ -31,6 +32,7 @@ public class PointerTracker {
private static final boolean DEBUG_EVENT = false;
private static final boolean DEBUG_MOVE_EVENT = false;
private static final boolean DEBUG_LISTENER = false;
+ private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
public interface UIProxy {
public void invalidateKey(Key key);
@@ -62,6 +64,7 @@ public class PointerTracker {
private Keyboard mKeyboard;
private Key[] mKeys;
private int mKeyHysteresisDistanceSquared = -1;
+ private int mKeyQuarterWidthSquared;
private final PointerTrackerKeyState mKeyState;
@@ -166,6 +169,8 @@ public class PointerTracker {
mKeyboard = keyboard;
mKeys = keys;
mKeyHysteresisDistanceSquared = (int)(keyHysteresisDistance * keyHysteresisDistance);
+ final int keyQuarterWidth = keyboard.getKeyWidth() / 4;
+ mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
// Mark that keyboard layout has been changed.
mKeyboardLayoutHasBeenChanged = true;
}
@@ -268,10 +273,6 @@ public class PointerTracker {
if (DEBUG_EVENT)
printTouchEvent("onDownEvent:", x, y, eventTime);
- // TODO: up-to-down filter, if (down-up) is less than threshold, removeMessage(UP, this) in
- // Handler, and just ignore this down event.
- // TODO: down-to-up filter, just record down time. do not enqueue pointer now.
-
// Naive up-to-down noise filter.
final long deltaT = eventTime - mKeyState.getUpTime();
if (deltaT < mTouchNoiseThresholdMillis) {
@@ -279,8 +280,9 @@ public class PointerTracker {
final int dy = y - mKeyState.getLastY();
final int distanceSquared = (dx * dx + dy * dy);
if (distanceSquared < mTouchNoiseThresholdDistanceSquared) {
- Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
- + " distance=" + distanceSquared);
+ if (DEBUG_MODE)
+ Log.w(TAG, "onDownEvent: ignore potential noise: time=" + deltaT
+ + " distance=" + distanceSquared);
setAlreadyProcessed();
return;
}
@@ -333,9 +335,8 @@ public class PointerTracker {
return;
final PointerTrackerKeyState keyState = mKeyState;
- // TODO: down-to-up filter, if (eventTime-downTime) is less than threshold, just ignore
- // this move event. Otherwise fire {@link onDownEventInternal} and continue.
-
+ final int lastX = keyState.getLastX();
+ final int lastY = keyState.getLastY();
int keyIndex = keyState.onMoveKey(x, y);
final Key oldKey = getKey(keyState.getKeyIndex());
if (isValidKeyIndex(keyIndex)) {
@@ -365,8 +366,22 @@ public class PointerTracker {
keyState.onMoveToNewKey(keyIndex, x, y);
startLongPressTimer(keyIndex);
} else {
- setAlreadyProcessed();
- showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+ // HACK: On some devices, quick successive touches may be translated to sudden
+ // move by touch panel firmware. This hack detects the case and translates the
+ // move event to successive up and down events.
+ final int dx = x - lastX;
+ final int dy = y - lastY;
+ final int lastMoveSquared = dx * dx + dy * dy;
+ if (lastMoveSquared >= mKeyQuarterWidthSquared) {
+ if (DEBUG_MODE)
+ Log.w(TAG, String.format("onMoveEvent: sudden move is translated to "
+ + "up[%d,%d]/down[%d,%d] events", lastX, lastY, x, y));
+ onUpEventInternal(lastX, lastY, eventTime);
+ onDownEventInternal(x, y, eventTime);
+ } else {
+ setAlreadyProcessed();
+ showKeyPreviewAndUpdateKeyGraphics(NOT_A_KEY);
+ }
return;
}
}
@@ -389,17 +404,11 @@ public class PointerTracker {
showKeyPreviewAndUpdateKeyGraphics(mKeyState.getKeyIndex());
}
- // TODO: up-to-down filter, if delayed UP message is fired, invoke {@link onUpEventInternal}.
-
public void onUpEvent(int x, int y, long eventTime, PointerTrackerQueue queue) {
if (ENABLE_ASSERTION) checkAssertion(queue);
if (DEBUG_EVENT)
printTouchEvent("onUpEvent :", x, y, eventTime);
- // TODO: up-to-down filter, just sendDelayedMessage(UP, this) to Handler.
- // TODO: down-to-up filter, if (eventTime-downTime) is less than threshold, just ignore
- // this up event. Otherwise fire {@link onDownEventInternal} and {@link onUpEventInternal}.
-
if (queue != null) {
if (isModifier()) {
// Before processing an up event of modifier key, all pointers already being
@@ -481,15 +490,6 @@ public class PointerTracker {
return mKeyState.getDownTime();
}
- // These package scope methods are only for debugging purpose.
- /* package */ int getStartX() {
- return mKeyState.getStartX();
- }
-
- /* package */ int getStartY() {
- return mKeyState.getStartY();
- }
-
private boolean isMinorMoveBounce(int x, int y, int newKey) {
if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
throw new IllegalStateException("keyboard and/or hysteresis not set");
@@ -561,7 +561,8 @@ public class PointerTracker {
codes[1] = codes[0];
codes[0] = code;
}
- callListenerOnCodeInput(code, codes, x, y);
+ if (key.mEnabled)
+ callListenerOnCodeInput(code, codes, x, y);
callListenerOnRelease(code);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
index 250bb95eb..a62ed96a3 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
@@ -23,8 +23,6 @@ package com.android.inputmethod.keyboard;
private final KeyDetector mKeyDetector;
// The position and time at which first down event occurred.
- private int mStartX;
- private int mStartY;
private long mDownTime;
private long mUpTime;
@@ -54,14 +52,6 @@ package com.android.inputmethod.keyboard;
return mKeyY;
}
- public int getStartX() {
- return mStartX;
- }
-
- public int getStartY() {
- return mStartY;
- }
-
public long getDownTime() {
return mDownTime;
}
@@ -79,8 +69,6 @@ package com.android.inputmethod.keyboard;
}
public int onDownKey(int x, int y, long eventTime) {
- mStartX = x;
- mStartY = y;
mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index b6035e15e..d87672c0e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
+import java.io.File;
import java.util.Arrays;
/**
@@ -72,9 +73,40 @@ public class BinaryDictionary extends Dictionary {
public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
synchronized (sInstance) {
sInstance.closeInternal();
- if (resId != 0) {
- sInstance.loadDictionary(context, resId);
+ try {
+ final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
+ if (afd == null) {
+ Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
+ return null;
+ }
+ final String sourceDir = context.getApplicationInfo().sourceDir;
+ final File packagePath = new File(sourceDir);
+ // TODO: Come up with a way to handle a directory.
+ if (!packagePath.isFile()) {
+ Log.e(TAG, "sourceDir is not a file: " + sourceDir);
+ return null;
+ }
+ sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength());
sInstance.mDicTypeId = dicTypeId;
+ } catch (android.content.res.Resources.NotFoundException e) {
+ Log.e(TAG, "Could not find the resource. resId=" + resId);
+ return null;
+ }
+ }
+ return sInstance;
+ }
+
+ // For unit test
+ /* package */ static BinaryDictionary initDictionary(File dictionary, long startOffset,
+ long length, int dicTypeId) {
+ synchronized (sInstance) {
+ sInstance.closeInternal();
+ if (dictionary.isFile()) {
+ sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length);
+ sInstance.mDicTypeId = dicTypeId;
+ } else {
+ Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
+ return null;
}
}
return sInstance;
@@ -92,22 +124,11 @@ public class BinaryDictionary extends Dictionary {
int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
int maxWordLength, int maxBigrams, int maxAlternatives);
- private final void loadDictionary(Context context, int resId) {
- try {
- final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
- if (afd == null) {
- Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
- return;
- }
- mNativeDict = openNative(context.getApplicationInfo().sourceDir,
- afd.getStartOffset(), afd.getLength(),
+ private final void loadDictionary(String path, long startOffset, long length) {
+ mNativeDict = openNative(path, startOffset, length,
TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES);
- mDictLength = afd.getLength();
- } catch (android.content.res.Resources.NotFoundException e) {
- Log.e(TAG, "Could not find the resource. resId=" + resId);
- return;
- }
+ mDictLength = length;
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index d2d1f22dd..9699ad136 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -16,8 +16,11 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.Message;
@@ -43,6 +46,7 @@ import android.widget.PopupWindow;
import android.widget.TextView;
import java.util.ArrayList;
+import java.util.List;
public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener {
@@ -50,6 +54,8 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
private static final int MAX_SUGGESTIONS = 16;
+ private static final boolean DBG = LatinImeLogger.sDBG;
+
private final ArrayList<View> mWords = new ArrayList<View>();
private final boolean mConfigCandidateHighlightFontColorEnabled;
private final CharacterStyle mInvertedForegroundColorSpan;
@@ -175,11 +181,12 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
final SuggestedWords suggestions = mSuggestions;
clear();
final int count = suggestions.size();
- final Object[] debugInfo = suggestions.mDebugInfo;
for (int i = 0; i < count; i++) {
CharSequence word = suggestions.getWord(i);
if (word == null) continue;
final int wordLength = word.length();
+ final List<SuggestedWordInfo> suggestedWordInfoList =
+ suggestions.mSuggestedWordInfoList;
final View v = mWords.get(i);
final TextView tv = (TextView)v.findViewById(R.id.candidate_word);
@@ -209,10 +216,25 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
}
tv.setText(word);
tv.setClickable(true);
- if (debugInfo != null && i < debugInfo.length && debugInfo[i] != null
- && !TextUtils.isEmpty(debugInfo[i].toString())) {
- dv.setText(debugInfo[i].toString());
- dv.setVisibility(VISIBLE);
+
+ if (suggestedWordInfoList != null && suggestedWordInfoList.get(i) != null) {
+ final SuggestedWordInfo info = suggestedWordInfoList.get(i);
+ if (info.isPreviousSuggestedWord()) {
+ int color = tv.getCurrentTextColor();
+ tv.setTextColor(Color.argb((int)(Color.alpha(color) * 0.5f), Color.red(color),
+ Color.green(color), Color.blue(color)));
+ }
+ final String debugString = info.getDebugString();
+ if (DBG) {
+ if (TextUtils.isEmpty(debugString)) {
+ dv.setVisibility(GONE);
+ } else {
+ dv.setText(debugString);
+ dv.setVisibility(VISIBLE);
+ }
+ } else {
+ dv.setVisibility(GONE);
+ }
} else {
dv.setVisibility(GONE);
}
@@ -231,8 +253,10 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word);
final Spannable word = new SpannableString(autoCorrectedWord);
final int wordLength = word.length();
- word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- word.setSpan(mInvertedForegroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedForegroundColorSpan, 0, wordLength,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tv.setText(word);
mShowingAutoCorrectionInverted = true;
}
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 03211f36b..2f1e7c2b8 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -20,6 +20,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
+import android.os.Process;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceActivity;
import android.util.Log;
@@ -30,6 +31,7 @@ public class DebugSettings extends PreferenceActivity
private static final String TAG = "DebugSettings";
private static final String DEBUG_MODE_KEY = "debug_mode";
+ private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
@Override
@@ -39,16 +41,24 @@ public class DebugSettings extends PreferenceActivity
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
+ mServiceNeedsRestart = false;
mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY);
updateDebugMode();
}
@Override
+ protected void onStop() {
+ super.onStop();
+ if (mServiceNeedsRestart) Process.killProcess(Process.myPid());
+ }
+
+ @Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals(DEBUG_MODE_KEY)) {
if (mDebugMode != null) {
mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false));
updateDebugMode();
+ mServiceNeedsRestart = true;
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index b6042c769..a55ee5246 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -38,6 +38,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManager;
+import android.net.ConnectivityManager;
import android.os.Debug;
import android.os.Handler;
import android.os.Message;
@@ -67,6 +68,8 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+// @@@ import android.view.inputmethod.InputMethodSubtype;
+import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
@@ -83,22 +86,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = "LatinIME";
private static final boolean PERF_DEBUG = false;
- private static final boolean DEBUG = false;
private static final boolean TRACE = false;
+ private static boolean DEBUG = LatinImeLogger.sDBG;
private static final int DELAY_UPDATE_SUGGESTIONS = 180;
private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300;
private static final int DELAY_UPDATE_SHIFT_STATE = 300;
+ private static final int EXTENDED_TOUCHABLE_REGION_HEIGHT = 100;
// How many continuous deletes at which to start deleting at a higher speed.
private static final int DELETE_ACCELERATE_AT = 20;
// Key events coming any faster than this are long-presses.
private static final int QUICK_PRESS = 200;
- // Contextual menu positions
- private static final int POS_METHOD = 0;
- private static final int POS_SETTINGS = 1;
-
private int mSuggestionVisibility;
private static final int SUGGESTION_VISIBILILTY_SHOW_VALUE
= R.string.prefs_suggestion_visibility_show_value;
@@ -121,6 +121,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private AlertDialog mOptionsDialog;
private InputMethodManager mImm;
+ private Resources mResources;
+ private SharedPreferences mPrefs;
+ private String mInputMethodId;
private KeyboardSwitcher mKeyboardSwitcher;
private SubtypeSwitcher mSubtypeSwitcher;
private VoiceIMEConnector mVoiceConnector;
@@ -130,9 +133,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private ContactsDictionary mContactsDictionary;
private AutoDictionary mAutoDictionary;
- private Resources mResources;
- private SharedPreferences mPrefs;
-
// These variables are initialized according to the {@link EditorInfo#inputType}.
private boolean mAutoSpace;
private boolean mInputTypeNoAutoCorrect;
@@ -154,6 +154,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private boolean mPopupOn;
private boolean mAutoCap;
private boolean mQuickFixes;
+ private boolean mConfigEnableShowSubtypeSettings;
private boolean mConfigSwipeDownDismissKeyboardEnabled;
private int mConfigDelayBeforeFadeoutLanguageOnSpacebar;
private int mConfigDurationOfFadeoutLanguageOnSpacebar;
@@ -348,6 +349,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
super.onCreate();
mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
+ mInputMethodId = Utils.getInputMethodId(mImm, getApplicationInfo().packageName);
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
@@ -358,11 +360,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// but always use the default setting defined in the resources.
if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
- res.getBoolean(R.bool.default_recorrection_enabled));
+ res.getBoolean(R.bool.config_default_recorrection_enabled));
} else {
- mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled);
+ mReCorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
}
+ mConfigEnableShowSubtypeSettings = res.getBoolean(
+ R.bool.config_enable_show_subtype_settings);
mConfigSwipeDownDismissKeyboardEnabled = res.getBoolean(
R.bool.config_swipe_down_dismiss_keyboard_enabled);
mConfigDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger(
@@ -386,8 +390,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mOrientation = res.getConfiguration().orientation;
initSuggestPuncList();
- // register to receive ringer mode changes for silent mode
- IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ // register to receive ringer mode change and network state change.
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+ filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, filter);
mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
prefs.registerOnSharedPreferenceChangeListener(this);
@@ -460,6 +466,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
commitTyped(ic);
if (ic != null) ic.finishComposingText(); // For voice input
mOrientation = conf.orientation;
+ if (isShowingOptionDialog())
+ mOptionsDialog.dismiss();
}
mConfigurationChanging = true;
@@ -506,6 +514,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final KeyboardSwitcher switcher = mKeyboardSwitcher;
LatinKeyboardView inputView = switcher.getInputView();
+ if(DEBUG) {
+ Log.d(TAG, "onStartInputView: " + inputView);
+ }
// In landscape mode, this method gets called without the input view being created.
if (inputView == null) {
return;
@@ -862,6 +873,34 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (!isFullscreenMode()) {
outInsets.contentTopInsets = outInsets.visibleTopInsets;
}
+ KeyboardView inputView = mKeyboardSwitcher.getInputView();
+ // Need to set touchable region only if input view is being shown
+ if (inputView != null && mKeyboardSwitcher.isInputViewShown()) {
+ final int x = 0;
+ int y = 0;
+ final int width = inputView.getWidth();
+ int height = inputView.getHeight() + EXTENDED_TOUCHABLE_REGION_HEIGHT;
+ if (mCandidateViewContainer != null) {
+ ViewParent candidateParent = mCandidateViewContainer.getParent();
+ if (candidateParent instanceof FrameLayout) {
+ FrameLayout fl = (FrameLayout) candidateParent;
+ if (fl != null) {
+ // Check frame layout's visibility
+ if (fl.getVisibility() == View.INVISIBLE) {
+ y = fl.getHeight();
+ height += y;
+ } else if (fl.getVisibility() == View.VISIBLE) {
+ height += fl.getHeight();
+ }
+ }
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Touchable region " + x + ", " + y + ", " + width + ", " + height);
+ }
+ outInsets.touchableInsets = InputMethodService.Insets.TOUCHABLE_INSETS_REGION;
+ outInsets.touchableRegion.set(x, y, width, height);
+ }
}
@Override
@@ -1034,7 +1073,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void onSettingsKeyPressed() {
if (!isShowingOptionDialog()) {
- if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
+ if (!mConfigEnableShowSubtypeSettings) {
+ showSubtypeSelectorAndSettings();
+ } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
showOptionsMenu();
} else {
launchSettings();
@@ -1420,6 +1461,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private boolean isCandidateStripVisible() {
+ if (mCandidateView == null)
+ return false;
if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isCorrecting())
return true;
if (!isShowingSuggestionsStrip())
@@ -1508,7 +1551,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies);
boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted
- && mSuggest.hasMinimalCorrection();
+ && mSuggest.hasAutoCorrection();
final CharSequence typedWord = word.getTypedWord();
// If we're in basic correct
final boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
@@ -1524,10 +1567,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Basically, we update the suggestion strip only when suggestion count > 1. However,
// there is an exception: We update the suggestion strip whenever typed word's length
- // is 1, regardless of suggestion count. Actually, in most cases, suggestion count is 1
- // when typed word's length is 1, but we do always need to clear the previous state when
- // the user starts typing a word (i.e. typed word's length == 1).
- if (typedWord.length() == 1 || builder.size() > 1
+ // is 1 or typed word is found in dictionary, regardless of suggestion count. Actually,
+ // in most cases, suggestion count is 1 when typed word's length is 1, but we do always
+ // need to clear the previous state when the user starts typing a word (i.e. typed word's
+ // length == 1).
+ if (builder.size() > 1 || typedWord.length() == 1 || typedWordValid
|| mCandidateView.isShowingAddToDictionaryHint()) {
builder.setTypedWordValid(typedWordValid).setHasMinimalSuggestion(correctionAvailable);
} else {
@@ -1542,7 +1586,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void showSuggestions(SuggestedWords suggestedWords, CharSequence typedWord) {
setSuggestions(suggestedWords);
if (suggestedWords.size() > 0) {
- if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords)) {
+ if (Utils.shouldBlockedBySafetyNetForAutoCorrection(suggestedWords, mSuggest)) {
mBestWord = typedWord;
} else if (suggestedWords.hasAutoCorrectionWord()) {
mBestWord = suggestedWords.getWord(1);
@@ -1912,7 +1956,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) {
mReCorrectionEnabled = sharedPreferences.getBoolean(
Settings.PREF_RECORRECTION_ENABLED,
- mResources.getBoolean(R.bool.default_recorrection_enabled));
+ mResources.getBoolean(R.bool.config_default_recorrection_enabled));
}
}
@@ -1953,11 +1997,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
- // receive ringer mode changes to detect silent mode
+ // receive ringer mode change and network state change.
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- updateRingerMode();
+ final String action = intent.getAction();
+ if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+ updateRingerMode();
+ } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+ mSubtypeSwitcher.onNetworkStateChanged(intent);
+ }
}
};
@@ -2039,7 +2088,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private void updateAutoTextEnabled() {
if (mSuggest == null) return;
- mSuggest.setAutoTextEnabled(mQuickFixes
+ mSuggest.setQuickFixesEnabled(mQuickFixes
&& SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage());
}
@@ -2079,9 +2128,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// @@@ mVibrateOn = vibrator != null && vibrator.hasVibrator()
mVibrateOn = vibrator != null
&& prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
- mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false);
- mPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON,
- mResources.getBoolean(R.bool.config_default_popup_preview));
+ mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
+ mResources.getBoolean(R.bool.config_default_sound_enabled));
+
+ mPopupOn = isPopupEnabled(prefs);
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
mQuickFixes = isQuickFixesEnabled(prefs);
@@ -2132,6 +2182,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSuggest.setAutoCorrectionThreshold(autoCorrectionThreshold);
}
+ private boolean isPopupEnabled(SharedPreferences sp) {
+ final boolean showPopupOption = getResources().getBoolean(
+ R.bool.config_enable_show_popup_on_keypress_option);
+ if (!showPopupOption) return mResources.getBoolean(R.bool.config_default_popup_preview);
+ return sp.getBoolean(Settings.PREF_POPUP_ON,
+ mResources.getBoolean(R.bool.config_default_popup_preview));
+ }
+
private boolean isQuickFixesEnabled(SharedPreferences sp) {
final boolean showQuickFixesOption = mResources.getBoolean(
R.bool.config_enable_quick_fixes_option);
@@ -2179,32 +2237,70 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return mSuggestPuncs.contains(String.valueOf((char)code));
}
- private void showOptionsMenu() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setCancelable(true);
- 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.selectInputMethod);
- builder.setItems(new CharSequence[] {
- itemInputMethod, itemSettings},
- new DialogInterface.OnClickListener() {
+ private void showSubtypeSelectorAndSettings() {
+ final CharSequence title = getString(R.string.english_ime_input_options);
+ final CharSequence[] items = new CharSequence[] {
+ // TODO: Should use new string "Select active input modes".
+ getString(R.string.language_selection_title),
+ getString(R.string.english_ime_settings),
+ };
+ final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface di, int position) {
+ di.dismiss();
+ switch (position) {
+ case 0:
+ Intent intent = new Intent(
+ android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.putExtra(android.provider.Settings.EXTRA_INPUT_METHOD_ID,
+ mInputMethodId);
+ startActivity(intent);
+ break;
+ case 1:
+ launchSettings();
+ break;
+ }
+ }
+ };
+ showOptionsMenuInternal(title, items, listener);
+ }
+ private void showOptionsMenu() {
+ final CharSequence title = getString(R.string.english_ime_input_options);
+ final CharSequence[] items = new CharSequence[] {
+ getString(R.string.selectInputMethod),
+ getString(R.string.english_ime_settings),
+ };
+ final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface di, int position) {
di.dismiss();
switch (position) {
- case POS_SETTINGS:
- launchSettings();
- break;
- case POS_METHOD:
- mImm.showInputMethodPicker();
- break;
+ case 0:
+ mImm.showInputMethodPicker();
+ break;
+ case 1:
+ launchSettings();
+ break;
}
}
- });
- builder.setTitle(mResources.getString(R.string.english_ime_input_options));
+ };
+ showOptionsMenuInternal(title, items, listener);
+ }
+
+ private void showOptionsMenuInternal(CharSequence title, CharSequence[] items,
+ DialogInterface.OnClickListener listener) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setCancelable(true);
+ builder.setIcon(R.drawable.ic_dialog_keyboard);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setItems(items, listener);
+ builder.setTitle(title);
mOptionsDialog = builder.create();
+ mOptionsDialog.setCanceledOnTouchOutside(true);
Window window = mOptionsDialog.getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index c78e6dd07..59c68f208 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -75,6 +75,7 @@ public class Settings extends PreferenceActivity
private CheckBoxPreference mQuickFixes;
private ListPreference mVoicePreference;
private ListPreference mSettingsKeyPreference;
+ private ListPreference mShowCorrectionSuggestionsPreference;
private ListPreference mAutoCorrectionThreshold;
private CheckBoxPreference mBigramSuggestion;
private boolean mVoiceOn;
@@ -102,6 +103,8 @@ public class Settings extends PreferenceActivity
mQuickFixes = (CheckBoxPreference) findPreference(PREF_QUICK_FIXES);
mVoicePreference = (ListPreference) findPreference(PREF_VOICE_SETTINGS_KEY);
mSettingsKeyPreference = (ListPreference) findPreference(PREF_SETTINGS_KEY);
+ mShowCorrectionSuggestionsPreference =
+ (ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING);
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
@@ -190,6 +193,7 @@ public class Settings extends PreferenceActivity
updateVoiceModeSummary();
}
updateSettingsKeySummary();
+ updateShowCorrectionSuggestionsSummary();
}
@Override
@@ -214,6 +218,7 @@ public class Settings extends PreferenceActivity
.equals(mVoiceModeOff));
updateVoiceModeSummary();
updateSettingsKeySummary();
+ updateShowCorrectionSuggestionsSummary();
}
@Override
@@ -222,7 +227,9 @@ public class Settings extends PreferenceActivity
final String action;
if (android.os.Build.VERSION.SDK_INT
>= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
- action = "android.settings.INPUT_METHOD_AND_SUBTYPE_ENABLER";
+ // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+ // TODO: Can this be a constant instead of literal String constant?
+ action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
} else {
action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
}
@@ -232,6 +239,13 @@ public class Settings extends PreferenceActivity
return false;
}
+ private void updateShowCorrectionSuggestionsSummary() {
+ mShowCorrectionSuggestionsPreference.setSummary(
+ getResources().getStringArray(R.array.prefs_suggestion_visibilities)
+ [mShowCorrectionSuggestionsPreference.findIndexOfValue(
+ mShowCorrectionSuggestionsPreference.getValue())]);
+ }
+
private void updateSettingsKeySummary() {
mSettingsKeyPreference.setSummary(
getResources().getStringArray(R.array.settings_key_modes)
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 6666c8e15..f45c2b7f8 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -18,16 +18,21 @@ package com.android.inputmethod.latin;
import com.android.inputmethod.compat.InputMethodSubtype;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.LatinKeyboard;
+import com.android.inputmethod.keyboard.LatinKeyboardView;
import com.android.inputmethod.voice.SettingsUtil;
import com.android.inputmethod.voice.VoiceIMEConnector;
import com.android.inputmethod.voice.VoiceInput;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
@@ -41,12 +46,14 @@ import java.util.Locale;
import java.util.Map;
public class SubtypeSwitcher {
- private static final boolean DBG = false;
+ private static boolean DBG = LatinImeLogger.sDBG;
private static final String TAG = "SubtypeSwitcher";
private static final char LOCALE_SEPARATER = '_';
private static final String KEYBOARD_MODE = "keyboard";
private static final String VOICE_MODE = "voice";
+ private static final String SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY =
+ "requireNetworkConnectivity";
private final TextUtils.SimpleStringSplitter mLocaleSplitter =
new TextUtils.SimpleStringSplitter(LOCALE_SEPARATER);
@@ -55,17 +62,17 @@ public class SubtypeSwitcher {
private /* final */ SharedPreferences mPrefs;
private /* final */ InputMethodManager mImm;
private /* final */ Resources mResources;
+ private /* final */ ConnectivityManager mConnectivityManager;
+ private /* final */ boolean mConfigUseSpacebarLanguageSwitcher;
private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod =
new ArrayList<InputMethodSubtype>();
private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>();
- private boolean mConfigUseSpacebarLanguageSwitcher;
-
/*-----------------------------------------------------------*/
// Variants which should be changed only by reload functions.
private boolean mNeedsToDisplayLanguage;
private boolean mIsSystemLanguageSameAsInputLanguage;
- private InputMethodInfo mShortcutInfo;
+ private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
private Locale mSystemLocale;
@@ -75,13 +82,14 @@ public class SubtypeSwitcher {
private VoiceInput mVoiceInput;
/*-----------------------------------------------------------*/
+ private boolean mIsNetworkConnected;
+
public static SubtypeSwitcher getInstance() {
return sInstance;
}
public static void init(LatinIME service, SharedPreferences prefs) {
- sInstance.mPrefs = prefs;
- sInstance.resetParams(service);
+ sInstance.initialize(service, prefs);
sInstance.updateAllParameters();
SubtypeLocale.init(service);
@@ -91,10 +99,13 @@ public class SubtypeSwitcher {
// Intentional empty constructor for singleton.
}
- private void resetParams(LatinIME service) {
+ private void initialize(LatinIME service, SharedPreferences prefs) {
mService = service;
+ mPrefs = prefs;
mResources = service.getResources();
mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
+ mConnectivityManager = (ConnectivityManager) service.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
mEnabledKeyboardSubtypesOfCurrentInputMethod.clear();
mEnabledLanguagesOfCurrentInputMethod.clear();
mSystemLocale = null;
@@ -109,6 +120,9 @@ public class SubtypeSwitcher {
R.bool.config_use_spacebar_language_switcher);
if (mConfigUseSpacebarLanguageSwitcher)
initLanguageSwitcher(service);
+
+ final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
+ mIsNetworkConnected = (info != null && info.isConnected());
}
// Update all parameters stored in SubtypeSwitcher.
@@ -163,6 +177,13 @@ public class SubtypeSwitcher {
}
private void updateShortcutIME() {
+ if (DBG) {
+ Log.d(TAG, "Update shortcut IME from : "
+ + (mShortcutInputMethodInfo == null
+ ? "<null>" : mShortcutInputMethodInfo.getId()) + ", "
+ + (mShortcutSubtype == null ? "<null>" : (mShortcutSubtype.getLocale()
+ + ", " + mShortcutSubtype.getMode())));
+ }
// TODO: Update an icon for shortcut IME
/*
Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts =
@@ -171,7 +192,7 @@ public class SubtypeSwitcher {
List<InputMethodSubtype> subtypes = shortcuts.get(imi);
// TODO: Returns the first found IMI for now. Should handle all shortcuts as
// appropriate.
- mShortcutInfo = imi;
+ mShortcutInputMethodInfo = imi;
// TODO: Pick up the first found subtype for now. Should handle all subtypes
// as appropriate.
mShortcutSubtype = subtypes.size() > 0 ? subtypes.get(0) : null;
@@ -213,6 +234,9 @@ public class SubtypeSwitcher {
}
mMode = newMode;
}
+
+ // If the old mode is voice input, we need to reset or cancel its status.
+ // We cancel its status when we change mode, while we reset otherwise.
if (isKeyboardMode()) {
if (modeChanged) {
if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
@@ -220,19 +244,26 @@ public class SubtypeSwitcher {
}
}
if (modeChanged || languageChanged) {
+ updateShortcutIME();
mService.onRefreshKeyboard();
}
- } else if (isVoiceMode()) {
+ } else if (isVoiceMode() && mVoiceInput != null) {
+ if (VOICE_MODE.equals(oldMode)) {
+ mVoiceInput.reset();
+ }
// If needsToShowWarningDialog is true, voice input need to show warning before
// show recognition view.
if (languageChanged || modeChanged
|| VoiceIMEConnector.getInstance().needsToShowWarningDialog()) {
- if (mVoiceInput != null) {
- triggerVoiceIME();
- }
+ triggerVoiceIME();
}
} else {
Log.w(TAG, "Unknown subtype mode: " + mMode);
+ if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
+ // We need to reset the voice input to release the resources and to reset its status
+ // as it is not the current input mode.
+ mVoiceInput.reset();
+ }
}
}
@@ -268,15 +299,15 @@ public class SubtypeSwitcher {
////////////////////////////
public void switchToShortcutIME() {
- IBinder token = mService.getWindow().getWindow().getAttributes().token;
- if (token == null || mShortcutInfo == null) {
+ final IBinder token = mService.getWindow().getWindow().getAttributes().token;
+ if (token == null || mShortcutInputMethodInfo == null) {
return;
}
// @@@ mImm.setInputMethodAndSubtype(token, mShortcutInfo.getId(), mShortcutSubtype);
}
public Drawable getShortcutIcon() {
- return getSubtypeIcon(mShortcutInfo, mShortcutSubtype);
+ return getSubtypeIcon(mShortcutInputMethodInfo, mShortcutSubtype);
}
private Drawable getSubtypeIcon(InputMethodInfo imi, InputMethodSubtype subtype) {
@@ -291,9 +322,9 @@ public class SubtypeSwitcher {
if (subtype != null) {
return pm.getDrawable(imiPackageName, subtype.getIconResId(),
imi.getServiceInfo().applicationInfo);
- } else if (imi.getSubtypes().size() > 0 && imi.getSubtypes().get(0) != null) {
+ } else if (imi.getSubtypeCount() > 0 && imi.getSubtypeAt(0) != null) {
return pm.getDrawable(imiPackageName,
- imi.getSubtypes().get(0).getIconResId(),
+ imi.getSubtypeAt(0).getIconResId(),
imi.getServiceInfo().applicationInfo);
} else {
try {
@@ -307,6 +338,38 @@ public class SubtypeSwitcher {
return null;
}
+ private static boolean contains(String[] hay, String needle) {
+ for (String element : hay) {
+ if (element.equals(needle))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isShortcutAvailable() {
+ if (mShortcutInputMethodInfo == null)
+ return false;
+ if (mShortcutSubtype != null && contains(mShortcutSubtype.getExtraValue().split(","),
+ SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
+ return mIsNetworkConnected;
+ }
+ return true;
+ }
+
+ public void onNetworkStateChanged(Intent intent) {
+ final boolean noConnection = intent.getBooleanExtra(
+ ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
+ mIsNetworkConnected = !noConnection;
+
+ final LatinKeyboardView inputView = KeyboardSwitcher.getInstance().getInputView();
+ if (inputView != null) {
+ final LatinKeyboard keyboard = inputView.getLatinKeyboard();
+ if (keyboard != null) {
+ keyboard.updateShortcutKey(isShortcutAvailable(), inputView);
+ }
+ }
+ }
+
//////////////////////////////////
// Language Switching functions //
//////////////////////////////////
@@ -353,8 +416,15 @@ public class SubtypeSwitcher {
if (mConfigUseSpacebarLanguageSwitcher) {
return mLanguageSwitcher.getEnabledLanguages();
} else {
+ int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
+ // Workaround for explicitly specifying the voice language
+ if (enabledLanguageCount == 1) {
+ mEnabledLanguagesOfCurrentInputMethod.add(
+ mEnabledLanguagesOfCurrentInputMethod.get(0));
+ ++enabledLanguageCount;
+ }
return mEnabledLanguagesOfCurrentInputMethod.toArray(
- new String[mEnabledLanguagesOfCurrentInputMethod.size()]);
+ new String[enabledLanguageCount]);
}
}
@@ -427,7 +497,7 @@ public class SubtypeSwitcher {
mVoiceInput = vi;
if (isVoiceMode()) {
if (DBG) {
- Log.d(TAG, "Set and call voice input.");
+ Log.d(TAG, "Set and call voice input.: " + getInputLocaleStr());
}
triggerVoiceIME();
return true;
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 24c73e8ea..c9e57d0a5 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.View;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -64,7 +65,7 @@ public class Suggest implements Dictionary.WordCallback {
static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
- private static boolean DBG = LatinImeLogger.sDBG;
+ private static final boolean DBG = LatinImeLogger.sDBG;
private BinaryDictionary mMainDict;
@@ -80,7 +81,7 @@ public class Suggest implements Dictionary.WordCallback {
private static final int PREF_MAX_BIGRAMS = 60;
- private boolean mAutoTextEnabled;
+ private boolean mQuickFixesEnabled;
private double mAutoCorrectionThreshold;
private int[] mPriorities = new int[mPrefMaxSuggestions];
@@ -95,7 +96,7 @@ public class Suggest implements Dictionary.WordCallback {
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
- private boolean mHaveAutoCorrection;
+ private boolean mHasAutoCorrection;
private String mLowerOriginalWord;
// TODO: Remove these member variables by passing more context to addWord() callback method
@@ -109,6 +110,12 @@ public class Suggest implements Dictionary.WordCallback {
initPool();
}
+ // For unit test
+ /* package */ Suggest(File dictionary, long startOffset, long length) {
+ mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN);
+ initPool();
+ }
+
private void initPool() {
for (int i = 0; i < mPrefMaxSuggestions; i++) {
StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
@@ -116,8 +123,8 @@ public class Suggest implements Dictionary.WordCallback {
}
}
- public void setAutoTextEnabled(boolean enabled) {
- mAutoTextEnabled = enabled;
+ public void setQuickFixesEnabled(boolean enabled) {
+ mQuickFixesEnabled = enabled;
}
public int getCorrectionMode() {
@@ -163,6 +170,10 @@ public class Suggest implements Dictionary.WordCallback {
mAutoCorrectionThreshold = threshold;
}
+ public boolean isAggressiveAutoCorrectionMode() {
+ return (mAutoCorrectionThreshold == 0);
+ }
+
/**
* Number of suggestions to generate from the input key sequence. This has
* to be a number between 1 and 100 (inclusive).
@@ -200,7 +211,7 @@ public class Suggest implements Dictionary.WordCallback {
public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
CharSequence prevWordForBigram) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
- mHaveAutoCorrection = false;
+ mHasAutoCorrection = false;
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase();
collectGarbage(mSuggestions, mPrefMaxSuggestions);
@@ -220,6 +231,7 @@ public class Suggest implements Dictionary.WordCallback {
mLowerOriginalWord = "";
}
+ double normalizedScore = Integer.MIN_VALUE;
if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM
|| mCorrectionMode == CORRECTION_BASIC)) {
// At first character typed, search only the bigrams
@@ -278,7 +290,7 @@ public class Suggest implements Dictionary.WordCallback {
if (DBG) {
Log.d(TAG, "Auto corrected by CORRECTION_FULL.");
}
- mHaveAutoCorrection = true;
+ mHasAutoCorrection = true;
}
}
if (mMainDict != null) mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
@@ -286,25 +298,25 @@ public class Suggest implements Dictionary.WordCallback {
&& mSuggestions.size() > 0 && mPriorities.length > 0) {
// TODO: when the normalized score of the first suggestion is nearly equals to
// the normalized score of the second suggestion, behave less aggressive.
- final double normalizedScore = Utils.calcNormalizedScore(
+ normalizedScore = Utils.calcNormalizedScore(
typedWord, mSuggestions.get(0), mPriorities[0]);
- if (LatinImeLogger.sDBG) {
+ if (DBG) {
Log.d(TAG, "Normalized " + typedWord + "," + mSuggestions.get(0) + ","
- + mPriorities[0] + normalizedScore
+ + mPriorities[0] + ", " + normalizedScore
+ "(" + mAutoCorrectionThreshold + ")");
}
if (normalizedScore >= mAutoCorrectionThreshold) {
if (DBG) {
Log.d(TAG, "Auto corrected by S-threthhold.");
}
- mHaveAutoCorrection = true;
+ mHasAutoCorrection = true;
}
}
}
if (typedWord != null) {
mSuggestions.add(0, typedWord.toString());
}
- if (mAutoTextEnabled) {
+ if (mQuickFixesEnabled) {
int i = 0;
int max = 6;
// Don't autotext the suggestions from the dictionaries
@@ -342,7 +354,7 @@ public class Suggest implements Dictionary.WordCallback {
if (DBG) {
Log.d(TAG, "Auto corrected by AUTOTEXT.");
}
- mHaveAutoCorrection = true;
+ mHasAutoCorrection = true;
mSuggestions.add(i + 1, autoText);
i++;
}
@@ -350,7 +362,30 @@ public class Suggest implements Dictionary.WordCallback {
}
}
removeDupes();
- return new SuggestedWords.Builder().addWords(mSuggestions);
+ if (DBG) {
+ ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList =
+ new ArrayList<SuggestedWords.SuggestedWordInfo>();
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
+ final int priorityLength = mPriorities.length;
+ for (int i = 0; i < priorityLength; ++i) {
+ if (normalizedScore > 0) {
+ final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" +
+ normalizedScore + ")";
+ frequencyInfoList.add(
+ new SuggestedWords.SuggestedWordInfo(priorityThreshold, false));
+ normalizedScore = 0.0;
+ } else {
+ final String priority = Integer.toString(mPriorities[i]);
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false));
+ }
+ }
+ for (int i = priorityLength; i < mSuggestions.size(); ++i) {
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
+ }
+ return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList);
+ } else {
+ return new SuggestedWords.Builder().addWords(mSuggestions, null);
+ }
}
public int[] getNextLettersFrequencies() {
@@ -384,16 +419,16 @@ public class Suggest implements Dictionary.WordCallback {
}
}
- public boolean hasMinimalCorrection() {
- return mHaveAutoCorrection;
+ public boolean hasAutoCorrection() {
+ return mHasAutoCorrection;
}
- private boolean compareCaseInsensitive(final String mLowerOriginalWord,
+ private static boolean compareCaseInsensitive(final String lowerOriginalWord,
final char[] word, final int offset, final int length) {
- final int originalLength = mLowerOriginalWord.length();
+ final int originalLength = lowerOriginalWord.length();
if (originalLength == length && Character.isUpperCase(word[offset])) {
for (int i = 0; i < originalLength; i++) {
- if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
+ if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
return false;
}
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 0fbbcdd91..f774ce3a5 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -20,6 +20,7 @@ import android.view.inputmethod.CompletionInfo;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
public class SuggestedWords {
@@ -29,10 +30,11 @@ public class SuggestedWords {
public final boolean mIsApplicationSpecifiedCompletions;
public final boolean mTypedWordValid;
public final boolean mHasMinimalSuggestion;
- public final Object[] mDebugInfo;
+ public final List<SuggestedWordInfo> mSuggestedWordInfoList;
private SuggestedWords(List<CharSequence> words, boolean isApplicationSpecifiedCompletions,
- boolean typedWordValid, boolean hasMinamlSuggestion, Object[] debugInfo) {
+ boolean typedWordValid, boolean hasMinamlSuggestion,
+ List<SuggestedWordInfo> suggestedWordInfoList) {
if (words != null) {
mWords = words;
} else {
@@ -41,7 +43,7 @@ public class SuggestedWords {
mIsApplicationSpecifiedCompletions = isApplicationSpecifiedCompletions;
mTypedWordValid = typedWordValid;
mHasMinimalSuggestion = hasMinamlSuggestion;
- mDebugInfo = debugInfo;
+ mSuggestedWordInfoList = suggestedWordInfoList;
}
public int size() {
@@ -61,38 +63,46 @@ public class SuggestedWords {
}
public static class Builder {
- private List<CharSequence> mWords;
+ private List<CharSequence> mWords = new ArrayList<CharSequence>();
private boolean mIsCompletions;
private boolean mTypedWordValid;
private boolean mHasMinimalSuggestion;
- private Object[] mDebugInfo;
+ private List<SuggestedWordInfo> mSuggestedWordInfoList =
+ new ArrayList<SuggestedWordInfo>();
public Builder() {
// Nothing to do here.
}
- public Builder addWords(List<CharSequence> words) {
- for (final CharSequence word : words)
- addWord(word);
+ public Builder addWords(List<CharSequence> words,
+ List<SuggestedWordInfo> suggestedWordInfoList) {
+ final int N = words.size();
+ for (int i = 0; i < N; ++i) {
+ SuggestedWordInfo suggestedWordInfo = null;
+ if (suggestedWordInfoList != null) {
+ suggestedWordInfo = suggestedWordInfoList.get(i);
+ }
+ if (suggestedWordInfo == null) {
+ suggestedWordInfo = new SuggestedWordInfo();
+ }
+ addWord(words.get(i), suggestedWordInfo);
+ }
return this;
}
- public Builder setDebugInfo(Object[] debuginfo) {
- mDebugInfo = debuginfo;
- return this;
+ public Builder addWord(CharSequence word) {
+ return addWord(word, null, false);
}
- public Builder addWord(int pos, CharSequence word) {
- if (mWords == null)
- mWords = new ArrayList<CharSequence>();
- mWords.add(pos, word);
- return this;
+ public Builder addWord(CharSequence word, CharSequence debugString,
+ boolean isPreviousSuggestedWord) {
+ SuggestedWordInfo info = new SuggestedWordInfo(debugString, isPreviousSuggestedWord);
+ return addWord(word, info);
}
- public Builder addWord(CharSequence word) {
- if (mWords == null)
- mWords = new ArrayList<CharSequence>();
+ private Builder addWord(CharSequence word, SuggestedWordInfo suggestedWordInfo) {
mWords.add(word);
+ mSuggestedWordInfoList.add(suggestedWordInfo);
return this;
}
@@ -117,11 +127,20 @@ public class SuggestedWords {
// and replace it with what the user currently typed.
public Builder addTypedWordAndPreviousSuggestions(CharSequence typedWord,
SuggestedWords previousSuggestions) {
- if (mWords != null) mWords.clear();
- addWord(typedWord);
+ mWords.clear();
+ mSuggestedWordInfoList.clear();
+ final HashSet<String> alreadySeen = new HashSet<String>();
+ addWord(typedWord, null, false);
+ alreadySeen.add(typedWord.toString());
final int previousSize = previousSuggestions.size();
- for (int pos = 1; pos < previousSize; pos++)
- addWord(previousSuggestions.getWord(pos));
+ for (int pos = 1; pos < previousSize; pos++) {
+ final String prevWord = previousSuggestions.getWord(pos).toString();
+ // Filter out duplicate suggestion.
+ if (!alreadySeen.contains(prevWord)) {
+ addWord(prevWord, null, true);
+ alreadySeen.add(prevWord);
+ }
+ }
mIsCompletions = false;
mTypedWordValid = false;
mHasMinimalSuggestion = false;
@@ -130,15 +149,42 @@ public class SuggestedWords {
public SuggestedWords build() {
return new SuggestedWords(mWords, mIsCompletions, mTypedWordValid,
- mHasMinimalSuggestion, mDebugInfo);
+ mHasMinimalSuggestion, mSuggestedWordInfoList);
}
public int size() {
- return mWords == null ? 0 : mWords.size();
+ return mWords.size();
}
public CharSequence getWord(int pos) {
return mWords.get(pos);
}
}
+
+ public static class SuggestedWordInfo {
+ private final CharSequence mDebugString;
+ private final boolean mPreviousSuggestedWord;
+
+ public SuggestedWordInfo() {
+ mDebugString = "";
+ mPreviousSuggestedWord = false;
+ }
+
+ public SuggestedWordInfo(CharSequence debugString, boolean previousSuggestedWord) {
+ mDebugString = debugString;
+ mPreviousSuggestedWord = previousSuggestedWord;
+ }
+
+ public String getDebugString() {
+ if (mDebugString == null) {
+ return "";
+ } else {
+ return mDebugString.toString();
+ }
+ }
+
+ public boolean isPreviousSuggestedWord () {
+ return mPreviousSuggestedWord;
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 160948507..d33d962c0 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -23,6 +23,7 @@ import android.os.HandlerThread;
import android.os.Process;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import java.io.BufferedReader;
@@ -37,6 +38,7 @@ import java.util.Date;
public class Utils {
private static final String TAG = Utils.class.getSimpleName();
+ private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
private static boolean DBG = LatinImeLogger.sDBG;
/**
@@ -98,12 +100,26 @@ public class Utils {
// || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1;
}
+ public static String getInputMethodId(InputMethodManager imm, String packageName) {
+ for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+ if (imi.getPackageName().equals(packageName))
+ return imi.getId();
+ }
+ throw new RuntimeException("Can not find input method id for " + packageName);
+ }
- public static boolean shouldBlockedBySafetyNetForAutoCorrection(SuggestedWords suggestions) {
+ public static boolean shouldBlockedBySafetyNetForAutoCorrection(SuggestedWords suggestions,
+ Suggest suggest) {
// Safety net for auto correction.
// Actually if we hit this safety net, it's actually a bug.
if (suggestions.size() <= 1 || suggestions.mTypedWordValid) return false;
+ // If user selected aggressive auto correction mode, there is no need to use the safety
+ // net.
+ if (suggest.isAggressiveAutoCorrectionMode()) return false;
CharSequence typedWord = suggestions.getWord(0);
+ // If the length of typed word is less than MINIMUM_SAFETY_NET_CHAR_LENGTH,
+ // we should not use net because relatively edit distance can be big.
+ if (typedWord.length() < MINIMUM_SAFETY_NET_CHAR_LENGTH) return false;
CharSequence candidateWord = suggestions.getWord(1);
final int typedWordLength = typedWord.length();
final int maxEditDistanceOfNativeDictionary = typedWordLength < 5 ? 2 : typedWordLength / 2;
@@ -113,8 +129,11 @@ public class Utils {
+ ", " + maxEditDistanceOfNativeDictionary);
}
if (distance > maxEditDistanceOfNativeDictionary) {
- Log.w(TAG, "(Error) The edit distance of this correction exceeds limit. "
- + "Turning off auto-correction.");
+ if (DBG) {
+ Log.d(TAG, "Safety net: before = " + typedWord + ", after = " + candidateWord);
+ Log.w(TAG, "(Error) The edit distance of this correction exceeds limit. "
+ + "Turning off auto-correction.");
+ }
return true;
} else {
return false;
@@ -260,9 +279,12 @@ public class Utils {
public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
final int beforeLength = before.length();
final int afterLength = after.length();
+ if (beforeLength == 0 || afterLength == 0) return 0;
final int distance = editDistance(before, after);
+ // If afterLength < beforeLength, the algorithm is suggesting a word by excessive character
+ // correction.
final double maximumScore = MAX_INITIAL_SCORE
- * Math.pow(TYPED_LETTER_MULTIPLIER, beforeLength)
+ * Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength))
* FULL_WORD_MULTIPLYER;
// add a weight based on edit distance.
// distance <= max(afterLength, beforeLength) == afterLength,
diff --git a/java/src/com/android/inputmethod/voice/RecognitionView.java b/java/src/com/android/inputmethod/voice/RecognitionView.java
index 98db9365f..95a79f463 100644
--- a/java/src/com/android/inputmethod/voice/RecognitionView.java
+++ b/java/src/com/android/inputmethod/voice/RecognitionView.java
@@ -16,6 +16,9 @@
package com.android.inputmethod.voice;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -35,13 +38,11 @@ import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
-import com.android.inputmethod.latin.R;
-
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
-import java.util.List;
+import java.util.Locale;
/**
* The user interface for the "Speak now" and "working" states.
@@ -60,6 +61,7 @@ public class RecognitionView {
private ImageView mImage;
private View mProgress;
private SoundIndicator mSoundIndicator;
+ private TextView mLanguage;
private Button mButton;
private Drawable mInitializing;
@@ -105,6 +107,7 @@ public class RecognitionView {
mButton = (Button) mView.findViewById(R.id.button);
mButton.setOnClickListener(clickListener);
mText = (TextView) mView.findViewById(R.id.text);
+ mLanguage = (TextView) mView.findViewById(R.id.language);
mContext = context;
}
@@ -184,9 +187,14 @@ public class RecognitionView {
private void prepareDialog(CharSequence text, Drawable image,
CharSequence btnTxt) {
+
+ /*
+ * The mic of INIT and of LISTENING has to be displayed in the same position. To accomplish
+ * that, some text visibility are not set as GONE but as INVISIBLE.
+ */
switch (mState) {
case INIT:
- mText.setVisibility(View.GONE);
+ mText.setVisibility(View.INVISIBLE);
mProgress.setVisibility(View.GONE);
@@ -196,6 +204,8 @@ public class RecognitionView {
mSoundIndicator.setVisibility(View.GONE);
mSoundIndicator.stop();
+ mLanguage.setVisibility(View.INVISIBLE);
+
mPopupLayout.setBackgroundDrawable(mListeningBorder);
break;
case LISTENING:
@@ -209,6 +219,11 @@ public class RecognitionView {
mSoundIndicator.setVisibility(View.VISIBLE);
mSoundIndicator.start();
+ Locale locale = SubtypeSwitcher.getInstance().getInputLocale();
+
+ mLanguage.setVisibility(View.VISIBLE);
+ mLanguage.setText(SubtypeSwitcher.getFullDisplayName(locale, true));
+
mPopupLayout.setBackgroundDrawable(mListeningBorder);
break;
case WORKING:
@@ -223,6 +238,8 @@ public class RecognitionView {
mSoundIndicator.setVisibility(View.GONE);
mSoundIndicator.stop();
+ mLanguage.setVisibility(View.GONE);
+
mPopupLayout.setBackgroundDrawable(mWorkingBorder);
break;
case READY:
@@ -237,6 +254,8 @@ public class RecognitionView {
mSoundIndicator.setVisibility(View.GONE);
mSoundIndicator.stop();
+ mLanguage.setVisibility(View.GONE);
+
mPopupLayout.setBackgroundDrawable(mErrorBorder);
break;
default:
diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
index fe6e318c9..267bef21d 100644
--- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
+++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
@@ -20,6 +20,7 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.latin.EditingUtils;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinIME.UIHandler;
+import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SharedPreferencesCompat;
import com.android.inputmethod.latin.SubtypeSwitcher;
@@ -32,19 +33,18 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.Browser;
import android.speech.SpeechRecognizer;
-import android.text.Layout;
-import android.text.Selection;
-import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
+import android.util.Log;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -78,9 +78,10 @@ public class VoiceIMEConnector implements VoiceInput.UiListener {
// given text field. For instance this is specified by the search dialog when the
// dialog is already showing a voice search button.
private static final String IME_OPTION_NO_MICROPHONE = "nm";
+ private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6;
- @SuppressWarnings("unused")
- private static final String TAG = "VoiceIMEConnector";
+ private static final String TAG = VoiceIMEConnector.class.getSimpleName();
+ private static final boolean DEBUG = LatinImeLogger.sDBG;
private boolean mAfterVoiceInput;
private boolean mHasUsedVoiceInput;
@@ -172,7 +173,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener {
if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
return;
}
- AlertDialog.Builder builder = new AlertDialog.Builder(mService);
+ AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService);
builder.setCancelable(true);
builder.setIcon(R.drawable.ic_mic_dialog);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -210,90 +211,80 @@ public class VoiceIMEConnector implements VoiceInput.UiListener {
mService.getText(R.string.voice_warning_how_to_turn_off));
}
builder.setMessage(message);
-
builder.setTitle(R.string.voice_warning_title);
mVoiceWarningDialog = builder.create();
- Window window = mVoiceWarningDialog.getWindow();
- WindowManager.LayoutParams lp = window.getAttributes();
+ final Window window = mVoiceWarningDialog.getWindow();
+ final WindowManager.LayoutParams lp = window.getAttributes();
lp.token = token;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
window.setAttributes(lp);
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
mVoiceInput.logKeyboardWarningDialogShown();
mVoiceWarningDialog.show();
- // Make URL in the dialog message clickable
- TextView textView = (TextView) mVoiceWarningDialog.findViewById(android.R.id.message);
- if (textView != null) {
- final CustomLinkMovementMethod method = CustomLinkMovementMethod.getInstance();
- method.setVoiceWarningDialog(mVoiceWarningDialog);
- textView.setMovementMethod(method);
- }
}
- private static class CustomLinkMovementMethod extends LinkMovementMethod {
- private static CustomLinkMovementMethod sLinkMovementMethodInstance =
- new CustomLinkMovementMethod();
+ private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder {
private AlertDialog mAlertDialog;
- public void setVoiceWarningDialog(AlertDialog alertDialog) {
- mAlertDialog = alertDialog;
+ public UrlLinkAlertDialogBuilder(Context context) {
+ super(context);
}
- public static CustomLinkMovementMethod getInstance() {
- return sLinkMovementMethodInstance;
+ @Override
+ public AlertDialog.Builder setMessage(CharSequence message) {
+ return super.setMessage(replaceURLSpan(message));
+ }
+
+ private Spanned replaceURLSpan(CharSequence message) {
+ // Replace all spans with the custom span
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(message);
+ for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) {
+ int spanStart = ssb.getSpanStart(span);
+ int spanEnd = ssb.getSpanEnd(span);
+ int spanFlags = ssb.getSpanFlags(span);
+ ssb.removeSpan(span);
+ ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags);
+ }
+ return ssb;
}
- // Almost the same as LinkMovementMethod.onTouchEvent(), but overrides it for
- // FLAG_ACTIVITY_NEW_TASK and mAlertDialog.cancel().
@Override
- public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
- int action = event.getAction();
-
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
- int off = layout.getOffsetForHorizontal(line, x);
-
- ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
-
- if (link.length != 0) {
- if (action == MotionEvent.ACTION_UP) {
- if (link[0] instanceof URLSpan) {
- URLSpan urlSpan = (URLSpan) link[0];
- Uri uri = Uri.parse(urlSpan.getURL());
- Context context = widget.getContext();
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
- if (mAlertDialog != null) {
- // Go back to the previous IME for now.
- // TODO: If we can find a way to bring the new activity to front
- // while keeping the warning dialog, we don't need to cancel here.
- mAlertDialog.cancel();
- }
- context.startActivity(intent);
- } else {
- link[0].onClick(widget);
- }
- } else if (action == MotionEvent.ACTION_DOWN) {
- Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
- buffer.getSpanEnd(link[0]));
+ public AlertDialog create() {
+ final AlertDialog dialog = super.create();
+
+ dialog.setOnShowListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialogInterface) {
+ // Make URL in the dialog message click-able.
+ TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message);
+ if (textView != null) {
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
}
- return true;
- } else {
- Selection.removeSelection(buffer);
}
+ });
+ mAlertDialog = dialog;
+ return dialog;
+ }
+
+ class ClickableSpan extends URLSpan {
+ public ClickableSpan(String url) {
+ super(url);
+ }
+
+ @Override
+ public void onClick(View widget) {
+ Uri uri = Uri.parse(getURL());
+ Context context = widget.getContext();
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ // Add this flag to start an activity from service
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
+ // Dismiss the warning dialog and go back to the previous IME.
+ // TODO: If we can find a way to bring the new activity to front while keeping
+ // the warning dialog, we don't need to dismiss it here.
+ mAlertDialog.cancel();
+ context.startActivity(intent);
}
- return super.onTouchEvent(widget, buffer, event);
}
}
@@ -437,7 +428,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener {
builder.addWord(word);
}
} else {
- builder.addWords(suggestions);
+ builder.addWords(suggestions, null);
}
builder.setTypedWordValid(true).setHasMinimalSuggestion(true);
mService.setSuggestions(builder.build());
@@ -543,10 +534,14 @@ public class VoiceIMEConnector implements VoiceInput.UiListener {
// As we add mm, we don't know how the rounding is going to work
// thus we may end up with few pixels extra (or less).
if (keyboardView != null) {
- int h = keyboardView.getHeight();
- if (h > 0) {
- View popupLayout = v.findViewById(R.id.popup_layout);
- popupLayout.getLayoutParams().height = h;
+ View popupLayout = v.findViewById(R.id.popup_layout);
+ final int displayHeight =
+ mService.getResources().getDisplayMetrics().heightPixels;
+ final int currentHeight = popupLayout.getLayoutParams().height;
+ final int keyboardHeight = keyboardView.getHeight();
+ if (keyboardHeight > currentHeight || keyboardHeight
+ > (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) {
+ popupLayout.getLayoutParams().height = keyboardHeight;
}
}
mService.setInputView(v);
@@ -645,14 +640,17 @@ public class VoiceIMEConnector implements VoiceInput.UiListener {
}
}
- public void onStartInputView(IBinder token) {
+ public void onStartInputView(IBinder keyboardViewToken) {
+ // If keyboardViewToken is null, keyboardView is not attached but voiceView is attached.
+ IBinder windowToken = keyboardViewToken != null ? keyboardViewToken
+ : mVoiceInput.getView().getWindowToken();
// If IME is in voice mode, but still needs to show the voice warning dialog,
// keep showing the warning.
- if (mSubtypeSwitcher.isVoiceMode() && token != null) {
+ if (mSubtypeSwitcher.isVoiceMode() && windowToken != null) {
// Close keyboard view if it is been shown.
if (KeyboardSwitcher.getInstance().isInputViewShown())
KeyboardSwitcher.getInstance().getInputView().purgeKeyboardAndClosing();
- startListening(false, token);
+ startListening(false, windowToken);
}
// If we have no token, onAttachedToWindow will take care of showing dialog and start
// listening.
@@ -701,7 +699,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener {
mHandler.updateVoiceResults();
}
- public FieldContext makeFieldContext() {
+ private FieldContext makeFieldContext() {
SubtypeSwitcher switcher = SubtypeSwitcher.getInstance();
return new FieldContext(mService.getCurrentInputConnection(),
mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(),
diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java
index ffa349fde..2df9e8588 100644
--- a/java/src/com/android/inputmethod/voice/VoiceInput.java
+++ b/java/src/com/android/inputmethod/voice/VoiceInput.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.voice;
import com.android.inputmethod.latin.EditingUtils;
+import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import android.content.ContentResolver;
@@ -58,6 +59,7 @@ public class VoiceInput implements OnClickListener {
private static final String EXTRA_CALLING_PACKAGE = "calling_package";
private static final String EXTRA_ALTERNATES = "android.speech.extra.ALTERNATES";
private static final int MAX_ALT_LIST_LENGTH = 6;
+ private static boolean DBG = LatinImeLogger.sDBG;
private static final String DEFAULT_RECOMMENDED_PACKAGES =
"com.android.mms " +
@@ -128,19 +130,14 @@ public class VoiceInput implements OnClickListener {
private int mState = DEFAULT;
- private final static int MSG_CLOSE_ERROR_DIALOG = 1;
-
- private final static int MSG_RESET = 2;
+ private final static int MSG_RESET = 1;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
- if (msg.what == MSG_RESET || msg.what == MSG_CLOSE_ERROR_DIALOG) {
+ if (msg.what == MSG_RESET) {
mState = DEFAULT;
mRecognitionView.finish();
- }
-
- if (msg.what == MSG_CLOSE_ERROR_DIALOG) {
mUiListener.onCancelVoice();
}
}
@@ -313,8 +310,18 @@ public class VoiceInput implements OnClickListener {
* @param swipe whether this voice input was started by swipe, for logging purposes
*/
public void startListening(FieldContext context, boolean swipe) {
- mState = DEFAULT;
-
+ if (DBG) {
+ Log.d(TAG, "startListening: " + context);
+ }
+
+ if (mState != DEFAULT) {
+ Log.w(TAG, "startListening in the wrong status " + mState);
+ }
+
+ // If everything works ok, the voice input should be already in the correct state. As this
+ // class can be called by third-party, we call reset just to be on the safe side.
+ reset();
+
Locale locale = Locale.getDefault();
String localeString = locale.getLanguage() + "-" + locale.getCountry();
@@ -499,6 +506,21 @@ public class VoiceInput implements OnClickListener {
}
/**
+ * Reset the current voice recognition.
+ */
+ public void reset() {
+ if (mState != DEFAULT) {
+ mState = DEFAULT;
+
+ // Remove all pending tasks (e.g., timers to cancel voice input)
+ mHandler.removeMessages(MSG_RESET);
+
+ mSpeechRecognizer.cancel();
+ mRecognitionView.finish();
+ }
+ }
+
+ /**
* Cancel in-progress speech recognition.
*/
public void cancel() {
@@ -513,14 +535,9 @@ public class VoiceInput implements OnClickListener {
mLogger.cancelDuringError();
break;
}
- mState = DEFAULT;
-
- // Remove all pending tasks (e.g., timers to cancel voice input)
- mHandler.removeMessages(MSG_RESET);
- mSpeechRecognizer.cancel();
+ reset();
mUiListener.onCancelVoice();
- mRecognitionView.finish();
}
private int getErrorStringId(int errorType, boolean endpointed) {
@@ -555,7 +572,7 @@ public class VoiceInput implements OnClickListener {
mState = ERROR;
mRecognitionView.showError(error);
// Wait a couple seconds and then automatically dismiss message.
- mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_CLOSE_ERROR_DIALOG), 2000);
+ mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_RESET), 2000);
}
private class ImeRecognitionListener implements RecognitionListener {