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/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.java2
-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.java16
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java268
-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/ContactsBinaryDictionary.java3
-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/LatinIME.java137
-rw-r--r--java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java28
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java23
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java4
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsActivity.java5
-rw-r--r--java/src/com/android/inputmethod/latin/StringUtils.java86
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeLocale.java4
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java2
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java1
-rw-r--r--java/src/com/android/inputmethod/latin/UserBinaryDictionary.java34
-rw-r--r--java/src/com/android/inputmethod/latin/WordComposer.java16
-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.java2
-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.java2
-rw-r--r--java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java2
-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
37 files changed, 896 insertions, 466 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/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..a356eb119 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -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/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..f682b518f 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);
@@ -90,7 +91,13 @@ final class GesturePreviewTrail {
}
public void addStroke(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
- final int trailSize = mEventTimes.getLength();
+ 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) {
return;
@@ -169,6 +176,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();
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/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/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 8b5a76a17..22d189987 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);
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/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 56b1c786e..094ccd77f 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;
@@ -197,6 +200,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 +238,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 +248,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);
}
@@ -910,13 +921,12 @@ 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();
+
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 +993,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
}
if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return;
- mApplicationSpecifiedCompletions = applicationSpecifiedCompletions;
+ mApplicationSpecifiedCompletions =
+ CompletionInfoUtils.removeNulls(applicationSpecifiedCompletions);
if (applicationSpecifiedCompletions == null) {
clearSuggestionStrip();
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -1243,10 +1254,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
} else {
wordToEdit = word;
}
- mPositionalInfoForUserDictPendingAddition =
- new PositionalInfoForUserDictPendingAddition(
- wordToEdit, mLastSelectionEnd, getCurrentInputEditorInfo(),
- mLastComposedWord.mCapitalizedMode);
mUserDictionary.addWordToUserDictionary(wordToEdit);
}
@@ -1540,7 +1547,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 +1559,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 +1591,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 +1622,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 +1734,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// during key repeat.
mHandler.postUpdateShiftState();
+ if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) {
+ resetEntireInputState(mLastSelectionStart);
+ }
if (mWordComposer.isComposingWord()) {
final int length = mWordComposer.size();
if (length > 0) {
@@ -1848,6 +1868,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
promotePhantomSpace();
}
+ if (mWordComposer.isComposingWord() && !mWordComposer.isCursorAtEndOfComposingWord()) {
+ 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.
@@ -2321,6 +2345,48 @@ 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 */);
+ final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
+ if (range.mWord instanceof SpannableString) {
+ final SpannableString spannableString = (SpannableString)range.mWord;
+ final String typedWord = spannableString.toString();
+ 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(range.mWord, mKeyboardSwitcher.getKeyboard());
+ mWordComposer.setCursorPositionWithinWord(range.mCharsBefore);
+ mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore,
+ mLastSelectionEnd + range.mCharsAfter);
+ if (suggestions.isEmpty()) {
+ suggestions.add(new SuggestedWordInfo(range.mWord.toString(), 1,
+ SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_RESUMED));
+ }
+ showSuggestionStrip(new SuggestedWords(suggestions,
+ true /* typedWordValid */, false /* willAutoCorrect */,
+ false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */,
+ false /* isPrediction */), range.mWord.toString());
+ }
+
+ /**
* Check if the cursor is actually at the end of a word. If so, restart suggestions on this
* word, else do nothing.
*/
@@ -2328,17 +2394,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);
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/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 16744d1f0..b74ea593d 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,7 +17,9 @@
package com.android.inputmethod.latin;
import android.inputmethodservice.InputMethodService;
+import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
@@ -392,7 +394,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 +446,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 +502,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 +521,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 +566,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);
}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 8fbe843cf..318d2b23f 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -134,6 +134,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
return mSettingsValues.mIsInternal;
}
+ public String getWordSeparators() {
+ return mSettingsValues.mWordSeparators;
+ }
+
// 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/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java
index 59ad28fc9..3ca209d34 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,75 @@ 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();
+ }
+
+ public static boolean containsAny(final String string, final String separators) {
+ final int len = separators.length();
+ for (int i = 0; i < len; i = separators.offsetByCodePoints(i, 1)) {
+ final int separator = separators.codePointAt(i);
+ if (-1 != string.indexOf(separator)) {
+ return true;
+ }
+ }
+ 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..6464bd0d7 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -394,7 +394,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..158cc1155 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
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..1af12428d 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -49,6 +49,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 +63,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
+ mCursorPositionWithinWord = 0;
refreshSize();
}
@@ -76,6 +78,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = source.mTrailingSingleQuotesCount;
mIsResumed = source.mIsResumed;
mIsBatchMode = source.mIsBatchMode;
+ mCursorPositionWithinWord = source.mCursorPositionWithinWord;
refreshSize();
}
@@ -91,6 +94,7 @@ public final class WordComposer {
mTrailingSingleQuotesCount = 0;
mIsResumed = false;
mIsBatchMode = false;
+ mCursorPositionWithinWord = 0;
refreshSize();
}
@@ -135,6 +139,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 +163,14 @@ public final class WordComposer {
mAutoCorrection = null;
}
+ public void setCursorPositionWithinWord(final int posWithinWord) {
+ mCursorPositionWithinWord = posWithinWord;
+ }
+
+ public boolean isCursorAtEndOfComposingWord() {
+ return mCursorPositionWithinWord == mCodePointSize;
+ }
+
public void setBatchInputPointers(final InputPointers batchPointers) {
mInputPointers.set(batchPointers);
mIsBatchMode = true;
@@ -242,6 +255,7 @@ public final class WordComposer {
++mTrailingSingleQuotesCount;
}
}
+ mCursorPositionWithinWord = mCodePointSize;
mAutoCorrection = null;
}
@@ -368,6 +382,7 @@ public final class WordComposer {
mCapitalizedMode = CAPS_MODE_OFF;
refreshSize();
mAutoCorrection = null;
+ mCursorPositionWithinWord = 0;
mIsResumed = false;
return lastComposedWord;
}
@@ -380,6 +395,7 @@ public final class WordComposer {
refreshSize();
mCapitalizedMode = lastComposedWord.mCapitalizedMode;
mAutoCorrection = null; // This will be filled by the next call to updateSuggestion.
+ mCursorPositionWithinWord = mCodePointSize;
mIsResumed = true;
}
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..96b2c818d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -226,7 +226,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..94715cd84 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -43,7 +43,7 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView {
}
public void updateKeyboardGeometry(final int keyHeight) {
- mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes);
+ updateKeyDrawParams(keyHeight);
}
@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..4ef36fa46 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(
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);
+ }
}