diff options
Diffstat (limited to 'java/src')
17 files changed, 358 insertions, 255 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 745e7dfed..7dfbea353 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -1460,7 +1460,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/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/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/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/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 56b1c786e..0f1f14957 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1540,7 +1540,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 +1552,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 +1584,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 +1615,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 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..11ef60dc9 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. 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/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..2c18b6e38 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java @@ -16,38 +16,41 @@ package com.android.inputmethod.latin.spellcheck; -import com.android.inputmethod.annotations.UsedForTesting; 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 { - @UsedForTesting - final public static int NUL = Constants.NOT_A_CODE; +public final class SpellCheckerProximityInfo extends ProximityInfo { + public SpellCheckerProximityInfo(final int script) { + super(getProximityForScript(script), PROXIMITY_GRID_WIDTH, PROXIMITY_GRID_HEIGHT); + } + + 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, + 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); } } - final protected static int computeIndex(final int characterCode, + + static int computeIndex(final int characterCode, final TreeMap<Integer, Integer> indices) { final Integer result = indices.get(characterCode); if (null == result) return NOT_AN_INDEX; @@ -61,7 +64,7 @@ public final class SpellCheckerProximityInfo { // 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(); + private static final TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap(); // The proximity here is the union of // - the proximity for a QWERTY keyboard. @@ -79,7 +82,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 +124,18 @@ 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 { buildProximityIndices(PROXIMITY, INDICES); } + static int getIndexOf(int characterCode) { return computeIndex(characterCode, INDICES); } } private static final class Cyrillic { - final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap(); + private static final 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 +212,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 +285,18 @@ 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 { buildProximityIndices(PROXIMITY, INDICES); } + static int getIndexOf(int characterCode) { return computeIndex(characterCode, INDICES); } } private static final class Greek { - final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap(); + private static final 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 +361,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 +426,39 @@ 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 { buildProximityIndices(PROXIMITY, INDICES); } + static int getIndexOf(int characterCode) { return computeIndex(characterCode, 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.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); } } 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..5aaced036 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(); @@ -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,29 @@ 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()); + } 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. * 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); + } } |