aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java51
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java1
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java146
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java21
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java43
-rw-r--r--java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java46
-rw-r--r--java/src/com/android/inputmethod/keyboard/TypefaceUtils.java91
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java49
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java49
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java268
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java20
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java2
-rw-r--r--java/src/com/android/inputmethod/latin/CapsModeUtils.java2
-rw-r--r--java/src/com/android/inputmethod/latin/CompletionInfoUtils.java43
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java2
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java5
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java2
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/InputTypeUtils.java1
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java264
-rw-r--r--java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java28
-rw-r--r--java/src/com/android/inputmethod/latin/RecapitalizeStatus.java189
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java38
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java8
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsActivity.java5
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsFragment.java13
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java140
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeLocale.java4
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java21
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java18
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java34
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java48
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java8
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java9
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java8
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java10
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java117
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java18
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java8
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java6
-rw-r--r--java/src/com/android/inputmethod/research/BootBroadcastReceiver.java5
-rw-r--r--java/src/com/android/inputmethod/research/MotionEventReader.java3
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java67
-rw-r--r--java/src/com/android/inputmethod/research/UploaderService.java46
47 files changed, 1457 insertions, 527 deletions
diff --git a/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
new file mode 100644
index 000000000..ff6561c58
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/UserDictionaryCompatUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.compat;
+
+import android.content.Context;
+import android.provider.UserDictionary.Words;
+
+import java.lang.reflect.Method;
+import java.util.Locale;
+
+public final class UserDictionaryCompatUtils {
+ // UserDictionary.Words#addWord(Context, String, int, String, Locale) was introduced
+ // in API level 16 (Build.VERSION_CODES.JELLY_BEAN).
+ private static final Method METHOD_addWord = CompatUtils.getMethod(Words.class, "addWord",
+ Context.class, String.class, Integer.TYPE, String.class, Locale.class);
+
+ public static void addWord(final Context context, final String word, final int freq,
+ final String shortcut, final Locale locale) {
+ if (hasNewerAddWord()) {
+ CompatUtils.invoke(Words.class, null, METHOD_addWord, context, word, freq, shortcut,
+ locale);
+ } else {
+ // Fall back to the pre-JellyBean method.
+ final int localeType;
+ if (null == locale) {
+ localeType = Words.LOCALE_TYPE_ALL;
+ } else {
+ localeType = Words.LOCALE_TYPE_CURRENT;
+ }
+ Words.addWord(context, word, freq, localeType);
+ }
+ }
+
+ private static final boolean hasNewerAddWord() {
+ return null != METHOD_addWord;
+ }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index e05a79b7b..3173e911b 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -181,7 +181,6 @@ public final class UpdateHandler {
if (!cursor.moveToFirst()) return;
do {
final String clientId = cursor.getString(0);
- if (TextUtils.isEmpty(clientId)) continue; // This probably can't happen
final String metadataUri =
MetadataDbHelper.getMetadataUriAsString(context, clientId);
PrivateLog.log("Update for clientId " + Utils.s(clientId), context);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index d15f14f88..4e41b77ce 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -207,7 +207,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
* Update keyboard shift state triggered by connected EditText status change.
*/
public void updateShiftState() {
- mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
+ mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
+ mLatinIME.getCurrentRecapitalizeState());
}
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
@@ -276,7 +277,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
// Implements {@link KeyboardState.SwitchActions}.
@Override
public void requestUpdatingShiftState() {
- mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState());
+ mState.onUpdateShiftState(mLatinIME.getCurrentAutoCapsState(),
+ mLatinIME.getCurrentRecapitalizeState());
}
// Implements {@link KeyboardState.SwitchActions}.
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 43d28be5d..e4e75c342 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -26,10 +26,8 @@ import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
-import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.util.SparseArray;
import android.view.View;
import com.android.inputmethod.keyboard.internal.KeyDrawParams;
@@ -73,15 +71,15 @@ import java.util.HashSet;
*/
public class KeyboardView extends View {
// XML attributes
- protected final KeyVisualAttributes mKeyVisualAttributes;
+ private final KeyVisualAttributes mKeyVisualAttributes;
private final int mKeyLabelHorizontalPadding;
private final float mKeyHintLetterPadding;
private final float mKeyPopupHintLetterPadding;
private final float mKeyShiftedLetterHintPadding;
private final float mKeyTextShadowRadius;
- protected final float mVerticalCorrection;
- protected final Drawable mKeyBackground;
- protected final Rect mKeyBackgroundPadding = new Rect();
+ private final float mVerticalCorrection;
+ private final Drawable mKeyBackground;
+ private final Rect mKeyBackgroundPadding = new Rect();
// HORIZONTAL ELLIPSIS "...", character for popup hint.
private static final String POPUP_HINT_CHAR = "\u2026";
@@ -113,10 +111,6 @@ public class KeyboardView extends View {
private final Canvas mOffscreenCanvas = new Canvas();
private final Paint mPaint = new Paint();
private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics();
- // This sparse array caches key label text height in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
- // This sparse array caches key label text width in pixel indexed by key label text size.
- private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
private static final char[] KEY_LABEL_REFERENCE_CHAR = { 'M' };
private static final char[] KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR = { '8' };
@@ -134,15 +128,15 @@ public class KeyboardView extends View {
mKeyLabelHorizontalPadding = keyboardViewAttr.getDimensionPixelOffset(
R.styleable.KeyboardView_keyLabelHorizontalPadding, 0);
mKeyHintLetterPadding = keyboardViewAttr.getDimension(
- R.styleable.KeyboardView_keyHintLetterPadding, 0);
+ R.styleable.KeyboardView_keyHintLetterPadding, 0.0f);
mKeyPopupHintLetterPadding = keyboardViewAttr.getDimension(
- R.styleable.KeyboardView_keyPopupHintLetterPadding, 0);
+ R.styleable.KeyboardView_keyPopupHintLetterPadding, 0.0f);
mKeyShiftedLetterHintPadding = keyboardViewAttr.getDimension(
- R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0);
+ R.styleable.KeyboardView_keyShiftedLetterHintPadding, 0.0f);
mKeyTextShadowRadius = keyboardViewAttr.getFloat(
R.styleable.KeyboardView_keyTextShadowRadius, 0.0f);
mVerticalCorrection = keyboardViewAttr.getDimension(
- R.styleable.KeyboardView_verticalCorrection, 0);
+ R.styleable.KeyboardView_verticalCorrection, 0.0f);
keyboardViewAttr.recycle();
final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
@@ -185,6 +179,14 @@ public class KeyboardView extends View {
return mKeyboard;
}
+ protected float getVerticalCorrection() {
+ return mVerticalCorrection;
+ }
+
+ protected void updateKeyDrawParams(final int keyHeight) {
+ mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+ }
+
@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
if (mKeyboard != null) {
@@ -213,7 +215,7 @@ public class KeyboardView extends View {
}
onDrawKeyboard(mOffscreenCanvas);
}
- canvas.drawBitmap(mOffscreenBuffer, 0, 0, null);
+ canvas.drawBitmap(mOffscreenBuffer, 0.0f, 0.0f, null);
}
private boolean maybeAllocateOffscreenBuffer() {
@@ -333,7 +335,7 @@ public class KeyboardView extends View {
canvas.translate(bgX, bgY);
background.draw(canvas);
if (LatinImeLogger.sVISUALDEBUG) {
- drawRectangle(canvas, 0, 0, bgWidth, bgHeight, 0x80c00000, new Paint());
+ drawRectangle(canvas, 0.0f, 0.0f, bgWidth, bgHeight, 0x80c00000, new Paint());
}
canvas.translate(-bgX, -bgY);
}
@@ -347,7 +349,7 @@ public class KeyboardView extends View {
final float centerY = keyHeight * 0.5f;
if (LatinImeLogger.sVISUALDEBUG) {
- drawRectangle(canvas, 0, 0, keyWidth, keyHeight, 0x800000c0, new Paint());
+ drawRectangle(canvas, 0.0f, 0.0f, keyWidth, keyHeight, 0x800000c0, new Paint());
}
// Draw key label.
@@ -357,14 +359,16 @@ public class KeyboardView extends View {
final String label = key.mLabel;
paint.setTypeface(key.selectTypeface(params));
paint.setTextSize(key.selectTextSize(params));
- final float labelCharHeight = getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint);
- final float labelCharWidth = getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint);
+ final float labelCharHeight = TypefaceUtils.getCharHeight(
+ KEY_LABEL_REFERENCE_CHAR, paint);
+ final float labelCharWidth = TypefaceUtils.getCharWidth(
+ KEY_LABEL_REFERENCE_CHAR, paint);
// Vertical label text alignment.
- final float baseline = centerY + labelCharHeight / 2;
+ final float baseline = centerY + labelCharHeight / 2.0f;
// Horizontal label text alignment
- float labelWidth = 0;
+ float labelWidth = 0.0f;
if (key.isAlignLeft()) {
positionX = mKeyLabelHorizontalPadding;
paint.setTextAlign(Align.LEFT);
@@ -373,31 +377,31 @@ public class KeyboardView extends View {
paint.setTextAlign(Align.RIGHT);
} else if (key.isAlignLeftOfCenter()) {
// TODO: Parameterise this?
- positionX = centerX - labelCharWidth * 7 / 4;
+ positionX = centerX - labelCharWidth * 7.0f / 4.0f;
paint.setTextAlign(Align.LEFT);
} else if (key.hasLabelWithIconLeft() && icon != null) {
- labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ LABEL_ICON_MARGIN * keyWidth;
- positionX = centerX + labelWidth / 2;
+ positionX = centerX + labelWidth / 2.0f;
paint.setTextAlign(Align.RIGHT);
} else if (key.hasLabelWithIconRight() && icon != null) {
- labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ labelWidth = TypefaceUtils.getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ LABEL_ICON_MARGIN * keyWidth;
- positionX = centerX - labelWidth / 2;
+ positionX = centerX - labelWidth / 2.0f;
paint.setTextAlign(Align.LEFT);
} else {
positionX = centerX;
paint.setTextAlign(Align.CENTER);
}
if (key.needsXScale()) {
- paint.setTextScaleX(
- Math.min(1.0f, (keyWidth * MAX_LABEL_RATIO) / getLabelWidth(label, paint)));
+ paint.setTextScaleX(Math.min(1.0f,
+ (keyWidth * MAX_LABEL_RATIO) / TypefaceUtils.getLabelWidth(label, paint)));
}
paint.setColor(key.selectTextColor(params));
if (key.isEnabled()) {
// Set a drop shadow for the text
- paint.setShadowLayer(mKeyTextShadowRadius, 0, 0, params.mTextShadowColor);
+ paint.setShadowLayer(mKeyTextShadowRadius, 0.0f, 0.0f, params.mTextShadowColor);
} else {
// Make label invisible
paint.setColor(Color.TRANSPARENT);
@@ -405,7 +409,7 @@ public class KeyboardView extends View {
blendAlpha(paint, params.mAnimAlpha);
canvas.drawText(label, 0, label.length(), positionX, baseline, paint);
// Turn off drop shadow and reset x-scale.
- paint.setShadowLayer(0, 0, 0, 0);
+ paint.setShadowLayer(0.0f, 0.0f, 0.0f, Color.TRANSPARENT);
paint.setTextScaleX(1.0f);
if (icon != null) {
@@ -413,10 +417,10 @@ public class KeyboardView extends View {
final int iconHeight = icon.getIntrinsicHeight();
final int iconY = (keyHeight - iconHeight) / 2;
if (key.hasLabelWithIconLeft()) {
- final int iconX = (int)(centerX - labelWidth / 2);
+ final int iconX = (int)(centerX - labelWidth / 2.0f);
drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
} else if (key.hasLabelWithIconRight()) {
- final int iconX = (int)(centerX + labelWidth / 2 - iconWidth);
+ final int iconX = (int)(centerX + labelWidth / 2.0f - iconWidth);
drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
}
}
@@ -439,20 +443,23 @@ public class KeyboardView extends View {
// The hint label is placed just right of the key label. Used mainly on
// "phone number" layout.
// TODO: Generalize the following calculations.
- hintX = positionX + getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2;
- hintY = centerY + getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
+ hintX = positionX
+ + TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) * 2.0f;
+ hintY = centerY
+ + TypefaceUtils.getCharHeight(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
paint.setTextAlign(Align.LEFT);
} else if (key.hasShiftedLetterHint()) {
// The hint label is placed at top-right corner of the key. Used mainly on tablet.
hintX = keyWidth - mKeyShiftedLetterHintPadding
- - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
+ - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
paint.getFontMetrics(mFontMetrics);
hintY = -mFontMetrics.top;
paint.setTextAlign(Align.CENTER);
} else { // key.hasHintLetter()
// The hint letter is placed at top-right corner of the key. Used mainly on phone.
hintX = keyWidth - mKeyHintLetterPadding
- - getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint) / 2;
+ - TypefaceUtils.getCharWidth(KEY_NUMERIC_HINT_LABEL_REFERENCE_CHAR, paint)
+ / 2.0f;
hintY = -paint.ascent();
paint.setTextAlign(Align.CENTER);
}
@@ -506,7 +513,7 @@ public class KeyboardView extends View {
paint.setColor(params.mHintLabelColor);
paint.setTextAlign(Align.CENTER);
final float hintX = keyWidth - mKeyHintLetterPadding
- - getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2;
+ - TypefaceUtils.getCharWidth(KEY_LABEL_REFERENCE_CHAR, paint) / 2.0f;
final float hintY = keyHeight - mKeyPopupHintLetterPadding;
canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint);
@@ -517,54 +524,6 @@ public class KeyboardView extends View {
}
}
- private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
- final int labelSize = (int)paint.getTextSize();
- final Typeface face = paint.getTypeface();
- final int codePointOffset = referenceChar << 15;
- if (face == Typeface.DEFAULT) {
- return codePointOffset + labelSize;
- } else if (face == Typeface.DEFAULT_BOLD) {
- return codePointOffset + labelSize + 0x1000;
- } else if (face == Typeface.MONOSPACE) {
- return codePointOffset + labelSize + 0x2000;
- } else {
- return codePointOffset + labelSize;
- }
- }
-
- // Working variable for the following methods.
- private final Rect mTextBounds = new Rect();
-
- private float getCharHeight(final char[] referenceChar, final Paint paint) {
- final int key = getCharGeometryCacheKey(referenceChar[0], paint);
- final Float cachedValue = sTextHeightCache.get(key);
- if (cachedValue != null)
- return cachedValue;
-
- paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
- final float height = mTextBounds.height();
- sTextHeightCache.put(key, height);
- return height;
- }
-
- private float getCharWidth(final char[] referenceChar, final Paint paint) {
- final int key = getCharGeometryCacheKey(referenceChar[0], paint);
- final Float cachedValue = sTextWidthCache.get(key);
- if (cachedValue != null)
- return cachedValue;
-
- paint.getTextBounds(referenceChar, 0, 1, mTextBounds);
- final float width = mTextBounds.width();
- sTextWidthCache.put(key, width);
- return width;
- }
-
- // TODO: Remove this method.
- public float getLabelWidth(final String label, final Paint paint) {
- paint.getTextBounds(label, 0, label.length(), mTextBounds);
- return mTextBounds.width();
- }
-
protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x,
final int y, final int width, final int height) {
canvas.translate(x, y);
@@ -578,7 +537,7 @@ public class KeyboardView extends View {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1.0f);
paint.setColor(color);
- canvas.drawLine(0, y, w, y, paint);
+ canvas.drawLine(0.0f, y, w, y, paint);
}
private static void drawVerticalLine(final Canvas canvas, final float x, final float h,
@@ -586,7 +545,7 @@ public class KeyboardView extends View {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1.0f);
paint.setColor(color);
- canvas.drawLine(x, 0, x, h, paint);
+ canvas.drawLine(x, 0.0f, x, h, paint);
}
private static void drawRectangle(final Canvas canvas, final float x, final float y,
@@ -595,15 +554,20 @@ public class KeyboardView extends View {
paint.setStrokeWidth(1.0f);
paint.setColor(color);
canvas.translate(x, y);
- canvas.drawRect(0, 0, w, h, paint);
+ canvas.drawRect(0.0f, 0.0f, w, h, paint);
canvas.translate(-x, -y);
}
- public Paint newDefaultLabelPaint() {
+ public Paint newLabelPaint(final Key key) {
final Paint paint = new Paint();
paint.setAntiAlias(true);
- paint.setTypeface(mKeyDrawParams.mTypeface);
- paint.setTextSize(mKeyDrawParams.mLabelSize);
+ if (key == null) {
+ paint.setTypeface(mKeyDrawParams.mTypeface);
+ paint.setTextSize(mKeyDrawParams.mLabelSize);
+ } else {
+ paint.setTypeface(key.selectTypeface(mKeyDrawParams));
+ paint.setTextSize(key.selectTextSize(mKeyDrawParams));
+ }
return paint;
}
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 745e7dfed..ba78d014a 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -530,9 +530,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0);
final float keyHysteresisDistance = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_keyHysteresisDistance, 0);
+ R.styleable.MainKeyboardView_keyHysteresisDistance, 0.0f);
final float keyHysteresisDistanceForSlidingModifier = mainKeyboardViewAttr.getDimension(
- R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0);
+ R.styleable.MainKeyboardView_keyHysteresisDistanceForSlidingModifier, 0.0f);
mKeyDetector = new KeyDetector(
keyHysteresisDistance, keyHysteresisDistanceForSlidingModifier);
mKeyTimerHandler = new KeyTimerHandler(this, mainKeyboardViewAttr);
@@ -655,7 +655,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
mKeyTimerHandler.cancelLongPressTimer();
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
- keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
+ keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
PointerTracker.setKeyDetector(mKeyDetector);
mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth);
mMoreKeysKeyboardCache.clear();
@@ -1329,7 +1329,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
// Overlay a dark rectangle to dim.
if (mNeedsToDimEntireKeyboard) {
- canvas.drawRect(0, 0, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
+ canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mBackgroundDimAlphaPaint);
}
}
@@ -1353,9 +1353,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
}
- private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) {
+ private static boolean fitsTextIntoWidth(final int width, final String text,
+ final Paint paint) {
paint.setTextScaleX(1.0f);
- final float textWidth = getLabelWidth(text, paint);
+ final float textWidth = TypefaceUtils.getLabelWidth(text, paint);
if (textWidth < width) {
return true;
}
@@ -1366,12 +1367,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
}
paint.setTextScaleX(scaleX);
- return getLabelWidth(text, paint) < width;
+ return TypefaceUtils.getLabelWidth(text, paint) < width;
}
// Layout language name on spacebar.
- private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype,
- final int width) {
+ private static String layoutLanguageOnSpacebar(final Paint paint,
+ final InputMethodSubtype subtype, final int width) {
// Choose appropriate language name to fit into the width.
final String fullText = getFullDisplayName(subtype);
if (fitsTextIntoWidth(width, fullText, paint)) {
@@ -1460,7 +1461,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
return "";
}
final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
- return StringUtils.toTitleCase(locale.getLanguage(), locale);
+ return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
}
// Get InputMethodSubtype's middle display name in its locale.
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 66c30149c..ae08a5953 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
@@ -73,10 +74,11 @@ public final class MoreKeysKeyboard extends Keyboard {
final int rowHeight, final int coordXInParent, final int parentKeyboardWidth,
final boolean isFixedColumnOrder, final int dividerWidth) {
mIsFixedOrder = isFixedColumnOrder;
- if (parentKeyboardWidth / keyWidth < maxColumns) {
+ if (parentKeyboardWidth / keyWidth < Math.min(numKeys, maxColumns)) {
throw new IllegalArgumentException(
"Keyboard is too small to hold more keys keyboard: "
- + parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
+ + parentKeyboardWidth + " " + keyWidth + " "
+ + numKeys + " " + maxColumns);
}
mDefaultKeyWidth = keyWidth;
mDefaultRowHeight = rowHeight;
@@ -257,7 +259,6 @@ public final class MoreKeysKeyboard extends Keyboard {
private static final float LABEL_PADDING_RATIO = 0.2f;
private static final float DIVIDER_RATIO = 0.2f;
-
/**
* The builder of MoreKeysKeyboard.
* @param context the context of {@link MoreKeysKeyboardView}.
@@ -289,11 +290,23 @@ public final class MoreKeysKeyboard extends Keyboard {
// be considered because the vertical positions of both backgrounds were already
// adjusted with their bottom paddings deducted.
width = keyPreviewDrawParams.mPreviewVisibleWidth;
- height = keyPreviewDrawParams.mPreviewVisibleHeight
- + mParams.mVerticalGap;
+ height = keyPreviewDrawParams.mPreviewVisibleHeight + mParams.mVerticalGap;
+ // TODO: Remove this check.
+ if (width == 0) {
+ throw new IllegalArgumentException(
+ "Zero width key detected: " + parentKey + " in " + parentKeyboard.mId);
+ }
} else {
- width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth);
+ width = getMaxKeyWidth(parentKeyboardView, parentKey, mParams.mDefaultKeyWidth,
+ context.getResources());
height = parentKeyboard.mMostCommonKeyHeight;
+ // TODO: Remove this check.
+ if (width == 0) {
+ throw new IllegalArgumentException(
+ "Zero width calculated: " + parentKey
+ + " moreKeys=" + java.util.Arrays.toString(parentKey.mMoreKeys)
+ + " in " + parentKeyboard.mId);
+ }
}
final int dividerWidth;
if (parentKey.needsDividersInMoreKeys()) {
@@ -310,22 +323,18 @@ public final class MoreKeysKeyboard extends Keyboard {
}
private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
- final int minKeyWidth) {
- final int padding = (int)(view.getResources()
- .getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
- + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0));
- final Paint paint = view.newDefaultLabelPaint();
- paint.setTypeface(parentKey.selectTypeface(view.mKeyDrawParams));
- paint.setTextSize(parentKey.selectMoreKeyTextSize(view.mKeyDrawParams));
+ final int minKeyWidth, final Resources res) {
+ final float padding =
+ res.getDimension(R.dimen.more_keys_keyboard_key_horizontal_padding)
+ + (parentKey.hasLabelsInMoreKeys() ? minKeyWidth * LABEL_PADDING_RATIO : 0.0f);
+ final Paint paint = view.newLabelPaint(parentKey);
int maxWidth = minKeyWidth;
for (final MoreKeySpec spec : parentKey.mMoreKeys) {
final String label = spec.mLabel;
// If the label is single letter, minKeyWidth is enough to hold the label.
if (label != null && StringUtils.codePointCount(label) > 1) {
- final int width = (int)view.getLabelWidth(label, paint) + padding;
- if (maxWidth < width) {
- maxWidth = width;
- }
+ maxWidth = Math.max(maxWidth,
+ (int)(TypefaceUtils.getLabelWidth(label, paint) + padding));
}
}
return maxWidth;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 0d42ab2fe..a82fb79bd 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -33,7 +33,7 @@ import com.android.inputmethod.latin.R;
public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel {
private final int[] mCoordinates = CoordinateUtils.newInstance();
- private final KeyDetector mKeyDetector;
+ protected final KeyDetector mKeyDetector;
private Controller mController;
protected KeyboardActionListener mListener;
private int mOriginX;
@@ -71,7 +71,7 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel
public void setKeyboard(final Keyboard keyboard) {
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(),
- -getPaddingTop() + mVerticalCorrection);
+ -getPaddingTop() + getVerticalCorrection());
}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index dcfae739e..91c4319e7 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -441,10 +441,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// Returns true if keyboard has been changed by this callback.
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
- if (sInGesture) {
+ if (sInGesture || mIsDetectingGesture) {
return false;
}
- final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier();
+ final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onPress : %s%s%s", mPointerId,
KeyDetector.printableCode(key),
@@ -468,7 +468,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// primaryCode is different from {@link Key#mCode}.
private void callListenerOnCodeInput(final Key key, final int primaryCode, final int x,
final int y, final long eventTime) {
- final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier();
+ final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState();
final int code = altersCode ? key.getAltCode() : primaryCode;
if (DEBUG_LISTENER) {
@@ -500,10 +500,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
// primaryCode is different from {@link Key#mCode}.
private void callListenerOnRelease(final Key key, final int primaryCode,
final boolean withSliding) {
- if (sInGesture) {
+ if (sInGesture || mIsDetectingGesture) {
return;
}
- final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier();
+ final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId,
Constants.printableCode(primaryCode),
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 32cee734a..b77e378bf 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -26,7 +26,7 @@ import com.android.inputmethod.latin.JniUtils;
import java.util.Arrays;
-public final class ProximityInfo {
+public class ProximityInfo {
private static final String TAG = ProximityInfo.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -79,22 +79,21 @@ public final class ProximityInfo {
mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
}
- private static ProximityInfo createDummyProximityInfo() {
- return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
- }
-
- public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximityCharsArray,
- final int rowSize, final int gridWidth, final int gridHeight) {
- final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
- spellCheckerProximityInfo.mNativeProximityInfo =
- spellCheckerProximityInfo.setProximityInfoNative("" /* locale */,
- gridWidth /* displayWidth */, gridHeight /* displayHeight */,
- gridWidth, gridHeight, 1 /* mostCommonKeyWidth */, proximityCharsArray,
- 0 /* keyCount */, null /*keyXCoordinates */, null /* keyYCoordinates */,
- null /* keyWidths */, null /* keyHeights */, null /* keyCharCodes */,
- null /* sweetSpotCenterXs */, null /* sweetSpotCenterYs */,
- null /* sweetSpotRadii */);
- return spellCheckerProximityInfo;
+ /**
+ * Constructor for subclasses such as
+ * {@link com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo}.
+ */
+ protected ProximityInfo(final int[] proximityCharsArray, final int gridWidth,
+ final int gridHeight) {
+ this("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
+ mNativeProximityInfo = setProximityInfoNative("" /* locale */,
+ gridWidth /* displayWidth */, gridHeight /* displayHeight */,
+ gridWidth, gridHeight, 1 /* mostCommonKeyWidth */,
+ 1 /* mostCommonKeyHeight */, proximityCharsArray, 0 /* keyCount */,
+ null /*keyXCoordinates */, null /* keyYCoordinates */,
+ null /* keyWidths */, null /* keyHeights */, null /* keyCharCodes */,
+ null /* sweetSpotCenterXs */, null /* sweetSpotCenterYs */,
+ null /* sweetSpotRadii */);
}
private long mNativeProximityInfo;
@@ -105,9 +104,10 @@ public final class ProximityInfo {
// TODO: Stop passing proximityCharsArray
private static native long setProximityInfoNative(String locale,
int displayWidth, int displayHeight, int gridWidth, int gridHeight,
- int mostCommonKeyWidth, int[] proximityCharsArray, int keyCount, int[] keyXCoordinates,
- int[] keyYCoordinates, int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
- float[] sweetSpotCenterXs, float[] sweetSpotCenterYs, float[] sweetSpotRadii);
+ int mostCommonKeyWidth, int mostCommonKeyHeight, int[] proximityCharsArray,
+ int keyCount, int[] keyXCoordinates, int[] keyYCoordinates, int[] keyWidths,
+ int[] keyHeights, int[] keyCharCodes, float[] sweetSpotCenterXs,
+ float[] sweetSpotCenterYs, float[] sweetSpotRadii);
private static native void releaseProximityInfoNative(long nativeProximityInfo);
@@ -234,9 +234,9 @@ public final class ProximityInfo {
// TODO: Stop passing proximityCharsArray
return setProximityInfoNative(mLocaleStr, mKeyboardMinWidth, mKeyboardHeight,
- mGridWidth, mGridHeight, mMostCommonKeyWidth, proximityCharsArray, keyCount,
- keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
- sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
+ mGridWidth, mGridHeight, mMostCommonKeyWidth, mMostCommonKeyHeight,
+ proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths,
+ keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
}
public long getNativeProximityInfo() {
diff --git a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java b/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
new file mode 100644
index 000000000..6a54e119c
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.util.SparseArray;
+
+import com.android.inputmethod.latin.CollectionUtils;
+
+public final class TypefaceUtils {
+ private TypefaceUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ // This sparse array caches key label text height in pixel indexed by key label text size.
+ private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray();
+ // Working variable for the following method.
+ private static final Rect sTextHeightBounds = new Rect();
+
+ public static float getCharHeight(final char[] referenceChar, final Paint paint) {
+ final int key = getCharGeometryCacheKey(referenceChar[0], paint);
+ synchronized (sTextHeightCache) {
+ final Float cachedValue = sTextHeightCache.get(key);
+ if (cachedValue != null) {
+ return cachedValue;
+ }
+
+ paint.getTextBounds(referenceChar, 0, 1, sTextHeightBounds);
+ final float height = sTextHeightBounds.height();
+ sTextHeightCache.put(key, height);
+ return height;
+ }
+ }
+
+ // This sparse array caches key label text width in pixel indexed by key label text size.
+ private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray();
+ // Working variable for the following method.
+ private static final Rect sTextWidthBounds = new Rect();
+
+ public static float getCharWidth(final char[] referenceChar, final Paint paint) {
+ final int key = getCharGeometryCacheKey(referenceChar[0], paint);
+ synchronized (sTextWidthCache) {
+ final Float cachedValue = sTextWidthCache.get(key);
+ if (cachedValue != null) {
+ return cachedValue;
+ }
+
+ paint.getTextBounds(referenceChar, 0, 1, sTextWidthBounds);
+ final float width = sTextWidthBounds.width();
+ sTextWidthCache.put(key, width);
+ return width;
+ }
+ }
+
+ private static int getCharGeometryCacheKey(final char referenceChar, final Paint paint) {
+ final int labelSize = (int)paint.getTextSize();
+ final Typeface face = paint.getTypeface();
+ final int codePointOffset = referenceChar << 15;
+ if (face == Typeface.DEFAULT) {
+ return codePointOffset + labelSize;
+ } else if (face == Typeface.DEFAULT_BOLD) {
+ return codePointOffset + labelSize + 0x1000;
+ } else if (face == Typeface.MONOSPACE) {
+ return codePointOffset + labelSize + 0x2000;
+ } else {
+ return codePointOffset + labelSize;
+ }
+ }
+
+ public static float getLabelWidth(final String label, final Paint paint) {
+ final Rect textBounds = new Rect();
+ paint.getTextBounds(label, 0, label.length(), textBounds);
+ return textBounds.width();
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index e3e6d39e4..7fd1bedcb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -37,6 +37,7 @@ import com.android.inputmethod.latin.ResizableIntArray;
final class GesturePreviewTrail {
private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
+ // These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}.
private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
@@ -50,6 +51,9 @@ final class GesturePreviewTrail {
public final int mTrailColor;
public final float mTrailStartWidth;
public final float mTrailEndWidth;
+ public final float mTrailBodyRatio;
+ public boolean mTrailShadowEnabled;
+ public final float mTrailShadowRatio;
public final int mFadeoutStartDelay;
public final int mFadeoutDuration;
public final int mUpdateInterval;
@@ -63,6 +67,14 @@ final class GesturePreviewTrail {
R.styleable.MainKeyboardView_gesturePreviewTrailStartWidth, 0.0f);
mTrailEndWidth = mainKeyboardViewAttr.getDimension(
R.styleable.MainKeyboardView_gesturePreviewTrailEndWidth, 0.0f);
+ final int PERCENTAGE_INT = 100;
+ mTrailBodyRatio = (float)mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gesturePreviewTrailBodyRatio, PERCENTAGE_INT)
+ / (float)PERCENTAGE_INT;
+ final int trailShadowRatioInt = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_gesturePreviewTrailShadowRatio, 0);
+ mTrailShadowEnabled = (trailShadowRatioInt > 0);
+ mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
mFadeoutStartDelay = mainKeyboardViewAttr.getInt(
R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutStartDelay, 0);
mFadeoutDuration = mainKeyboardViewAttr.getInt(
@@ -90,6 +102,12 @@ final class GesturePreviewTrail {
}
public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
+ synchronized (mEventTimes) {
+ addStrokeLocked(stroke, downTime);
+ }
+ }
+
+ private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
final int trailSize = mEventTimes.getLength();
stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
if (mEventTimes.getLength() == trailSize) {
@@ -169,6 +187,13 @@ final class GesturePreviewTrail {
*/
public boolean drawGestureTrail(final Canvas canvas, final Paint paint,
final Rect outBoundsRect, final Params params) {
+ synchronized (mEventTimes) {
+ return drawGestureTrailLocked(canvas, paint, outBoundsRect, params);
+ }
+ }
+
+ private boolean drawGestureTrailLocked(final Canvas canvas, final Paint paint,
+ final Rect outBoundsRect, final Params params) {
// Initialize bounds rectangle.
outBoundsRect.setEmpty();
final int trailSize = mEventTimes.getLength();
@@ -205,14 +230,22 @@ final class GesturePreviewTrail {
final float r2 = getWidth(elapsedTime, params) / 2.0f;
// Draw trail line only when the current point isn't a down point.
if (!isDownEventXCoord(xCoords[i])) {
- final Path path = roundedLine.makePath(p1x, p1y, r1, p2x, p2y, r2);
+ final float body1 = r1 * params.mTrailBodyRatio;
+ final float body2 = r2 * params.mTrailBodyRatio;
+ final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2);
if (path != null) {
+ roundedLine.getBounds(mRoundedLineBounds);
+ if (params.mTrailShadowEnabled) {
+ final float shadow2 = r2 * params.mTrailShadowRatio;
+ paint.setShadowLayer(shadow2, 0.0f, 0.0f, params.mTrailColor);
+ final int shadowInset = -(int)Math.ceil(shadow2);
+ mRoundedLineBounds.inset(shadowInset, shadowInset);
+ }
+ // Take union for the bounds.
+ outBoundsRect.union(mRoundedLineBounds);
final int alpha = getAlpha(elapsedTime, params);
paint.setAlpha(alpha);
canvas.drawPath(path, paint);
- // Take union for the bounds.
- roundedLine.getBounds(mRoundedLineBounds);
- outBoundsRect.union(mRoundedLineBounds);
}
}
p1x = p2x;
@@ -228,14 +261,14 @@ final class GesturePreviewTrail {
System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
- // The start index of the last segment of the stroke
- // {@link mLastInterpolatedDrawIndex} should also be updated because all array
- // elements have just been shifted for compaction.
- mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0);
}
mEventTimes.setLength(newSize);
mXCoordinates.setLength(newSize);
mYCoordinates.setLength(newSize);
+ // The start index of the last segment of the stroke
+ // {@link mLastInterpolatedDrawIndex} should also be updated because all array
+ // elements have just been shifted for compaction or been zeroed.
+ mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0);
}
return newSize > 0;
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 95d9ccb58..b1d499702 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -20,6 +20,7 @@ import android.text.TextUtils;
import android.util.Log;
import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.RecapitalizeStatus;
/**
* Keyboard state machine.
@@ -29,7 +30,7 @@ import com.android.inputmethod.latin.Constants;
* The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()},
* {@link #onPressKey(int, boolean, int)}, {@link #onReleaseKey(int, boolean)},
* {@link #onCodeInput(int, boolean, int)}, {@link #onCancelInput(boolean)},
- * {@link #onUpdateShiftState(int)}, {@link #onLongPressTimeout(int)}.
+ * {@link #onUpdateShiftState(int, int)}, {@link #onLongPressTimeout(int)}.
*
* The actions are {@link SwitchActions}'s methods.
*/
@@ -48,7 +49,7 @@ public final class KeyboardState {
public void setSymbolsShiftedKeyboard();
/**
- * Request to call back {@link KeyboardState#onUpdateShiftState(int)}.
+ * Request to call back {@link KeyboardState#onUpdateShiftState(int, int)}.
*/
public void requestUpdatingShiftState();
@@ -80,6 +81,7 @@ public final class KeyboardState {
private boolean mIsSymbolShifted;
private boolean mPrevMainKeyboardWasShiftLocked;
private boolean mPrevSymbolsKeyboardWasShifted;
+ private int mRecapitalizeMode;
// For handling long press.
private boolean mLongPressShiftLockFired;
@@ -110,6 +112,7 @@ public final class KeyboardState {
public KeyboardState(final SwitchActions switchActions) {
mSwitchActions = switchActions;
+ mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
}
public void onLoadKeyboard() {
@@ -283,6 +286,7 @@ public final class KeyboardState {
mSwitchActions.setAlphabetKeyboard();
mIsAlphabetMode = true;
mIsSymbolShifted = false;
+ mRecapitalizeMode = RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
mSwitchState = SWITCH_STATE_ALPHA;
mSwitchActions.requestUpdatingShiftState();
}
@@ -386,11 +390,13 @@ public final class KeyboardState {
}
}
- public void onUpdateShiftState(final int autoCaps) {
+ public void onUpdateShiftState(final int autoCaps, final int recapitalizeMode) {
if (DEBUG_EVENT) {
- Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + " " + this);
+ Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + ", recapitalizeMode="
+ + recapitalizeMode + " " + this);
}
- updateAlphabetShiftState(autoCaps);
+ mRecapitalizeMode = recapitalizeMode;
+ updateAlphabetShiftState(autoCaps, recapitalizeMode);
}
// TODO: Remove this method. Come up with a more comprehensive way to reset the keyboard layout
@@ -402,8 +408,28 @@ public final class KeyboardState {
resetKeyboardStateToAlphabet();
}
- private void updateAlphabetShiftState(final int autoCaps) {
+ private void updateShiftStateForRecapitalize(final int recapitalizeMode) {
+ switch (recapitalizeMode) {
+ case RecapitalizeStatus.CAPS_MODE_ALL_UPPER:
+ setShifted(SHIFT_LOCK_SHIFTED);
+ break;
+ case RecapitalizeStatus.CAPS_MODE_FIRST_WORD_UPPER:
+ setShifted(AUTOMATIC_SHIFT);
+ break;
+ case RecapitalizeStatus.CAPS_MODE_ALL_LOWER:
+ case RecapitalizeStatus.CAPS_MODE_ORIGINAL_MIXED_CASE:
+ default:
+ setShifted(UNSHIFT);
+ }
+ }
+
+ private void updateAlphabetShiftState(final int autoCaps, final int recapitalizeMode) {
if (!mIsAlphabetMode) return;
+ if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != recapitalizeMode) {
+ // We are recapitalizing. Match the keyboard to the current recapitalize state.
+ updateShiftStateForRecapitalize(recapitalizeMode);
+ return;
+ }
if (!mShiftKeyState.isReleasing()) {
// Ignore update shift state event while the shift key is being pressed (including
// chording).
@@ -421,6 +447,9 @@ public final class KeyboardState {
private void onPressShift() {
mLongPressShiftLockFired = false;
+ // If we are recapitalizing, we don't do any of the normal processing, including
+ // importantly the double tap timer.
+ if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) return;
if (mIsAlphabetMode) {
mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapTimeout();
if (!mIsInDoubleTapShiftKey) {
@@ -467,7 +496,11 @@ public final class KeyboardState {
}
private void onReleaseShift(final boolean withSliding) {
- if (mIsAlphabetMode) {
+ if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
+ // We are recapitalizing. We should match the keyboard state to the recapitalize
+ // state in priority.
+ updateShiftStateForRecapitalize(mRecapitalizeMode);
+ } else if (mIsAlphabetMode) {
final boolean isShiftLocked = mAlphabetShiftState.isShiftLocked();
mIsInAlphabetUnshiftedFromShifted = false;
if (mIsInDoubleTapShiftKey) {
@@ -597,7 +630,7 @@ public final class KeyboardState {
// If the code is a letter, update keyboard shift state.
if (Constants.isLetterCode(code)) {
- updateAlphabetShiftState(autoCaps);
+ updateAlphabetShiftState(autoCaps, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 2df7e5cf5..6bc6acc0f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -34,175 +34,197 @@ public final class PointerTrackerQueue {
}
private static final int INITIAL_CAPACITY = 10;
+ // Note: {@link #mExpandableArrayOfActivePointers} and {@link #mArraySize} are synchronized by
+ // {@link #mExpandableArrayOfActivePointers}
private final ArrayList<Element> mExpandableArrayOfActivePointers =
CollectionUtils.newArrayList(INITIAL_CAPACITY);
private int mArraySize = 0;
- public synchronized int size() {
- return mArraySize;
+ public int size() {
+ synchronized (mExpandableArrayOfActivePointers) {
+ return mArraySize;
+ }
}
- public synchronized void add(final Element pointer) {
- final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
- final int arraySize = mArraySize;
- if (arraySize < expandableArray.size()) {
- expandableArray.set(arraySize, pointer);
- } else {
- expandableArray.add(pointer);
+ public void add(final Element pointer) {
+ synchronized (mExpandableArrayOfActivePointers) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ if (arraySize < expandableArray.size()) {
+ expandableArray.set(arraySize, pointer);
+ } else {
+ expandableArray.add(pointer);
+ }
+ mArraySize = arraySize + 1;
}
- mArraySize = arraySize + 1;
}
- public synchronized void remove(final Element pointer) {
- final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
- final int arraySize = mArraySize;
- int newSize = 0;
- for (int index = 0; index < arraySize; index++) {
- final Element element = expandableArray.get(index);
- if (element == pointer) {
+ public void remove(final Element pointer) {
+ synchronized (mExpandableArrayOfActivePointers) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ int newSize = 0;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ if (newSize != index) {
+ Log.w(TAG, "Found duplicated element in remove: " + pointer);
+ }
+ continue; // Remove this element from the expandableArray.
+ }
if (newSize != index) {
- Log.w(TAG, "Found duplicated element in remove: " + pointer);
+ // Shift this element toward the beginning of the expandableArray.
+ expandableArray.set(newSize, element);
}
- continue; // Remove this element from the expandableArray.
- }
- if (newSize != index) {
- // Shift this element toward the beginning of the expandableArray.
- expandableArray.set(newSize, element);
+ newSize++;
}
- newSize++;
+ mArraySize = newSize;
}
- mArraySize = newSize;
}
- public synchronized Element getOldestElement() {
- return (mArraySize == 0) ? null : mExpandableArrayOfActivePointers.get(0);
+ public Element getOldestElement() {
+ synchronized (mExpandableArrayOfActivePointers) {
+ return (mArraySize == 0) ? null : mExpandableArrayOfActivePointers.get(0);
+ }
}
- public synchronized void releaseAllPointersOlderThan(final Element pointer,
- final long eventTime) {
- if (DEBUG) {
- Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
- }
- final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
- final int arraySize = mArraySize;
- int newSize, index;
- for (newSize = index = 0; index < arraySize; index++) {
- final Element element = expandableArray.get(index);
- if (element == pointer) {
- break; // Stop releasing elements.
- }
- if (!element.isModifier()) {
- element.onPhantomUpEvent(eventTime);
- continue; // Remove this element from the expandableArray.
+ public void releaseAllPointersOlderThan(final Element pointer, final long eventTime) {
+ synchronized (mExpandableArrayOfActivePointers) {
+ if (DEBUG) {
+ Log.d(TAG, "releaseAllPoniterOlderThan: " + pointer + " " + this);
}
- if (newSize != index) {
- // Shift this element toward the beginning of the expandableArray.
- expandableArray.set(newSize, element);
- }
- newSize++;
- }
- // Shift rest of the expandableArray.
- int count = 0;
- for (; index < arraySize; index++) {
- final Element element = expandableArray.get(index);
- if (element == pointer) {
- if (count > 0) {
- Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
- + pointer);
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ int newSize, index;
+ for (newSize = index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ break; // Stop releasing elements.
+ }
+ if (!element.isModifier()) {
+ element.onPhantomUpEvent(eventTime);
+ continue; // Remove this element from the expandableArray.
+ }
+ if (newSize != index) {
+ // Shift this element toward the beginning of the expandableArray.
+ expandableArray.set(newSize, element);
}
- count++;
- }
- if (newSize != index) {
- expandableArray.set(newSize, expandableArray.get(index));
newSize++;
}
+ // Shift rest of the expandableArray.
+ int count = 0;
+ for (; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ if (count > 0) {
+ Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: "
+ + pointer);
+ }
+ count++;
+ }
+ if (newSize != index) {
+ expandableArray.set(newSize, expandableArray.get(index));
+ newSize++;
+ }
+ }
+ mArraySize = newSize;
}
- mArraySize = newSize;
}
public void releaseAllPointers(final long eventTime) {
releaseAllPointersExcept(null, eventTime);
}
- public synchronized void releaseAllPointersExcept(final Element pointer,
- final long eventTime) {
- if (DEBUG) {
- if (pointer == null) {
- Log.d(TAG, "releaseAllPoniters: " + this);
- } else {
- Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
- }
- }
- final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
- final int arraySize = mArraySize;
- int newSize = 0, count = 0;
- for (int index = 0; index < arraySize; index++) {
- final Element element = expandableArray.get(index);
- if (element == pointer) {
- if (count > 0) {
- Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " + pointer);
+ public void releaseAllPointersExcept(final Element pointer, final long eventTime) {
+ synchronized (mExpandableArrayOfActivePointers) {
+ if (DEBUG) {
+ if (pointer == null) {
+ Log.d(TAG, "releaseAllPoniters: " + this);
+ } else {
+ Log.d(TAG, "releaseAllPoniterExcept: " + pointer + " " + this);
}
- count++;
- } else {
- element.onPhantomUpEvent(eventTime);
- continue; // Remove this element from the expandableArray.
}
- if (newSize != index) {
- // Shift this element toward the beginning of the expandableArray.
- expandableArray.set(newSize, element);
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ int newSize = 0, count = 0;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ if (count > 0) {
+ Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: "
+ + pointer);
+ }
+ count++;
+ } else {
+ element.onPhantomUpEvent(eventTime);
+ continue; // Remove this element from the expandableArray.
+ }
+ if (newSize != index) {
+ // Shift this element toward the beginning of the expandableArray.
+ expandableArray.set(newSize, element);
+ }
+ newSize++;
}
- newSize++;
+ mArraySize = newSize;
}
- mArraySize = newSize;
}
- public synchronized boolean hasModifierKeyOlderThan(final Element pointer) {
- final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
- final int arraySize = mArraySize;
- for (int index = 0; index < arraySize; index++) {
- final Element element = expandableArray.get(index);
- if (element == pointer) {
- return false; // Stop searching modifier key.
- }
- if (element.isModifier()) {
- return true;
+ public boolean hasModifierKeyOlderThan(final Element pointer) {
+ synchronized (mExpandableArrayOfActivePointers) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element == pointer) {
+ return false; // Stop searching modifier key.
+ }
+ if (element.isModifier()) {
+ return true;
+ }
}
+ return false;
}
- return false;
}
- public synchronized boolean isAnyInSlidingKeyInput() {
- final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
- final int arraySize = mArraySize;
- for (int index = 0; index < arraySize; index++) {
- final Element element = expandableArray.get(index);
- if (element.isInSlidingKeyInput()) {
- return true;
+ public boolean isAnyInSlidingKeyInput() {
+ synchronized (mExpandableArrayOfActivePointers) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (element.isInSlidingKeyInput()) {
+ return true;
+ }
}
+ return false;
}
- return false;
}
- public synchronized void cancelAllPointerTracker() {
- final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
- final int arraySize = mArraySize;
- for (int index = 0; index < arraySize; index++) {
- final Element element = expandableArray.get(index);
- element.cancelTracking();
+ public void cancelAllPointerTracker() {
+ synchronized (mExpandableArrayOfActivePointers) {
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ element.cancelTracking();
+ }
}
}
@Override
- public synchronized String toString() {
- final StringBuilder sb = new StringBuilder();
- final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
- final int arraySize = mArraySize;
- for (int index = 0; index < arraySize; index++) {
- final Element element = expandableArray.get(index);
- if (sb.length() > 0)
- sb.append(" ");
- sb.append(element.toString());
+ public String toString() {
+ synchronized (mExpandableArrayOfActivePointers) {
+ final StringBuilder sb = new StringBuilder();
+ final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers;
+ final int arraySize = mArraySize;
+ for (int index = 0; index < arraySize; index++) {
+ final Element element = expandableArray.get(index);
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append(element.toString());
+ }
+ return "[" + sb.toString() + "]";
}
- return "[" + sb.toString() + "]";
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
index 37f4e3582..33dbbafa3 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
@@ -30,7 +30,7 @@ import com.android.inputmethod.latin.R;
* Draw rubber band preview graphics during sliding key input.
*/
public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
- private final int mPreviewWidth;
+ private final float mPreviewBodyRadius;
private boolean mShowSlidingKeyInputPreview;
private final int[] mPreviewFrom = CoordinateUtils.newInstance();
@@ -44,8 +44,20 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
super(drawingView);
final int previewColor = mainKeyboardViewAttr.getColor(
R.styleable.MainKeyboardView_slidingKeyInputPreviewColor, 0);
- mPreviewWidth = mainKeyboardViewAttr.getDimensionPixelSize(
- R.styleable.MainKeyboardView_slidingKeyInputPreviewWidth, 0);
+ final float previewRadius = mainKeyboardViewAttr.getDimension(
+ R.styleable.MainKeyboardView_slidingKeyInputPreviewWidth, 0) / 2.0f;
+ final int PERCENTAGE_INT = 100;
+ final float previewBodyRatio = (float)mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_slidingKeyInputPreviewBodyRatio, PERCENTAGE_INT)
+ / (float)PERCENTAGE_INT;
+ mPreviewBodyRadius = previewRadius * previewBodyRatio;
+ final int previewShadowRatioInt = mainKeyboardViewAttr.getInt(
+ R.styleable.MainKeyboardView_slidingKeyInputPreviewShadowRatio, 0);
+ if (previewShadowRatioInt > 0) {
+ final float previewShadowRatio = (float)previewShadowRatioInt / (float)PERCENTAGE_INT;
+ final float shadowRadius = previewRadius * previewShadowRatio;
+ mPaint.setShadowLayer(shadowRadius, 0.0f, 0.0f, previewColor);
+ }
mPaint.setColor(previewColor);
}
@@ -65,7 +77,7 @@ public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
}
// TODO: Finalize the rubber band preview implementation.
- final int radius = mPreviewWidth / 2;
+ final float radius = mPreviewBodyRadius;
final Path path = mRoundedLine.makePath(
CoordinateUtils.x(mPreviewFrom), CoordinateUtils.y(mPreviewFrom), radius,
CoordinateUtils.x(mPreviewTo), CoordinateUtils.y(mPreviewTo), radius);
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 562e1d0b7..42f713697 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -422,7 +422,7 @@ public final class BinaryDictionaryFileDumper {
private static void reinitializeClientRecordInDictionaryContentProvider(final Context context,
final ContentProviderClient client, final String clientId) throws RemoteException {
- final String metadataFileUri = context.getString(R.string.dictionary_pack_metadata_uri);
+ final String metadataFileUri = MetadataFileUriGetter.getMetadataUri(context);
if (TextUtils.isEmpty(metadataFileUri)) return;
// Tell the content provider to reset all information about this client id
final Uri metadataContentUri = getProviderUriBuilder(clientId)
diff --git a/java/src/com/android/inputmethod/latin/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
index 1012cd519..4b8d1ac11 100644
--- a/java/src/com/android/inputmethod/latin/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/CapsModeUtils.java
@@ -41,7 +41,7 @@ public final class CapsModeUtils {
if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) {
return s.toUpperCase(locale);
} else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) {
- return StringUtils.toTitleCase(s, locale);
+ return StringUtils.capitalizeFirstCodePoint(s, locale);
} else {
return s;
}
diff --git a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
new file mode 100644
index 000000000..792a446c9
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.TextUtils;
+import android.view.inputmethod.CompletionInfo;
+
+import java.util.Arrays;
+
+/**
+ * Utilities to do various stuff with CompletionInfo.
+ */
+public class CompletionInfoUtils {
+ private CompletionInfoUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ public static CompletionInfo[] removeNulls(final CompletionInfo[] src) {
+ int j = 0;
+ final CompletionInfo[] dst = new CompletionInfo[src.length];
+ for (int i = 0; i < src.length; ++i) {
+ if (null != src[i] && !TextUtils.isEmpty(src[i].getText())) {
+ dst[j] = src[i];
+ ++j;
+ }
+ }
+ return Arrays.copyOfRange(dst, 0, j);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 50e50233e..86bb25562 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -160,6 +160,8 @@ public final class Constants {
public static final int CODE_DOUBLE_QUOTE = '"';
public static final int CODE_QUESTION_MARK = '?';
public static final int CODE_EXCLAMATION_MARK = '!';
+ public static final int CODE_SLASH = '/';
+ public static final int CODE_COMMERCIAL_AT = '@';
// TODO: Check how this should work for right-to-left languages. It seems to stand
// that for rtl languages, a closing parenthesis is a left parenthesis. Is this
// managed by the font? Or is it a different char?
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 8b5a76a17..75c2cf2c8 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -173,7 +173,8 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
// capitalization of i.
final int wordLen = StringUtils.codePointCount(word);
if (wordLen < MAX_WORD_LENGTH && wordLen > 1) {
- super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS);
+ super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS,
+ false /* isNotAWord */);
if (!TextUtils.isEmpty(prevWord)) {
if (mUseFirstLastBigrams) {
super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM);
@@ -251,7 +252,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary {
}
private static boolean isValidName(final String name) {
- if (name != null && -1 == name.indexOf('@')) {
+ if (name != null && -1 == name.indexOf(Constants.CODE_COMMERCIAL_AT)) {
return true;
}
return false;
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index ff3d83fad..9691fa231 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -37,6 +37,8 @@ public abstract class Dictionary {
public static final String TYPE_USER = "user";
// User history dictionary internal to LatinIME.
public static final String TYPE_USER_HISTORY = "history";
+ // Spawned by resuming suggestions. Comes from a span that was in the TextView.
+ public static final String TYPE_RESUMED = "resumed";
protected final String mDictType;
public Dictionary(final String dictType) {
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 97dc6a8ac..4b1975a00 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -176,14 +176,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
*/
// TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
// considering performance regression.
- protected void addWord(final String word, final String shortcutTarget, final int frequency) {
+ protected void addWord(final String word, final String shortcutTarget, final int frequency,
+ final boolean isNotAWord) {
if (shortcutTarget == null) {
- mFusionDictionary.add(word, frequency, null, false /* isNotAWord */);
+ mFusionDictionary.add(word, frequency, null, isNotAWord);
} else {
// TODO: Do this in the subclass, with this class taking an arraylist.
final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
- mFusionDictionary.add(word, frequency, shortcutTargets, false /* isNotAWord */);
+ mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
}
}
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index ecb20144b..46194f6e4 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -33,7 +33,6 @@ public final class InputTypeUtils implements InputType {
private static final int[] SUPPRESSING_AUTO_SPACES_FIELD_VARIATION = {
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
InputType.TYPE_TEXT_VARIATION_PASSWORD,
- InputType.TYPE_TEXT_VARIATION_URI,
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD };
public static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 56b1c786e..efa51569f 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -44,7 +44,9 @@ import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
+import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
@@ -72,6 +74,7 @@ import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.latin.RichInputConnection.Range;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.Utils.Stats;
import com.android.inputmethod.latin.define.ProductionFlag;
@@ -158,6 +161,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mPositionalInfoForUserDictPendingAddition = null;
private final WordComposer mWordComposer = new WordComposer();
private final RichInputConnection mConnection = new RichInputConnection(this);
+ private final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus();
// Keep track of the last selection range to decide if we need to show word alternatives
private static final int NOT_A_CURSOR_POSITION = -1;
@@ -197,6 +201,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private static final int MSG_PENDING_IMS_CALLBACK = 1;
private static final int MSG_UPDATE_SUGGESTION_STRIP = 2;
private static final int MSG_SHOW_GESTURE_PREVIEW_AND_SUGGESTION_STRIP = 3;
+ private static final int MSG_RESUME_SUGGESTIONS = 4;
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@@ -234,6 +239,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
latinIme.showGesturePreviewAndSuggestionStrip((SuggestedWords)msg.obj,
msg.arg1 == ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
break;
+ case MSG_RESUME_SUGGESTIONS:
+ latinIme.restartSuggestionsOnWordTouchedByCursor();
+ break;
}
}
@@ -241,6 +249,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions);
}
+ public void postResumeSuggestions() {
+ sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
+ }
+
public void cancelUpdateSuggestionStrip() {
removeMessages(MSG_UPDATE_SUGGESTION_STRIP);
}
@@ -427,6 +439,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mHandler.onCreate();
DEBUG = LatinImeLogger.sDBG;
+ // TODO: Resolve mutual dependencies of {@link #loadSettings()} and {@link #initSuggest()}.
loadSettings();
initSuggest();
@@ -464,6 +477,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final InputAttributes inputAttributes =
new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode());
mSettings.loadSettings(locale, inputAttributes);
+ // May need to reset the contacts dictionary depending on the user settings.
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
}
@@ -730,8 +744,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
resetComposingState(true /* alsoResetLastComposedWord */);
mDeleteCount = 0;
mSpaceState = SPACE_STATE_NONE;
+ mRecapitalizeStatus.deactivate();
mCurrentlyPressedHardwareKeys.clear();
+ // Note: the following does a round-trip IPC on the main thread: be careful
+ final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
+ if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) {
+ initSuggest();
+ }
if (mSuggestionStripView != null) {
// This will set the punctuation suggestions if next word suggestion is off;
// otherwise it will clear the suggestion strip.
@@ -784,8 +804,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// to the user dictionary.
if (null != mPositionalInfoForUserDictPendingAddition
&& mPositionalInfoForUserDictPendingAddition.tryReplaceWithActualWord(
- mConnection, editorInfo, mLastSelectionEnd,
- mSubtypeSwitcher.getCurrentSubtypeLocale())) {
+ mConnection, editorInfo, mLastSelectionEnd, currentLocale)) {
mPositionalInfoForUserDictPendingAddition = null;
}
// If tryReplaceWithActualWord returns false, we don't know what word was
@@ -910,13 +929,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
resetEntireInputState(newSelStart);
}
+ // We moved the cursor. If we are touching a word, we need to resume suggestion.
+ mHandler.postResumeSuggestions();
+ // Reset the last recapitalization.
+ mRecapitalizeStatus.deactivate();
mKeyboardSwitcher.updateShiftState();
}
mExpectingUpdateSelection = false;
- // TODO: Decide to call restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() or not
- // here. It would probably be too expensive to call directly here but we may want to post a
- // message to delay it. The point would be to unify behavior between backspace to the
- // end of a word and manually put the pointer at the end of the word.
// Make a note of the cursor position
mLastSelectionStart = newSelStart;
@@ -983,7 +1002,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
}
if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return;
- mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
if (applicationSpecifiedCompletions == null) {
clearSuggestionStrip();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -991,6 +1009,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
return;
}
+ mApplicationSpecifiedCompletions =
+ CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions);
final ArrayList<SuggestedWords.SuggestedWordInfo> applicationSuggestedWords =
SuggestedWords.getFromApplicationSpecifiedCompletions(
@@ -1166,6 +1186,15 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
SPACE_STATE_PHANTOM == mSpaceState);
}
+ public int getCurrentRecapitalizeState() {
+ if (!mRecapitalizeStatus.isActive()
+ || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ // Not recapitalizing at the moment
+ return RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE;
+ }
+ return mRecapitalizeStatus.getCurrentMode();
+ }
+
// Factor in auto-caps and manual caps and compute the current caps mode.
private int getActualCapsMode() {
final int keyboardShiftMode = mKeyboardSwitcher.getKeyboardShiftMode();
@@ -1243,10 +1272,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
} else {
wordToEdit = word;
}
- mPositionalInfoForUserDictPendingAddition =
- new PositionalInfoForUserDictPendingAddition(
- wordToEdit, mLastSelectionEnd, getCurrentInputEditorInfo(),
- mLastComposedWord.mCapitalizedMode);
mUserDictionary.addWordToUserDictionary(wordToEdit);
}
@@ -1380,8 +1405,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
LatinImeLogger.logOnDelete(x, y);
break;
case Constants.CODE_SHIFT:
+ // Note: calling back to the keyboard on Shift key is handled in onPressKey()
+ // and onReleaseKey().
+ final Keyboard currentKeyboard = switcher.getKeyboard();
+ if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
+ // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
+ // alphabetic shift and shift while in symbol layout.
+ handleRecapitalize();
+ }
+ break;
case Constants.CODE_SWITCH_ALPHA_SYMBOL:
- // Shift and symbol key is handled in onPressKey() and onReleaseKey().
+ // Note: calling back to the keyboard on symbol key is handled in onPressKey()
+ // and onReleaseKey().
break;
case Constants.CODE_SETTINGS:
onSettingsKeyPressed();
@@ -1459,7 +1494,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
"", mWordComposer.getTypedWord(), " ", mWordComposer);
}
}
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the character at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ } else {
+ commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ }
}
final int keyX, keyY;
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
@@ -1515,8 +1556,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
final int wordComposerSize = mWordComposer.size();
// Since isComposingWord() is true, the size is at least 1.
- final int lastChar = mWordComposer.getCodeAt(wordComposerSize - 1);
- if (wordComposerSize <= 1) {
+ final int lastChar = mWordComposer.getCodeBeforeCursor();
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the batch input at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ } else if (wordComposerSize <= 1) {
// We auto-correct the previous (typed, not gestured) string iff it's one character
// long. The reason for this is, even in the middle of gesture typing, you'll still
// tap one-letter words and you want them auto-corrected (typically, "i" in English
@@ -1540,7 +1585,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
} else {
final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
- if (mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+ if (Character.isLetter(codePointBeforeCursor)
+ || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
mSpaceState = SPACE_STATE_PHANTOM;
}
}
@@ -1551,7 +1597,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private static final class BatchInputUpdater implements Handler.Callback {
private final Handler mHandler;
private LatinIME mLatinIme;
- private boolean mInBatchInput; // synchronized using "this".
+ private final Object mLock = new Object();
+ private boolean mInBatchInput; // synchronized using {@link #mLock}.
private BatchInputUpdater() {
final HandlerThread handlerThread = new HandlerThread(
@@ -1582,21 +1629,25 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
// Run in the UI thread.
- public synchronized void onStartBatchInput(final LatinIME latinIme) {
- mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
- mLatinIme = latinIme;
- mInBatchInput = true;
+ public void onStartBatchInput(final LatinIME latinIme) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
+ mLatinIme = latinIme;
+ mInBatchInput = true;
+ }
}
// Run in the Handler thread.
- private synchronized void updateBatchInput(final InputPointers batchPointers) {
- if (!mInBatchInput) {
- // Batch input has ended or canceled while the message was being delivered.
- return;
+ private void updateBatchInput(final InputPointers batchPointers) {
+ synchronized (mLock) {
+ if (!mInBatchInput) {
+ // Batch input has ended or canceled while the message was being delivered.
+ return;
+ }
+ final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ suggestedWords, false /* dismissGestureFloatingPreviewText */);
}
- final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- suggestedWords, false /* dismissGestureFloatingPreviewText */);
}
// Run in the UI thread.
@@ -1609,19 +1660,23 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
.sendToTarget();
}
- public synchronized void onCancelBatchInput() {
- mInBatchInput = false;
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+ public void onCancelBatchInput() {
+ synchronized (mLock) {
+ mInBatchInput = false;
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ SuggestedWords.EMPTY, true /* dismissGestureFloatingPreviewText */);
+ }
}
// Run in the UI thread.
- public synchronized SuggestedWords onEndBatchInput(final InputPointers batchPointers) {
- mInBatchInput = false;
- final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
- mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
- suggestedWords, true /* dismissGestureFloatingPreviewText */);
- return suggestedWords;
+ public SuggestedWords onEndBatchInput(final InputPointers batchPointers) {
+ synchronized (mLock) {
+ mInBatchInput = false;
+ final SuggestedWords suggestedWords = getSuggestedWordsGestureLocked(batchPointers);
+ mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+ suggestedWords, true /* dismissGestureFloatingPreviewText */);
+ return suggestedWords;
+ }
}
// {@link LatinIME#getSuggestedWords(int)} method calls with same session id have to
@@ -1717,6 +1772,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// during key repeat.
mHandler.postUpdateShiftState();
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can remove the character at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ // When we exit this if-clause, mWordComposer.isComposingWord() will return false.
+ }
if (mWordComposer.isComposingWord()) {
final int length = mWordComposer.size();
if (length > 0) {
@@ -1727,7 +1788,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
ResearchLogger.getInstance().uncommitCurrentLogUnit(
word, false /* dumpCurrentLogUnit */);
}
+ final String rejectedSuggestion = mWordComposer.getTypedWord();
mWordComposer.reset();
+ mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
} else {
mWordComposer.deleteLast();
}
@@ -1848,6 +1911,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
promotePhantomSpace();
}
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the character at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ isComposingWord = false;
+ }
// NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several
// dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
// thread here.
@@ -1902,6 +1971,38 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
}
+ private void handleRecapitalize() {
+ if (mLastSelectionStart == mLastSelectionEnd) return; // No selection
+ // If we have a recapitalize in progress, use it; otherwise, create a new one.
+ if (!mRecapitalizeStatus.isActive()
+ || !mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd,
+ mConnection.getSelectedText(0 /* flags, 0 for no styles */).toString(),
+ mSettings.getCurrentLocale(), mSettings.getWordSeparators());
+ // We trim leading and trailing whitespace.
+ mRecapitalizeStatus.trim();
+ // Trimming the object may have changed the length of the string, and we need to
+ // reposition the selection handles accordingly. As this result in an IPC call,
+ // only do it if it's actually necessary, in other words if the recapitalize status
+ // is not set at the same place as before.
+ if (!mRecapitalizeStatus.isSetAt(mLastSelectionStart, mLastSelectionEnd)) {
+ mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
+ mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
+ mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
+ }
+ }
+ mRecapitalizeStatus.rotate();
+ final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
+ mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0);
+ mLastSelectionStart = mRecapitalizeStatus.getNewCursorStart();
+ mLastSelectionEnd = mRecapitalizeStatus.getNewCursorEnd();
+ mConnection.setSelection(mLastSelectionStart, mLastSelectionEnd);
+ // Match the keyboard to the new state.
+ mKeyboardSwitcher.updateShiftState();
+ }
+
// Returns true if we did an autocorrection, false otherwise.
private boolean handleSeparator(final int primaryCode, final int x, final int y,
final int spaceState) {
@@ -1909,7 +2010,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
}
boolean didAutoCorrect = false;
- // Handle separator
+ if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
+ // If we are in the middle of a recorrection, we need to commit the recorrection
+ // first so that we can insert the separator at the current cursor position.
+ resetEntireInputState(mLastSelectionStart);
+ }
if (mWordComposer.isComposingWord()) {
if (mSettings.getCurrent().mCorrectionEnabled) {
// TODO: maybe cache Strings in an <String> sparse array or something
@@ -2321,6 +2426,73 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
/**
+ * Check if the cursor is touching a word. If so, restart suggestions on this word, else
+ * do nothing.
+ */
+ private void restartSuggestionsOnWordTouchedByCursor() {
+ // If the cursor is not touching a word, or if there is a selection, return right away.
+ if (mLastSelectionStart != mLastSelectionEnd) return;
+ if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
+ final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
+ 0 /* additionalPrecedingWordsCount */);
+ if (null == range) return; // Happens if we don't have an input connection at all
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ final String typedWord = range.mWord.toString();
+ if (range.mWord instanceof SpannableString) {
+ final SpannableString spannableString = (SpannableString)range.mWord;
+ int i = 0;
+ for (Object object : spannableString.getSpans(0, spannableString.length(),
+ SuggestionSpan.class)) {
+ SuggestionSpan span = (SuggestionSpan)object;
+ for (String s : span.getSuggestions()) {
+ ++i;
+ if (!TextUtils.equals(s, typedWord)) {
+ suggestions.add(new SuggestedWordInfo(s,
+ SuggestionStripView.MAX_SUGGESTIONS - i,
+ SuggestedWordInfo.KIND_RESUMED, Dictionary.TYPE_RESUMED));
+ }
+ }
+ }
+ }
+ mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setCursorPositionWithinWord(range.mCharsBefore);
+ mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore,
+ mLastSelectionEnd + range.mCharsAfter);
+ final SuggestedWords suggestedWords;
+ if (suggestions.isEmpty()) {
+ // We come here if there weren't any suggestion spans on this word. We will try to
+ // compute suggestions for it instead.
+ final SuggestedWords suggestedWordsIncludingTypedWord =
+ getSuggestedWords(Suggest.SESSION_TYPING);
+ if (suggestedWordsIncludingTypedWord.size() > 1) {
+ // We were able to compute new suggestions for this word.
+ // Remove the typed word, since we don't want to display it in this case.
+ // The #getSuggestedWordsExcludingTypedWord() method sets willAutoCorrect to false.
+ suggestedWords =
+ suggestedWordsIncludingTypedWord.getSuggestedWordsExcludingTypedWord();
+ } else {
+ // No saved suggestions, and we were unable to compute any good one either.
+ // Rather than displaying an empty suggestion strip, we'll display the original
+ // word alone in the middle.
+ // Since there is only one word, willAutoCorrect is false.
+ suggestedWords = suggestedWordsIncludingTypedWord;
+ }
+ } else {
+ // We found suggestion spans in the word. We'll create the SuggestedWords out of
+ // them, and make willAutoCorrect false.
+ suggestedWords = new SuggestedWords(suggestions,
+ true /* typedWordValid */, false /* willAutoCorrect */,
+ false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
+ false /* isPrediction */);
+ }
+
+ // Note that it's very important here that suggestedWords.mWillAutoCorrect is false.
+ // We never want to auto-correct on a resumed suggestion. Please refer to the three
+ // places above where suggestedWords is affected.
+ showSuggestionStrip(suggestedWords, typedWord);
+ }
+
+ /**
* Check if the cursor is actually at the end of a word. If so, restart suggestions on this
* word, else do nothing.
*/
@@ -2328,17 +2500,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final CharSequence word =
mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
if (null != word) {
- restartSuggestionsOnWordBeforeCursor(word);
+ final String wordString = word.toString();
+ restartSuggestionsOnWordBeforeCursor(wordString);
// TODO: Handle the case where the user manually moves the cursor and then backs up over
// a separator. In that case, the current log unit should not be uncommitted.
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().uncommitCurrentLogUnit(word.toString(),
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(wordString,
true /* dumpCurrentLogUnit */);
}
}
}
- private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) {
+ private void restartSuggestionsOnWordBeforeCursor(final String word) {
mWordComposer.setComposingWord(word, mKeyboardSwitcher.getKeyboard());
final int length = word.length();
mConnection.deleteSurroundingText(length, 0);
@@ -2392,7 +2565,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// This essentially inserts a space, and that's it.
public void promotePhantomSpace() {
- if (mSettings.getCurrent().shouldInsertSpacesAutomatically()) {
+ if (mSettings.getCurrent().shouldInsertSpacesAutomatically()
+ && !mConnection.textBeforeCursorLooksLikeURL()) {
sendKeyCodePoint(Constants.CODE_SPACE);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.latinIME_promotePhantomSpace();
@@ -2409,8 +2583,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Outside LatinIME, only used by the {@link InputTestsBase} test suite.
@UsedForTesting
void loadKeyboard() {
- // When the device locale is changed in SetupWizard etc., this method may get called via
- // onConfigurationChanged before SoftInputWindow is shown.
+ // TODO: Why are we calling {@link #loadSettings()} and {@link #initSuggest()} in a
+ // different order than in {@link #onStartInputView}?
initSuggest();
loadSettings();
if (mKeyboardSwitcher.getMainKeyboardView() != null) {
diff --git a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
new file mode 100644
index 000000000..e6dc6db8f
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+
+/**
+ * Helper class to get the metadata URI.
+ */
+public class MetadataFileUriGetter {
+ public static String getMetadataUri(Context context) {
+ return context.getString(R.string.dictionary_pack_metadata_uri);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
new file mode 100644
index 000000000..8a704ab42
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.StringUtils;
+
+import java.util.Locale;
+
+/**
+ * The status of the current recapitalize process.
+ */
+public class RecapitalizeStatus {
+ public static final int NOT_A_RECAPITALIZE_MODE = -1;
+ public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0;
+ public static final int CAPS_MODE_ALL_LOWER = 1;
+ public static final int CAPS_MODE_FIRST_WORD_UPPER = 2;
+ public static final int CAPS_MODE_ALL_UPPER = 3;
+ // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant.
+ public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER;
+
+ private static final int[] ROTATION_STYLE = {
+ CAPS_MODE_ORIGINAL_MIXED_CASE,
+ CAPS_MODE_ALL_LOWER,
+ CAPS_MODE_FIRST_WORD_UPPER,
+ CAPS_MODE_ALL_UPPER
+ };
+
+ private static final int getStringMode(final String string, final String separators) {
+ if (StringUtils.isIdenticalAfterUpcase(string)) {
+ return CAPS_MODE_ALL_UPPER;
+ } else if (StringUtils.isIdenticalAfterDowncase(string)) {
+ return CAPS_MODE_ALL_LOWER;
+ } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) {
+ return CAPS_MODE_FIRST_WORD_UPPER;
+ } else {
+ return CAPS_MODE_ORIGINAL_MIXED_CASE;
+ }
+ }
+
+ /**
+ * We store the location of the cursor and the string that was there before the recapitalize
+ * action was done, and the location of the cursor and the string that was there after.
+ */
+ private int mCursorStartBefore;
+ private String mStringBefore;
+ private int mCursorStartAfter;
+ private int mCursorEndAfter;
+ private int mRotationStyleCurrentIndex;
+ private boolean mSkipOriginalMixedCaseMode;
+ private Locale mLocale;
+ private String mSeparators;
+ private String mStringAfter;
+ private boolean mIsActive;
+
+ public RecapitalizeStatus() {
+ // By default, initialize with dummy values that won't match any real recapitalize.
+ initialize(-1, -1, "", Locale.getDefault(), "");
+ deactivate();
+ }
+
+ public void initialize(final int cursorStart, final int cursorEnd, final String string,
+ final Locale locale, final String separators) {
+ mCursorStartBefore = cursorStart;
+ mStringBefore = string;
+ mCursorStartAfter = cursorStart;
+ mCursorEndAfter = cursorEnd;
+ mStringAfter = string;
+ final int initialMode = getStringMode(mStringBefore, separators);
+ mLocale = locale;
+ mSeparators = separators;
+ if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) {
+ mRotationStyleCurrentIndex = 0;
+ mSkipOriginalMixedCaseMode = false;
+ } else {
+ // Find the current mode in the array.
+ int currentMode;
+ for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) {
+ if (ROTATION_STYLE[currentMode] == initialMode) {
+ break;
+ }
+ }
+ mRotationStyleCurrentIndex = currentMode;
+ mSkipOriginalMixedCaseMode = true;
+ }
+ mIsActive = true;
+ }
+
+ public void deactivate() {
+ mIsActive = false;
+ }
+
+ public boolean isActive() {
+ return mIsActive;
+ }
+
+ public boolean isSetAt(final int cursorStart, final int cursorEnd) {
+ return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter;
+ }
+
+ /**
+ * Rotate through the different possible capitalization modes.
+ */
+ public void rotate() {
+ final String oldResult = mStringAfter;
+ int count = 0; // Protection against infinite loop.
+ do {
+ mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
+ if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex]
+ && mSkipOriginalMixedCaseMode) {
+ mRotationStyleCurrentIndex =
+ (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length;
+ }
+ ++count;
+ switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) {
+ case CAPS_MODE_ORIGINAL_MIXED_CASE:
+ mStringAfter = mStringBefore;
+ break;
+ case CAPS_MODE_ALL_LOWER:
+ mStringAfter = mStringBefore.toLowerCase(mLocale);
+ break;
+ case CAPS_MODE_FIRST_WORD_UPPER:
+ mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators,
+ mLocale);
+ break;
+ case CAPS_MODE_ALL_UPPER:
+ mStringAfter = mStringBefore.toUpperCase(mLocale);
+ break;
+ default:
+ mStringAfter = mStringBefore;
+ }
+ } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1);
+ mCursorEndAfter = mCursorStartAfter + mStringAfter.length();
+ }
+
+ /**
+ * Remove leading/trailing whitespace from the considered string.
+ */
+ public void trim() {
+ final int len = mStringBefore.length();
+ int nonWhitespaceStart = 0;
+ for (; nonWhitespaceStart < len;
+ nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) {
+ final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart);
+ if (!Character.isWhitespace(codePoint)) break;
+ }
+ int nonWhitespaceEnd = len;
+ for (; nonWhitespaceEnd > 0;
+ nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) {
+ final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd);
+ if (!Character.isWhitespace(codePoint)) break;
+ }
+ if (0 != nonWhitespaceStart || len != nonWhitespaceEnd) {
+ mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
+ mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart;
+ mStringAfter = mStringBefore =
+ mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd);
+ }
+ }
+
+ public String getRecapitalizedString() {
+ return mStringAfter;
+ }
+
+ public int getNewCursorStart() {
+ return mCursorStartAfter;
+ }
+
+ public int getNewCursorEnd() {
+ return mCursorEndAfter;
+ }
+
+ public int getCurrentMode() {
+ return ROTATION_STYLE[mRotationStyleCurrentIndex];
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 16744d1f0..8ed7ab264 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin;
import android.inputmethodservice.InputMethodService;
+import android.text.SpannableString;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -181,6 +182,11 @@ public final class RichInputConnection {
}
}
+ public CharSequence getSelectedText(final int flags) {
+ if (null == mIC) return null;
+ return mIC.getSelectedText(flags);
+ }
+
/**
* Gets the caps modes we should be in after this specific string.
*
@@ -392,7 +398,9 @@ public final class RichInputConnection {
public void commitCompletion(final CompletionInfo completionInfo) {
if (DEBUG_BATCH_NESTING) checkBatchEdit();
if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
- final CharSequence text = completionInfo.getText();
+ CharSequence text = completionInfo.getText();
+ // text should never be null, but just in case, it's better to insert nothing than to crash
+ if (null == text) text = "";
mCommittedTextBeforeComposingText.append(text);
mCurrentCursorPosition += text.length() - mComposingText.length();
mComposingText.setLength(0);
@@ -442,9 +450,9 @@ public final class RichInputConnection {
public final int mCharsAfter;
/** The actual characters that make up a word */
- public final String mWord;
+ public final CharSequence mWord;
- public Range(int charsBefore, int charsAfter, String word) {
+ public Range(int charsBefore, int charsAfter, CharSequence word) {
if (charsBefore < 0 || charsAfter < 0) {
throw new IndexOutOfBoundsException();
}
@@ -498,7 +506,7 @@ public final class RichInputConnection {
* separator. For example, if the field contains "he|llo world", where |
* represents the cursor, then "hello " will be returned.
*/
- public String getWordAtCursor(String separators) {
+ public CharSequence getWordAtCursor(String separators) {
// getWordRangeAtCursor returns null if the connection is null
Range r = getWordRangeAtCursor(separators, 0);
return (r == null) ? null : r.mWord;
@@ -517,8 +525,10 @@ public final class RichInputConnection {
if (mIC == null || sep == null) {
return null;
}
- final CharSequence before = mIC.getTextBeforeCursor(1000, 0);
- final CharSequence after = mIC.getTextAfterCursor(1000, 0);
+ final CharSequence before = mIC.getTextBeforeCursor(1000,
+ InputConnection.GET_TEXT_WITH_STYLES);
+ final CharSequence after = mIC.getTextAfterCursor(1000,
+ InputConnection.GET_TEXT_WITH_STYLES);
if (before == null || after == null) {
return null;
}
@@ -560,8 +570,9 @@ public final class RichInputConnection {
}
}
- final String word = before.toString().substring(startIndexInBefore, before.length())
- + after.toString().substring(0, endIndexInAfter);
+ final SpannableString word = new SpannableString(TextUtils.concat(
+ before.subSequence(startIndexInBefore, before.length()),
+ after.subSequence(0, endIndexInAfter)));
return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
}
@@ -709,4 +720,15 @@ public final class RichInputConnection {
// position and the expected position, then it must be a belated update.
return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0;
}
+
+ /**
+ * Looks at the text just before the cursor to find out if it looks like a URL.
+ *
+ * The weakest point here is, if we don't have enough text bufferized, we may fail to realize
+ * we are in URL situation, but other places in this class have the same limitation and it
+ * does not matter too much in the practice.
+ */
+ public boolean textBeforeCursorLooksLikeURL() {
+ return StringUtils.lastPartLooksLikeURL(mCommittedTextBeforeComposingText);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 8fbe843cf..72e08700a 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -134,6 +134,14 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mIsInternal;
}
+ public String getWordSeparators() {
+ return mSettingsValues.mWordSeparators;
+ }
+
+ public Locale getCurrentLocale() {
+ return mCurrentLocale;
+ }
+
// Accessed from the settings interface, hence public
public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
final Resources res) {
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/SettingsActivity.java
index 99b572e06..37ac2e35c 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/SettingsActivity.java
@@ -25,7 +25,10 @@ public final class SettingsActivity extends PreferenceActivity {
@Override
public Intent getIntent() {
final Intent intent = super.getIntent();
- intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
+ final String fragment = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
+ if (fragment == null) {
+ intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
+ }
intent.putExtra(EXTRA_NO_HEADERS, true);
return intent;
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index 5405a5eb7..a96c997c8 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -16,7 +16,6 @@
package com.android.inputmethod.latin;
-import android.app.Activity;
import android.app.backup.BackupManager;
import android.content.Context;
import android.content.Intent;
@@ -115,12 +114,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment
if (FeedbackUtils.isFeedbackFormSupported()) {
feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
- public boolean onPreferenceClick(Preference arg0) {
- final Activity activity = getActivity();
- FeedbackUtils.showFeedbackForm(activity);
- if (!activity.isFinishing()) {
- activity.finish();
- }
+ public boolean onPreferenceClick(final Preference pref) {
+ FeedbackUtils.showFeedbackForm(getActivity());
return true;
}
});
@@ -232,10 +227,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment
} else if (key.equals(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY)) {
setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST,
Settings.readShowsLanguageSwitchKey(prefs));
- } else if (key.equals(Settings.PREF_GESTURE_INPUT)) {
- final boolean gestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
- setPreferenceEnabled(Settings.PREF_GESTURE_PREVIEW_TRAIL, gestureInputEnabled);
- setPreferenceEnabled(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, gestureInputEnabled);
} else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
}
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 59ad28fc9..d5ee58a63 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/StringUtils.java
@@ -106,10 +106,19 @@ public final class StringUtils {
}
}
- public static String toTitleCase(final String s, final Locale locale) {
+ public static String capitalizeFirstCodePoint(final String s, final Locale locale) {
if (s.length() <= 1) {
- // TODO: is this really correct? Shouldn't this be s.toUpperCase()?
- return s;
+ return s.toUpperCase(locale);
+ }
+ // Please refer to the comment below in
+ // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings
+ final int cutoff = s.offsetByCodePoints(0, 1);
+ return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff);
+ }
+
+ public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) {
+ if (s.length() <= 1) {
+ return s.toUpperCase(locale);
}
// TODO: fix the bugs below
// - This does not work for Greek, because it returns upper case instead of title case.
@@ -213,4 +222,129 @@ public final class StringUtils {
if (1 == capsCount) return CAPITALIZE_FIRST;
return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE);
}
+
+ public static boolean isIdenticalAfterUpcase(final String text) {
+ final int len = text.length();
+ for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+ final int codePoint = text.codePointAt(i);
+ if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isIdenticalAfterDowncase(final String text) {
+ final int len = text.length();
+ for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+ final int codePoint = text.codePointAt(i);
+ if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isIdenticalAfterCapitalizeEachWord(final String text,
+ final String separators) {
+ boolean needCapsNext = true;
+ final int len = text.length();
+ for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+ final int codePoint = text.codePointAt(i);
+ if (Character.isLetter(codePoint)) {
+ if ((needCapsNext && !Character.isUpperCase(codePoint))
+ || (!needCapsNext && !Character.isLowerCase(codePoint))) {
+ return false;
+ }
+ }
+ // We need a capital letter next if this is a separator.
+ needCapsNext = (-1 != separators.indexOf(codePoint));
+ }
+ return true;
+ }
+
+ // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph
+ // which should be capitalized together in *some* cases.
+ public static String capitalizeEachWord(final String text, final String separators,
+ final Locale locale) {
+ final StringBuilder builder = new StringBuilder();
+ boolean needCapsNext = true;
+ final int len = text.length();
+ for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) {
+ final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1));
+ if (needCapsNext) {
+ builder.append(nextChar.toUpperCase(locale));
+ } else {
+ builder.append(nextChar.toLowerCase(locale));
+ }
+ // We need a capital letter next if this is a separator.
+ needCapsNext = (-1 != separators.indexOf(nextChar.codePointAt(0)));
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Approximates whether the text before the cursor looks like a URL.
+ *
+ * This is not foolproof, but it should work well in the practice.
+ * Essentially it walks backward from the cursor until it finds something that's not a letter,
+ * digit, or common URL symbol like underscore. If it hasn't found a period yet, then it
+ * does not look like a URL.
+ * If the text:
+ * - starts with www and contains a period
+ * - starts with a slash preceded by either a slash, whitespace, or start-of-string
+ * Then it looks like a URL and we return true. Otherwise, we return false.
+ *
+ * Note: this method is called quite often, and should be fast.
+ *
+ * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the
+ * code complexity, but ideally it should not. It's acceptable for now.
+ */
+ public static boolean lastPartLooksLikeURL(final CharSequence text) {
+ int i = text.length();
+ if (0 == i) return false;
+ int wCount = 0;
+ int slashCount = 0;
+ boolean hasSlash = false;
+ boolean hasPeriod = false;
+ int codePoint = 0;
+ while (i > 0) {
+ codePoint = Character.codePointBefore(text, i);
+ if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') {
+ // Handwavy heuristic to see if that's a URL character. Anything between period
+ // and z. This includes all lower- and upper-case ascii letters, period,
+ // underscore, arrobase, question mark, equal sign. It excludes spaces, exclamation
+ // marks, double quotes...
+ // Anything that's not a URL-like character causes us to break from here and
+ // evaluate normally.
+ break;
+ }
+ if (Constants.CODE_PERIOD == codePoint) {
+ hasPeriod = true;
+ }
+ if (Constants.CODE_SLASH == codePoint) {
+ hasSlash = true;
+ if (2 == ++slashCount) {
+ return true;
+ }
+ } else {
+ slashCount = 0;
+ }
+ if ('w' == codePoint) {
+ ++wCount;
+ } else {
+ wCount = 0;
+ }
+ i = Character.offsetByCodePoints(text, i, -1);
+ }
+ // End of the text run.
+ // If it starts with www and includes a period, then it looks like a URL.
+ if (wCount >= 3 && hasPeriod) return true;
+ // If it starts with a slash, and the code point before is whitespace, it looks like an URL.
+ if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true;
+ // If it has both a period and a slash, it looks like an URL.
+ if (hasPeriod && hasSlash) return true;
+ // Otherwise, it doesn't look like an URL.
+ return false;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
index 5e28cc2d0..4d88ecc0c 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java
@@ -183,7 +183,7 @@ public final class SubtypeLocale {
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
displayName = locale.getDisplayName(displayLocale);
}
- return StringUtils.toTitleCase(displayName, displayLocale);
+ return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
}
// InputMethodSubtype's display name in its locale.
@@ -243,7 +243,7 @@ public final class SubtypeLocale {
}
}
};
- return StringUtils.toTitleCase(
+ return StringUtils.capitalizeFirstCodePoint(
getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 975664dca..59d0207f6 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -47,6 +47,9 @@ public final class Suggest {
// TODO: rename this to CORRECTION_ON
public static final int CORRECTION_FULL = 1;
+ // Close to -2**31
+ private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
+
public interface SuggestInitializationListener {
public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
}
@@ -65,7 +68,7 @@ public final class Suggest {
private float mAutoCorrectionThreshold;
// Locale used for upper- and title-casing words
- private final Locale mLocale;
+ public final Locale mLocale;
public Suggest(final Context context, final Locale locale,
final SuggestInitializationListener listener) {
@@ -334,7 +337,21 @@ public final class Suggest {
}
}
+ if (suggestionsContainer.size() > 1 && TextUtils.equals(suggestionsContainer.get(0).mWord,
+ wordComposer.getRejectedBatchModeSuggestion())) {
+ final SuggestedWordInfo rejected = suggestionsContainer.remove(0);
+ suggestionsContainer.add(1, rejected);
+ }
SuggestedWordInfo.removeDups(suggestionsContainer);
+
+ // For some reason some suggestions with MIN_VALUE are making their way here.
+ // TODO: Find a more robust way to detect distractors.
+ for (int i = suggestionsContainer.size() - 1; i >= 0; --i) {
+ if (suggestionsContainer.get(i).mScore < SUPPRESS_SUGGEST_THRESHOLD) {
+ suggestionsContainer.remove(i);
+ }
+ }
+
// In the batch input mode, the most relevant suggested word should act as a "typed word"
// (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
return new SuggestedWords(suggestionsContainer,
@@ -394,7 +411,7 @@ public final class Suggest {
if (isAllUpperCase) {
sb.append(wordInfo.mWord.toUpperCase(locale));
} else if (isFirstCharCapitalized) {
- sb.append(StringUtils.toTitleCase(wordInfo.mWord, locale));
+ sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale));
} else {
sb.append(wordInfo.mWord);
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 3d6fe2d22..616e1911b 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -131,6 +131,7 @@ public final class SuggestedWords {
public static final int KIND_APP_DEFINED = 6; // Suggested by the application
public static final int KIND_SHORTCUT = 7; // A shortcut
public static final int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
+ public static final int KIND_RESUMED = 9; // A resumed suggestion (comes from a span)
public final String mWord;
public final int mScore;
public final int mKind; // one of the KIND_* constants above
@@ -194,4 +195,21 @@ public final class SuggestedWords {
}
}
}
+
+ // SuggestedWords is an immutable object, as much as possible. We must not just remove
+ // words from the member ArrayList as some other parties may expect the object to never change.
+ public SuggestedWords getSuggestedWordsExcludingTypedWord() {
+ final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList();
+ for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) {
+ final SuggestedWordInfo info = mSuggestedWordInfoList.get(i);
+ if (SuggestedWordInfo.KIND_TYPED != info.mKind) {
+ newSuggestions.add(info);
+ }
+ }
+ // We should never autocorrect, so we say the typed word is valid. Also, in this case,
+ // no auto-correction should take place hence willAutoCorrect = false.
+ return new SuggestedWords(newSuggestions, true /* typedWordValid */,
+ false /* willAutoCorrect */, mIsPunctuationSuggestions, mIsObsoleteSuggestions,
+ mIsPrediction);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 0d5bde623..90f92972a 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -20,7 +20,6 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
-import android.content.Intent;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
@@ -28,7 +27,10 @@ import android.os.Build;
import android.provider.UserDictionary.Words;
import android.text.TextUtils;
+import com.android.inputmethod.compat.UserDictionaryCompatUtils;
+
import java.util.Arrays;
+import java.util.Locale;
/**
* An expandable dictionary that stores the words in the user dictionary provider into a binary
@@ -61,10 +63,6 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
private static final String NAME = "userunigram";
- // This is not exported by the framework so we pretty much have to write it here verbatim
- private static final String ACTION_USER_DICTIONARY_INSERT =
- "com.android.settings.USER_DICTIONARY_INSERT";
-
private ContentObserver mObserver;
final private String mLocale;
final private boolean mAlsoUseMoreRestrictiveLocales;
@@ -211,23 +209,19 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
/**
* Adds a word to the user dictionary and makes it persistent.
*
- * This will call upon the system interface to do the actual work through the intent readied by
- * the system to this effect.
- *
* @param word the word to add. If the word is capitalized, then the dictionary will
* recognize it as a capitalized word when searched.
*/
public synchronized void addWordToUserDictionary(final String word) {
- // TODO: do something for the UI. With the following, any sufficiently long word will
- // look like it will go to the user dictionary but it won't.
- // Safeguard against adding long words. Can cause stack overflow.
- if (word.length() >= MAX_WORD_LENGTH) return;
-
- Intent intent = new Intent(ACTION_USER_DICTIONARY_INSERT);
- intent.putExtra(Words.WORD, word);
- intent.putExtra(Words.LOCALE, mLocale);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
+ // Update the user dictionary provider
+ final Locale locale;
+ if (USER_DICTIONARY_ALL_LANGUAGES == mLocale) {
+ locale = null;
+ } else {
+ locale = LocaleUtils.constructLocaleFromString(mLocale);
+ }
+ UserDictionaryCompatUtils.addWord(mContext, word,
+ HISTORICAL_DEFAULT_USER_DICTIONARY_FREQUENCY, null, locale);
}
private int scaleFrequencyFromDefaultToLatinIme(final int defaultFrequency) {
@@ -258,10 +252,10 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary {
final int adjustedFrequency = scaleFrequencyFromDefaultToLatinIme(frequency);
// Safeguard against adding really long words.
if (word.length() < MAX_WORD_LENGTH) {
- super.addWord(word, null, adjustedFrequency);
+ super.addWord(word, null, adjustedFrequency, false /* isNotAWord */);
}
if (null != shortcut && shortcut.length() < MAX_WORD_LENGTH) {
- super.addWord(shortcut, word, adjustedFrequency);
+ super.addWord(shortcut, word, adjustedFrequency, true /* isNotAWord */);
}
cursor.moveToNext();
}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index f7cb4346a..51bd901fb 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -27,6 +27,7 @@ import java.util.Arrays;
*/
public final class WordComposer {
private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
+ private static final boolean DBG = LatinImeLogger.sDBG;
public static final int CAPS_MODE_OFF = 0;
// 1 is shift bit, 2 is caps bit, 4 is auto bit but this is just a convention as these bits
@@ -42,6 +43,13 @@ public final class WordComposer {
private String mAutoCorrection;
private boolean mIsResumed;
private boolean mIsBatchMode;
+ // A memory of the last rejected batch mode suggestion, if any. This goes like this: the user
+ // gestures a word, is displeased with the results and hits backspace, then gestures again.
+ // At the very least we should avoid re-suggesting the same thing, and to do that we memorize
+ // the rejected suggestion in this variable.
+ // TODO: this should be done in a comprehensive way by the User History feature instead of
+ // as an ad-hockery here.
+ private String mRejectedBatchModeSuggestion;
// Cache these values for performance
private int mCapsCount;
@@ -49,6 +57,7 @@ public final class WordComposer {
private int mCapitalizedMode;
private int mTrailingSingleQuotesCount;
private int mCodePointSize;
+ private int mCursorPositionWithinWord;
/**
* Whether the user chose to capitalize the first char of the word.
@@ -62,6 +71,8 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
+ mCursorPositionWithinWord = 0;
+ mRejectedBatchModeSuggestion = null;
refreshSize();
}
@@ -76,6 +87,8 @@ public final class WordComposer {
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode;
+ mCursorPositionWithinWord = source.mCursorPositionWithinWord;
+ mRejectedBatchModeSuggestion = source.mRejectedBatchModeSuggestion;
refreshSize();
}
@@ -91,6 +104,8 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
+ mCursorPositionWithinWord = 0;
+ mRejectedBatchModeSuggestion = null;
refreshSize();
}
@@ -118,6 +133,13 @@ public final class WordComposer {
return mPrimaryKeyCodes[index];
}
+ public int getCodeBeforeCursor() {
+ if (mCursorPositionWithinWord < 1 || mCursorPositionWithinWord > mPrimaryKeyCodes.length) {
+ return Constants.NOT_A_CODE;
+ }
+ return mPrimaryKeyCodes[mCursorPositionWithinWord - 1];
+ }
+
public InputPointers getInputPointers() {
return mInputPointers;
}
@@ -135,6 +157,7 @@ public final class WordComposer {
final int newIndex = size();
mTypedWord.appendCodePoint(primaryCode);
refreshSize();
+ mCursorPositionWithinWord = mCodePointSize;
if (newIndex < MAX_WORD_LENGTH) {
mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE
? Character.toLowerCase(primaryCode) : primaryCode;
@@ -158,6 +181,18 @@ public final class WordComposer {
mAutoCorrection = null;
}
+ public void setCursorPositionWithinWord(final int posWithinWord) {
+ mCursorPositionWithinWord = posWithinWord;
+ }
+
+ public boolean isCursorFrontOrMiddleOfComposingWord() {
+ if (DBG && mCursorPositionWithinWord > mCodePointSize) {
+ throw new RuntimeException("Wrong cursor position : " + mCursorPositionWithinWord
+ + "in a word of size " + mCodePointSize);
+ }
+ return mCursorPositionWithinWord != mCodePointSize;
+ }
+
public void setBatchInputPointers(final InputPointers batchPointers) {
mInputPointers.set(batchPointers);
mIsBatchMode = true;
@@ -242,6 +277,7 @@ public final class WordComposer {
++mTrailingSingleQuotesCount;
}
}
+ mCursorPositionWithinWord = mCodePointSize;
mAutoCorrection = null;
}
@@ -368,7 +404,9 @@ public final class WordComposer {
mCapitalizedMode = CAPS_MODE_OFF;
refreshSize();
mAutoCorrection = null;
+ mCursorPositionWithinWord = 0;
mIsResumed = false;
+ mRejectedBatchModeSuggestion = null;
return lastComposedWord;
}
@@ -380,10 +418,20 @@ public final class WordComposer {
refreshSize();
mCapitalizedMode = lastComposedWord.mCapitalizedMode;
mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
+ mCursorPositionWithinWord = mCodePointSize;
+ mRejectedBatchModeSuggestion = null;
mIsResumed = true;
}
public boolean isBatchMode() {
return mIsBatchMode;
}
+
+ public void setRejectedBatchModeSuggestion(final String rejectedSuggestion) {
+ mRejectedBatchModeSuggestion = rejectedSuggestion;
+ }
+
+ public String getRejectedBatchModeSuggestion() {
+ return mRejectedBatchModeSuggestion;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 58ec1e83f..467f6a053 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -1467,8 +1467,8 @@ public final class BinaryDictInputOutput {
if (null == last) continue;
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
buffer.position(last.mChildrenAddress + headerSize);
- groupOffset = last.mChildrenAddress + 1;
- i = buffer.readUnsignedByte();
+ i = readCharGroupCount(buffer);
+ groupOffset = last.mChildrenAddress + getGroupCountSize(i);
last = null;
continue;
}
@@ -1477,8 +1477,8 @@ public final class BinaryDictInputOutput {
if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
buffer.position(last.mChildrenAddress + headerSize);
- groupOffset = last.mChildrenAddress + 1;
- i = buffer.readUnsignedByte();
+ i = readCharGroupCount(buffer);
+ groupOffset = last.mChildrenAddress + getGroupCountSize(i);
last = null;
continue;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index e7c7e2b8a..17d281518 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -647,7 +647,7 @@ public final class FusionDictionary implements Iterable<Word> {
if (index < codePoints.length) return null;
if (!currentGroup.isTerminal()) return null;
- if (DBG && !codePoints.equals(checker.toString())) return null;
+ if (DBG && !string.equals(checker.toString())) return null;
return currentGroup;
}
@@ -853,16 +853,19 @@ public final class FusionDictionary implements Iterable<Word> {
if (currentPos.pos.hasNext()) {
final CharGroup currentGroup = currentPos.pos.next();
currentPos.length = mCurrentString.length();
- for (int i : currentGroup.mChars)
+ for (int i : currentGroup.mChars) {
mCurrentString.append(Character.toChars(i));
+ }
if (null != currentGroup.mChildren) {
currentPos = new Position(currentGroup.mChildren.mData);
+ currentPos.length = mCurrentString.length();
mPositions.addLast(currentPos);
}
- if (currentGroup.mFrequency >= 0)
+ if (currentGroup.mFrequency >= 0) {
return new Word(mCurrentString.toString(), currentGroup.mFrequency,
currentGroup.mShortcutTargets, currentGroup.mBigrams,
currentGroup.mIsNotAWord, currentGroup.mIsBlacklistEntry);
+ }
} else {
mPositions.removeLast();
currentPos = mPositions.getLast();
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 8d3b062ff..2d0a89bb3 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -330,7 +330,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
} else if (StringUtils.CAPITALIZE_FIRST == capitalizeType) {
for (int i = 0; i < mSuggestions.size(); ++i) {
// Likewise
- mSuggestions.set(i, StringUtils.toTitleCase(
+ mSuggestions.set(i, StringUtils.capitalizeFirstCodePoint(
mSuggestions.get(i).toString(), locale));
}
}
@@ -403,11 +403,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public DictAndProximity createDictAndProximity(final Locale locale) {
final int script = getScriptFromLocale(locale);
- final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo(
- SpellCheckerProximityInfo.getProximityForScript(script),
- SpellCheckerProximityInfo.ROW_SIZE,
- SpellCheckerProximityInfo.PROXIMITY_GRID_WIDTH,
- SpellCheckerProximityInfo.PROXIMITY_GRID_HEIGHT);
+ final ProximityInfo proximityInfo = new SpellCheckerProximityInfo(script);
final DictionaryCollection dictionaryCollection =
DictionaryFactory.createMainDictionaryFromManager(this, locale,
true /* useFullEditDistance */);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index b15063235..da8657201 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -189,10 +189,12 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
int letterCount = 0;
for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) {
final int codePoint = text.codePointAt(i);
- // Any word containing a '@' is probably an e-mail address
- // Any word containing a '/' is probably either an ad-hoc combination of two
+ // Any word containing a COMMERCIAL_AT is probably an e-mail address
+ // Any word containing a SLASH is probably either an ad-hoc combination of two
// words or a URI - in either case we don't want to spell check that
- if ('@' == codePoint || '/' == codePoint) return true;
+ if (Constants.CODE_COMMERCIAL_AT == codePoint || Constants.CODE_SLASH == codePoint) {
+ return true;
+ }
if (isLetterCheckableByLanguage(codePoint, script)) ++letterCount;
}
// Guestimate heuristic: perform spell checking if at least 3/4 of the characters
@@ -226,7 +228,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
// If the lower case version is not in the dictionary, it's still possible
// that we have an all-caps version of a word that needs to be capitalized
// according to the dictionary. E.g. "GERMANS" only exists in the dictionary as "Germans".
- return dict.isValidWord(StringUtils.toTitleCase(lowerCaseText, mLocale));
+ return dict.isValidWord(StringUtils.capitalizeFirstAndDowncaseRest(lowerCaseText, mLocale));
}
// Note : this must be reentrant
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 49dca21e6..0c480eaba 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -16,53 +16,40 @@
package com.android.inputmethod.latin.spellcheck;
-import com.android.inputmethod.annotations.UsedForTesting;
+import android.util.SparseIntArray;
+
import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
-import java.util.TreeMap;
+public final class SpellCheckerProximityInfo extends ProximityInfo {
+ public SpellCheckerProximityInfo(final int script) {
+ super(getProximityForScript(script), PROXIMITY_GRID_WIDTH, PROXIMITY_GRID_HEIGHT);
+ }
-public final class SpellCheckerProximityInfo {
- @UsedForTesting
- final public static int NUL = Constants.NOT_A_CODE;
+ private static final int NUL = Constants.NOT_A_CODE;
// This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
// native code - this value is passed at creation of the binary object and reused
// as the size of the passed array afterwards so they can't be different.
- final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
+ private static final int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
// The number of keys in a row of the grid used by the spell checker.
- final public static int PROXIMITY_GRID_WIDTH = 11;
+ private static final int PROXIMITY_GRID_WIDTH = 11;
// The number of rows in the grid used by the spell checker.
- final public static int PROXIMITY_GRID_HEIGHT = 3;
+ private static final int PROXIMITY_GRID_HEIGHT = 3;
- final private static int NOT_AN_INDEX = -1;
- final public static int NOT_A_COORDINATE_PAIR = -1;
+ private static final int NOT_AN_INDEX = -1;
+ public static final int NOT_A_COORDINATE_PAIR = -1;
// Helper methods
- final protected static void buildProximityIndices(final int[] proximity,
- final TreeMap<Integer, Integer> indices) {
- for (int i = 0; i < proximity.length; i += ROW_SIZE) {
- if (NUL != proximity[i]) indices.put(proximity[i], i / ROW_SIZE);
+ static void buildProximityIndices(final int[] proximity, final int rowSize,
+ final SparseIntArray indices) {
+ for (int i = 0; i < proximity.length; i += rowSize) {
+ if (NUL != proximity[i]) indices.put(proximity[i], i / rowSize);
}
}
- final protected static int computeIndex(final int characterCode,
- final TreeMap<Integer, Integer> indices) {
- final Integer result = indices.get(characterCode);
- if (null == result) return NOT_AN_INDEX;
- return result;
- }
private static final class Latin {
- // This is a map from the code point to the index in the PROXIMITY array.
- // At the time the native code to read the binary dictionary needs the proximity info be
- // passed as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input
- // character.
- // Since we need to build such an array, we want to be able to search in our big proximity
- // data quickly by character, and a map is probably the best way to do this.
- final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
-
// The proximity here is the union of
// - the proximity for a QWERTY keyboard.
// - the proximity for an AZERTY keyboard.
@@ -79,7 +66,7 @@ public final class SpellCheckerProximityInfo {
a s d f g h j k l
z x c v b n m
*/
- final static int[] PROXIMITY = {
+ static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -121,16 +108,21 @@ public final class SpellCheckerProximityInfo {
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
+
+ // This is a mapping array from the code point to the index in the PROXIMITY array.
+ // When we check the spelling of a word, we need to pass (x,y) coordinates to the native
+ // code for each letter of the word. These are most easily computed from the index in the
+ // PROXIMITY array. Since we'll need to do that very often, the index lookup from the code
+ // point needs to be as fast as possible, and a map is probably the best way to do this.
+ // To avoid unnecessary boxing conversion to Integer, here we use SparseIntArray.
+ static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
static {
- buildProximityIndices(PROXIMITY, INDICES);
- }
- static int getIndexOf(int characterCode) {
- return computeIndex(characterCode, INDICES);
+ buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
private static final class Cyrillic {
- final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters.
/*
@@ -207,7 +199,7 @@ public final class SpellCheckerProximityInfo {
private static final int CY_SOFT_SIGN = '\u044C'; // ь
private static final int CY_BE = '\u0431'; // б
private static final int CY_YU = '\u044E'; // ю
- final static int[] PROXIMITY = {
+ static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -280,16 +272,15 @@ public final class SpellCheckerProximityInfo {
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
+
+ static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
static {
- buildProximityIndices(PROXIMITY, INDICES);
- }
- static int getIndexOf(int characterCode) {
- return computeIndex(characterCode, INDICES);
+ buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
private static final class Greek {
- final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Greek
// speakers on commonly misspelled words/letters.
/*
@@ -354,7 +345,7 @@ public final class SpellCheckerProximityInfo {
private static final int GR_BETA = '\u03B2'; // β
private static final int GR_NU = '\u03BD'; // ν
private static final int GR_MU = '\u03BC'; // μ
- final static int[] PROXIMITY = {
+ static final int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
// The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
@@ -419,37 +410,37 @@ public final class SpellCheckerProximityInfo {
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
+
+ static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE);
+
static {
- buildProximityIndices(PROXIMITY, INDICES);
- }
- static int getIndexOf(int characterCode) {
- return computeIndex(characterCode, INDICES);
+ buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES);
}
}
- public static int[] getProximityForScript(final int script) {
+ private static int[] getProximityForScript(final int script) {
switch (script) {
- case AndroidSpellCheckerService.SCRIPT_LATIN:
- return Latin.PROXIMITY;
- case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
- return Cyrillic.PROXIMITY;
- case AndroidSpellCheckerService.SCRIPT_GREEK:
- return Greek.PROXIMITY;
- default:
- throw new RuntimeException("Wrong script supplied: " + script);
+ case AndroidSpellCheckerService.SCRIPT_LATIN:
+ return Latin.PROXIMITY;
+ case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+ return Cyrillic.PROXIMITY;
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ return Greek.PROXIMITY;
+ default:
+ throw new RuntimeException("Wrong script supplied: " + script);
}
}
private static int getIndexOfCodeForScript(final int codePoint, final int script) {
switch (script) {
- case AndroidSpellCheckerService.SCRIPT_LATIN:
- return Latin.getIndexOf(codePoint);
- case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
- return Cyrillic.getIndexOf(codePoint);
- case AndroidSpellCheckerService.SCRIPT_GREEK:
- return Greek.getIndexOf(codePoint);
- default:
- throw new RuntimeException("Wrong script supplied: " + script);
+ case AndroidSpellCheckerService.SCRIPT_LATIN:
+ return Latin.INDICES.get(codePoint, NOT_AN_INDEX);
+ case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
+ return Cyrillic.INDICES.get(codePoint, NOT_AN_INDEX);
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ return Greek.INDICES.get(codePoint, NOT_AN_INDEX);
+ default:
+ throw new RuntimeException("Wrong script supplied: " + script);
}
}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index ed408bb3c..3037669c0 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -16,12 +16,14 @@
package com.android.inputmethod.latin.suggestions;
+import android.content.Context;
import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.TypefaceUtils;
import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -50,16 +52,12 @@ public final class MoreSuggestions extends Keyboard {
super();
}
- // TODO: Remove {@link MoreSuggestionsView} argument.
public int layout(final SuggestedWords suggestions, final int fromPos, final int maxWidth,
- final int minWidth, final int maxRow, final MoreSuggestionsView view) {
+ final int minWidth, final int maxRow, final Paint paint, final Resources res) {
clearKeys();
- final Resources res = view.getResources();
mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
mDividerWidth = mDivider.getIntrinsicWidth();
- final int padding = (int) res.getDimension(
- R.dimen.more_suggestions_key_horizontal_padding);
- final Paint paint = view.newDefaultLabelPaint();
+ final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
int row = 0;
int pos = fromPos, rowStartPos = fromPos;
@@ -67,7 +65,7 @@ public final class MoreSuggestions extends Keyboard {
while (pos < size) {
final String word = suggestions.getWord(pos);
// TODO: Should take care of text x-scaling.
- mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding;
+ mWidths[pos] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
final int numColumn = pos - rowStartPos + 1;
final int columnWidth =
(maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
@@ -169,8 +167,8 @@ public final class MoreSuggestions extends Keyboard {
private int mFromPos;
private int mToPos;
- public Builder(final MoreSuggestionsView paneView) {
- super(paneView.getContext(), new MoreSuggestionsParam());
+ public Builder(final Context context, final MoreSuggestionsView paneView) {
+ super(context, new MoreSuggestionsParam());
mPaneView = paneView;
}
@@ -183,7 +181,7 @@ public final class MoreSuggestions extends Keyboard {
mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
- mPaneView);
+ mPaneView.newLabelPaint(null /* key */), mResources);
mFromPos = fromPos;
mToPos = fromPos + count;
mSuggestions = suggestions;
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
index 438820d17..6509f394b 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -43,7 +43,13 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
}
public void updateKeyboardGeometry(final int keyHeight) {
- mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+ updateKeyDrawParams(keyHeight);
+ }
+
+ public void adjustVerticalCorrectionForModalMode() {
+ // Set vertical correction to zero (Reset more keys keyboard sliding allowance
+ // {@link R#dimen.more_keys_keyboard_slide_allowance}).
+ mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop());
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index eeaf828a7..2a21ec2f5 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -596,7 +596,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
.findViewById(R.id.more_suggestions_view);
- mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView);
+ mMoreSuggestionsBuilder = new MoreSuggestions.Builder(context, mMoreSuggestionsView);
final Resources res = context.getResources();
mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
@@ -755,8 +755,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
@Override
public boolean dispatchTouchEvent(final MotionEvent me) {
- if (!mMoreSuggestionsView.isShowingInParent()
- || mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) {
+ if (!mMoreSuggestionsView.isShowingInParent()) {
mLastX = (int)me.getX();
mLastY = (int)me.getY();
if (mMoreSuggestionsSlidingDetector.onTouchEvent(me)) {
@@ -784,6 +783,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
// Decided to be in the modal input mode
mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE;
+ mMoreSuggestionsView.adjustVerticalCorrectionForModalMode();
}
return true;
}
diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
index c5f095919..4f86526a7 100644
--- a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java
@@ -25,9 +25,10 @@ import android.content.Intent;
*/
public final class BootBroadcastReceiver extends BroadcastReceiver {
@Override
- public void onReceive(Context context, Intent intent) {
+ public void onReceive(final Context context, final Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
- ResearchLogger.scheduleUploadingService(context);
+ UploaderService.cancelAndRescheduleUploadingService(context,
+ true /* needsRescheduling */);
}
}
}
diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java
index e1cc2da73..fbfd9b531 100644
--- a/java/src/com/android/inputmethod/research/MotionEventReader.java
+++ b/java/src/com/android/inputmethod/research/MotionEventReader.java
@@ -22,6 +22,7 @@ import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.BufferedReader;
@@ -64,6 +65,7 @@ public class MotionEventReader {
return replayData;
}
+ @UsedForTesting
static class ReplayData {
final ArrayList<Integer> mActions = new ArrayList<Integer>();
final ArrayList<PointerProperties[]> mPointerPropertiesArrays
@@ -134,6 +136,7 @@ public class MotionEventReader {
* },
* </pre>
*/
+ @UsedForTesting
/* package for test */ void readLogStatement(final JsonReader jsonReader,
final ReplayData replayData) throws IOException {
String logStatementType = null;
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index fa124f3ba..7a23ddb05 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -20,16 +20,13 @@ import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOAR
import android.accounts.Account;
import android.accounts.AccountManager;
-import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
@@ -74,22 +71,17 @@ import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.research.MotionEventReader.ReplayData;
-import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStreamReader;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
-import java.util.Locale;
import java.util.Random;
-import java.util.UUID;
+import java.util.regex.Pattern;
/**
* Logs the use of the LatinIME keyboard.
@@ -254,7 +246,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- scheduleUploadingService(mLatinIME);
+ UploaderService.cancelAndRescheduleUploadingService(mLatinIME,
+ true /* needsRescheduling */);
}
mReplayer.setKeyboardSwitcher(keyboardSwitcher);
}
@@ -268,25 +261,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
ResearchSettings.writeResearchLastDirCleanupTime(mPrefs, now);
}
- /**
- * Arrange for the UploaderService to be run on a regular basis.
- *
- * Any existing scheduled invocation of UploaderService is removed and rescheduled. This may
- * cause problems if this method is called often and frequent updates are required, but since
- * the user will likely be sleeping at some point, if the interval is less that the expected
- * sleep duration and this method is not called during that time, the service should be invoked
- * at some point.
- */
- public static void scheduleUploadingService(Context context) {
- final Intent intent = new Intent(context, UploaderService.class);
- final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
- final AlarmManager manager =
- (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- manager.cancel(pendingIntent);
- manager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
- UploaderService.RUN_INTERVAL, UploaderService.RUN_INTERVAL, pendingIntent);
- }
-
public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) {
mMainKeyboardView = mainKeyboardView;
maybeShowSplashScreen();
@@ -406,6 +380,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
requestIndicatorRedraw();
mStatistics.reset();
checkForEmptyEditor();
+ if (mFeedbackLogBuffer == null) {
+ resetFeedbackLogging();
+ }
if (!isAllowedToLog()) {
// Log.w(TAG, "not in usability mode; not logging");
return;
@@ -439,9 +416,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
};
}
- if (mFeedbackLogBuffer == null) {
- resetFeedbackLogging();
- }
}
private void resetFeedbackLogging() {
@@ -1092,7 +1066,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
new LogStatement("LatinImeOnStartInputViewInternal", false, false, "uuid",
"packageName", "inputType", "imeOptions", "fieldId", "display", "model",
"prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything",
- "isUsingDevelopmentOnlyDiagnosticsDebug");
+ "isDevTeamBuild");
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
final ResearchLogger researchLogger = getInstance();
@@ -1114,13 +1088,32 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING,
- ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG);
- } catch (NameNotFoundException e) {
- e.printStackTrace();
+ researchLogger.isDevTeamBuild());
+ // Commit the logUnit so the LatinImeOnStartInputViewInternal event is in its own
+ // logUnit at the beginning of the log.
+ researchLogger.commitCurrentLogUnit();
+ } catch (final NameNotFoundException e) {
+ Log.e(TAG, "NameNotFound", e);
}
}
}
+ // TODO: Update this heuristic pattern to something more reliable. Developer builds tend to
+ // have the developer name and year embedded.
+ private static final Pattern developerBuildRegex = Pattern.compile("[A-Za-z]\\.20[1-9]");
+ private boolean isDevTeamBuild() {
+ try {
+ final PackageInfo packageInfo;
+ packageInfo = mLatinIME.getPackageManager().getPackageInfo(mLatinIME.getPackageName(),
+ 0);
+ final String versionName = packageInfo.versionName;
+ return !(developerBuildRegex.matcher(versionName).find());
+ } catch (final NameNotFoundException e) {
+ Log.e(TAG, "Could not determine package name", e);
+ return false;
+ }
+ }
+
/**
* Log a change in preferences.
*
@@ -1290,7 +1283,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (connection != null) {
Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
if (range != null) {
- word = range.mWord;
+ word = range.mWord.toString();
}
}
final ResearchLogger researchLogger = getInstance();
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index 6a9f5c1f4..6a9717b7c 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -18,6 +18,8 @@ package com.android.inputmethod.research;
import android.app.AlarmManager;
import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
@@ -43,11 +45,17 @@ public final class UploaderService extends IntentService {
@Override
protected void onHandleIntent(final Intent intent) {
+ // We may reach this point either because the alarm fired, or because the system explicitly
+ // requested that an Upload occur. In the latter case, we want to cancel the alarm in case
+ // it's about to fire.
+ cancelAndRescheduleUploadingService(this, false /* needsRescheduling */);
+
final Uploader uploader = new Uploader(this);
if (!uploader.isPossibleToUpload()) return;
if (isUploadingUnconditionally(intent.getExtras()) || uploader.isConvenientToUpload()) {
uploader.doUpload();
}
+ cancelAndRescheduleUploadingService(this, true /* needsRescheduling */);
}
private boolean isUploadingUnconditionally(final Bundle bundle) {
@@ -57,4 +65,42 @@ public final class UploaderService extends IntentService {
}
return false;
}
+
+ /**
+ * Arrange for the UploaderService to be run on a regular basis.
+ *
+ * Any existing scheduled invocation of UploaderService is removed and optionally rescheduled.
+ * This may cause problems if this method is called so often that no scheduled invocation is
+ * ever run. But if the delay is short enough that it will go off when the user is sleeping,
+ * then there should be no starvation.
+ *
+ * @param context {@link Context} object
+ * @param needsRescheduling whether to schedule a future intent to be delivered to this service
+ */
+ public static void cancelAndRescheduleUploadingService(final Context context,
+ final boolean needsRescheduling) {
+ final PendingIntent pendingIntent = getPendingIntentForService(context);
+ final AlarmManager alarmManager = (AlarmManager) context.getSystemService(
+ Context.ALARM_SERVICE);
+ cancelAnyScheduledServiceAlarm(alarmManager, pendingIntent);
+ if (needsRescheduling) {
+ scheduleServiceAlarm(alarmManager, pendingIntent);
+ }
+ }
+
+ private static PendingIntent getPendingIntentForService(final Context context) {
+ final Intent intent = new Intent(context, UploaderService.class);
+ return PendingIntent.getService(context, 0, intent, 0);
+ }
+
+ private static void cancelAnyScheduledServiceAlarm(final AlarmManager alarmManager,
+ final PendingIntent pendingIntent) {
+ alarmManager.cancel(pendingIntent);
+ }
+
+ private static void scheduleServiceAlarm(final AlarmManager alarmManager,
+ final PendingIntent pendingIntent) {
+ alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, UploaderService.RUN_INTERVAL,
+ pendingIntent);
+ }
}