diff options
Diffstat (limited to 'java/src')
41 files changed, 1297 insertions, 1402 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java index fb75d6dc0..618322357 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java @@ -66,6 +66,8 @@ public final class DictionarySettingsFragment extends PreferenceFragment private boolean mChangedSettings; private DictionaryListInterfaceState mDictionaryListInterfaceState = new DictionaryListInterfaceState(); + private TreeMap<String, WordListPreference> mCurrentPreferenceMap = + new TreeMap<String, WordListPreference>(); // never null private final BroadcastReceiver mConnectivityChangedReceiver = new BroadcastReceiver() { @Override @@ -278,7 +280,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment return result; } else { final String systemLocaleString = Locale.getDefault().toString(); - final TreeMap<String, WordListPreference> prefList = + final TreeMap<String, WordListPreference> prefMap = new TreeMap<String, WordListPreference>(); final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN); final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN); @@ -299,16 +301,31 @@ public final class DictionarySettingsFragment extends PreferenceFragment // The key is sorted in lexicographic order, according to the match level, then // the description. final String key = matchLevelString + "." + description + "." + wordlistId; - final WordListPreference existingPref = prefList.get(key); + final WordListPreference existingPref = prefMap.get(key); if (null == existingPref || hasPriority(status, existingPref.mStatus)) { - final WordListPreference pref = new WordListPreference(activity, - mDictionaryListInterfaceState, mClientId, wordlistId, version, locale, - description, status, filesize); - prefList.put(key, pref); + final WordListPreference oldPreference = mCurrentPreferenceMap.get(key); + final WordListPreference pref; + if (null != oldPreference + && oldPreference.mVersion == version + && oldPreference.mLocale.equals(locale)) { + // If the old preference has all the new attributes, reuse it. We test + // for version and locale because although attributes other than status + // need to be the same, others have been tested through the key of the + // map. Also, status may differ so we don't want to use #equals() here. + pref = oldPreference; + pref.mStatus = status; + } else { + // Otherwise, discard it and create a new one instead. + pref = new WordListPreference(activity, mDictionaryListInterfaceState, + mClientId, wordlistId, version, locale, description, status, + filesize); + } + prefMap.put(key, pref); } } while (cursor.moveToNext()); cursor.close(); - return prefList.values(); + mCurrentPreferenceMap = prefMap; + return prefMap.values(); } } diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java index 29015d61b..451a0fb82 100644 --- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java +++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java @@ -58,6 +58,8 @@ public final class WordListPreference extends Preference { // The metadata word list id and version of this word list. public final String mWordlistId; public final int mVersion; + public final Locale mLocale; + public final String mDescription; // The status public int mStatus; // The size of the dictionary file @@ -80,6 +82,8 @@ public final class WordListPreference extends Preference { mVersion = version; mWordlistId = wordlistId; mFilesize = filesize; + mLocale = locale; + mDescription = description; setLayoutResource(R.layout.dictionary_line); diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index d160038ad..1550e77e3 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -105,7 +105,7 @@ public class Key implements Comparable<Key> { /** Hit bounding box of the key */ public final Rect mHitBox = new Rect(); - /** More keys */ + /** More keys. It is guaranteed that this is null or an array of one or more elements */ public final MoreKeySpec[] mMoreKeys; /** More keys column number and flags */ private final int mMoreKeysColumnAndFlags; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java index 60d09d6fd..9eeee5baf 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -27,8 +27,9 @@ public interface KeyboardActionListener { * * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key, * the value will be zero. + * @param isSinglePointer true if pressing has occurred while no other key is being pressed. */ - public void onPressKey(int primaryCode); + public void onPressKey(int primaryCode, boolean isSinglePointer); /** * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called. @@ -88,6 +89,11 @@ public interface KeyboardActionListener { public void onCancelInput(); /** + * Called when user finished sliding key input. + */ + public void onFinishSlidingInput(); + + /** * Send a non-"code input" custom request to the listener. * @return true if the request has been consumed, false otherwise. */ @@ -97,7 +103,7 @@ public interface KeyboardActionListener { public static final Adapter EMPTY_LISTENER = new Adapter(); @Override - public void onPressKey(int primaryCode) {} + public void onPressKey(int primaryCode, boolean isSinglePointer) {} @Override public void onReleaseKey(int primaryCode, boolean withSliding) {} @Override @@ -115,6 +121,8 @@ public interface KeyboardActionListener { @Override public void onCancelInput() {} @Override + public void onFinishSlidingInput() {} + @Override public boolean onCustomRequest(int requestCode) { return false; } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index bc9e8cdd4..1fe23a330 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -29,17 +29,18 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.InputType; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; import android.util.Xml; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; -import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.keyboard.internal.KeysCache; +import com.android.inputmethod.latin.AdditionalSubtype; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.InputTypeUtils; @@ -72,6 +73,8 @@ public final class KeyboardLayoutSet { private static final String TAG_ELEMENT = "Element"; private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_"; + private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480; + private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 800; private final Context mContext; private final Params mParams; @@ -282,8 +285,7 @@ public final class KeyboardLayoutSet { return this; } - @UsedForTesting - public void disableTouchPositionCorrectionDataForTest() { + public void disableTouchPositionCorrectionData() { mParams.mDisableTouchPositionCorrectionDataForTest = true; } @@ -413,4 +415,47 @@ public final class KeyboardLayoutSet { } } } + + public static KeyboardLayoutSet createKeyboardSetForSpellChecker(final Context context, + final String locale, final String layout) { + final InputMethodSubtype subtype = + AdditionalSubtype.createAdditionalSubtype(locale, layout, null); + return createKeyboardSet(context, subtype, SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, + SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false); + } + + public static KeyboardLayoutSet createKeyboardSetForTest(final Context context, + final InputMethodSubtype subtype, final int orientation, + final boolean testCasesHaveTouchCoordinates) { + final DisplayMetrics dm = context.getResources().getDisplayMetrics(); + final int width; + final int height; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + width = Math.max(dm.widthPixels, dm.heightPixels); + height = Math.min(dm.widthPixels, dm.heightPixels); + } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { + width = Math.min(dm.widthPixels, dm.heightPixels); + height = Math.max(dm.widthPixels, dm.heightPixels); + } else { + throw new RuntimeException("Orientation should be ORIENTATION_LANDSCAPE or " + + "ORIENTATION_PORTRAIT: orientation=" + orientation); + } + return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates); + } + + private static KeyboardLayoutSet createKeyboardSet(final Context context, + final InputMethodSubtype subtype, final int width, final int height, + final boolean testCasesHaveTouchCoordinates) { + final EditorInfo editorInfo = new EditorInfo(); + editorInfo.inputType = InputType.TYPE_CLASS_TEXT; + final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( + context, editorInfo); + builder.setScreenGeometry(width, height); + builder.setSubtype(subtype); + if (!testCasesHaveTouchCoordinates) { + // For spell checker and tests + builder.disableTouchPositionCorrectionData(); + } + return builder.build(); + } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 39afe9072..ad08d6477 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -216,19 +216,19 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mState.onResetKeyboardStateToAlphabet(); } - public void onPressKey(final int code) { + public void onPressKey(final int code, final boolean isSinglePointer) { if (isVibrateAndSoundFeedbackRequired()) { mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView); } - mState.onPressKey(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState()); + mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState()); } public void onReleaseKey(final int code, final boolean withSliding) { mState.onReleaseKey(code, withSliding); } - public void onCancelInput() { - mState.onCancelInput(isSinglePointer()); + public void onFinishSlidingInput() { + mState.onFinishSlidingInput(); } // Implements {@link KeyboardState.SwitchActions}. @@ -346,15 +346,11 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput(); } - private boolean isSinglePointer() { - return mKeyboardView != null && mKeyboardView.getPointerCount() == 1; - } - /** * Updates state machine to figure out when to automatically switch back to the previous mode. */ public void onCodeInput(final int code) { - mState.onCodeInput(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState()); + mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState()); } public MainKeyboardView getMainKeyboardView() { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index e4e75c342..7941fcba2 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -497,7 +497,7 @@ public class KeyboardView extends View { } } - if (key.hasPopupHint() && key.mMoreKeys != null && key.mMoreKeys.length > 0) { + if (key.hasPopupHint() && key.mMoreKeys != null) { drawKeyPopupHint(key, canvas, paint, params); } } diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index a0ac47535..6c6fc6157 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -910,10 +910,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack mSlidingKeyInputPreview.dismissSlidingKeyInputPreview(); } - public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail, + public void setGesturePreviewMode(final boolean drawsGestureTrail, final boolean drawsGestureFloatingPreviewText) { mGestureFloatingPreviewText.setPreviewEnabled(drawsGestureFloatingPreviewText); - mGestureTrailsPreview.setPreviewEnabled(drawsGesturePreviewTrail); + mGestureTrailsPreview.setPreviewEnabled(drawsGestureTrail); } public void showGestureFloatingPreviewText(final SuggestedWords suggestedWords) { @@ -927,7 +927,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } @Override - public void showGesturePreviewTrail(final PointerTracker tracker) { + public void showGestureTrail(final PointerTracker tracker) { locatePreviewPlacerView(); mGestureFloatingPreviewText.setPreviewPosition(tracker); mGestureTrailsPreview.setPreviewPosition(tracker); @@ -1100,10 +1100,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack return false; } - public int getPointerCount() { - return mOldPointerCount; - } - @Override public boolean dispatchTouchEvent(MotionEvent event) { if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 0556fddd3..174239325 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -25,6 +25,7 @@ import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.keyboard.internal.GestureStroke; import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokeParams; import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; +import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; @@ -83,7 +84,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public void dismissKeyPreview(PointerTracker tracker); public void showSlidingKeyInputPreview(PointerTracker tracker); public void dismissSlidingKeyInputPreview(); - public void showGesturePreviewTrail(PointerTracker tracker); + public void showGestureTrail(PointerTracker tracker); } public interface TimerProxy { @@ -161,6 +162,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // Parameters for pointer handling. private static PointerTrackerParams sParams; private static GestureStrokeParams sGestureStrokeParams; + private static GestureStrokePreviewParams sGesturePreviewParams; private static boolean sNeedsPhantomSuddenMoveEventHack; // Move this threshold to resource. // TODO: Device specific parameter would be better for device specific hack? @@ -339,12 +341,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element { sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; sParams = PointerTrackerParams.DEFAULT; sGestureStrokeParams = GestureStrokeParams.DEFAULT; + sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT; sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); } public static void setParameters(final TypedArray mainKeyboardViewAttr) { sParams = new PointerTrackerParams(mainKeyboardViewAttr); sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr); + sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr); sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); } @@ -428,7 +432,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } mPointerId = id; mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints( - id, sGestureStrokeParams); + id, sGestureStrokeParams, sGesturePreviewParams); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); @@ -455,7 +459,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { return false; } if (key.isEnabled()) { - mListener.onPressKey(key.mCode); + mListener.onPressKey(key.mCode, getActivePointerTrackerCount() == 1); final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; mKeyboardLayoutHasBeenChanged = false; mTimerProxy.startTypingStateTimer(key); @@ -523,6 +527,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } } + private void callListenerOnFinishSlidingInput() { + if (DEBUG_LISTENER) { + Log.d(TAG, String.format("[%d] onFinishSlidingInput", mPointerId)); + } + mListener.onFinishSlidingInput(); + } + private void callListenerOnCancelInput() { if (DEBUG_LISTENER) { Log.d(TAG, String.format("[%d] onCancelInput", mPointerId)); @@ -733,7 +744,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { dismissAllMoreKeysPanels(); } mTimerProxy.cancelLongPressTimer(); - mDrawingProxy.showGesturePreviewTrail(this); + mDrawingProxy.showGestureTrail(this); } public void updateBatchInputByTimer(final long eventTime) { @@ -749,7 +760,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (mIsTrackingForActionDisabled) { return; } - mDrawingProxy.showGesturePreviewTrail(this); + mDrawingProxy.showGestureTrail(this); } private void updateBatchInput(final long eventTime) { @@ -790,7 +801,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (mIsTrackingForActionDisabled) { return; } - mDrawingProxy.showGesturePreviewTrail(this); + mDrawingProxy.showGestureTrail(this); } private void cancelBatchInput() { @@ -1032,7 +1043,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private void processSildeOutFromOldKey(final Key oldKey) { setReleasedKeyGraphics(oldKey); - callListenerOnRelease(oldKey, oldKey.mCode, true); + callListenerOnRelease(oldKey, oldKey.mCode, true /* withSliding */); startSlidingKeyInput(oldKey); mTimerProxy.cancelKeyTimers(); } @@ -1164,6 +1175,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private void onUpEventInternal(final int x, final int y, final long eventTime) { mTimerProxy.cancelKeyTimers(); + final boolean isInSlidingKeyInput = mIsInSlidingKeyInput; + final boolean isInSlidingKeyInputFromModifier = mIsInSlidingKeyInputFromModifier; resetSlidingKeyInput(); mIsDetectingGesture = false; final Key currentKey = mCurrentKey; @@ -1184,7 +1197,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (sInGesture) { if (currentKey != null) { - callListenerOnRelease(currentKey, currentKey.mCode, true); + callListenerOnRelease(currentKey, currentKey.mCode, true /* withSliding */); } mayEndBatchInput(eventTime); return; @@ -1193,8 +1206,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (mIsTrackingForActionDisabled) { return; } - if (currentKey != null && !currentKey.isRepeatable()) { - detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime); + if (currentKey != null && currentKey.isRepeatable() && !isInSlidingKeyInput) { + // Repeatable key has been registered in {@link #onDownEventInternal(int,int,long)}. + return; + } + detectAndSendKey(currentKey, mKeyX, mKeyY, eventTime); + if (isInSlidingKeyInputFromModifier) { + callListenerOnFinishSlidingInput(); } } @@ -1243,10 +1261,13 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } private void startRepeatKey(final Key key) { - if (key != null && key.isRepeatable() && !sInGesture) { - onRegisterKey(key); - mTimerProxy.startKeyRepeatTimer(this); - } + if (sInGesture) return; + if (key == null) return; + if (!key.isRepeatable()) return; + // Don't start key repeat when we are in sliding input mode. + if (mIsInSlidingKeyInput) return; + onRegisterKey(key); + mTimerProxy.startKeyRepeatTimer(this); } public void onRegisterKey(final Key key) { @@ -1299,9 +1320,15 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } private void startLongPressTimer(final Key key) { - if (key != null && key.isLongPressEnabled() && !sInGesture) { - mTimerProxy.startLongPressTimer(this); - } + if (sInGesture) return; + if (key == null) return; + if (!key.isLongPressEnabled()) return; + // Caveat: Please note that isLongPressEnabled() can be true even if the current key + // doesn't have its more keys. (e.g. spacebar, globe key) + // We always need to start the long press timer if the key has its more keys regardless of + // whether or not we are in the sliding input mode. + if (mIsInSlidingKeyInput && key.mMoreKeys == null) return; + mTimerProxy.startLongPressTimer(this); } private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) { @@ -1312,7 +1339,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final int code = key.mCode; callListenerOnCodeInput(key, code, x, y, eventTime); - callListenerOnRelease(key, code, false); + callListenerOnRelease(key, code, false /* withSliding */); } private void printTouchEvent(final String title, final int x, final int y, diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index b77e378bf..57d3fede4 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -79,23 +79,6 @@ public class ProximityInfo { mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection); } - /** - * 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; static { JniUtils.loadNativeLibrary(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 93ff26466..70363e602 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -145,7 +145,7 @@ public class GestureStroke { public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { mKeyWidth = keyWidth; mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); - mMaxYCoordinate = keyboardHeight - 1; + mMaxYCoordinate = keyboardHeight; // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? mDetectFastMoveSpeedThreshold = (int)(keyWidth * mParams.mDetectFastMoveSpeedThreshold); mGestureDynamicDistanceThresholdFrom = diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index 7a51e2568..b31f00b62 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -16,6 +16,9 @@ package com.android.inputmethod.keyboard.internal; +import android.content.res.TypedArray; + +import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResizableIntArray; public final class GestureStrokeWithPreviewPoints extends GestureStroke { @@ -25,6 +28,8 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); + private final GestureStrokePreviewParams mPreviewParams; + private int mStrokeId; private int mLastPreviewSize; private final HermiteInterpolator mInterpolator = new HermiteInterpolator(); @@ -32,23 +37,53 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { private int mLastX; private int mLastY; - private double mMinPreviewSamplingDistance; private double mDistanceFromLastSample; - private double mInterpolationDistanceThreshold; - - // TODO: Move these constants to resource. - // TODO: Use "dp" instead of ratio to the keyWidth because table has rather large keys. - // The minimum trail distance between sample points for preview in keyWidth unit when using - // interpolation. - private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.2f; - // The angular threshold to use interpolation in radian. PI/12 is 15 degree. - private static final double INTERPOLATION_ANGULAR_THRESHOLD = Math.PI / 12.0d; - // The distance threshold to use interpolation in keyWidth unit. - private static final float INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH = 0.5f; - private static final int MAX_INTERPOLATION_PARTITIONS = 6; - - public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) { - super(pointerId, params); + + public static final class GestureStrokePreviewParams { + public final double mMinSamplingDistance; // in pixel + public final double mMaxInterpolationAngularThreshold; // in radian + public final double mMaxInterpolationDistanceThreshold; // in pixel + public final int mMaxInterpolationSegments; + + public static final GestureStrokePreviewParams DEFAULT = new GestureStrokePreviewParams(); + + private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree + + private GestureStrokePreviewParams() { + mMinSamplingDistance = 0.0d; + mMaxInterpolationAngularThreshold = + degreeToRadian(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD); + mMaxInterpolationDistanceThreshold = mMinSamplingDistance; + mMaxInterpolationSegments = 4; + } + + private static double degreeToRadian(final int degree) { + return (double)degree / 180.0d * Math.PI; + } + + public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) { + mMinSamplingDistance = mainKeyboardViewAttr.getDimension( + R.styleable.MainKeyboardView_gestureTrailMinSamplingDistance, + (float)DEFAULT.mMinSamplingDistance); + final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable + .MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold, 0); + mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0) + ? DEFAULT.mMaxInterpolationAngularThreshold + : degreeToRadian(interpolationAngularDegree); + mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable + .MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold, + (float)DEFAULT.mMaxInterpolationDistanceThreshold); + mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger( + R.styleable.MainKeyboardView_gestureTrailMaxInterpolationSegments, + DEFAULT.mMaxInterpolationSegments); + } + } + + public GestureStrokeWithPreviewPoints(final int pointerId, + final GestureStrokeParams strokeParams, + final GestureStrokePreviewParams previewParams) { + super(pointerId, strokeParams); + mPreviewParams = previewParams; } @Override @@ -66,19 +101,12 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { return mStrokeId; } - @Override - public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { - super.setKeyboardGeometry(keyWidth, keyboardHeight); - mMinPreviewSamplingDistance = keyWidth * MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH; - mInterpolationDistanceThreshold = keyWidth * INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH; - } - private boolean needsSampling(final int x, final int y) { mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY); mLastX = x; mLastY = y; final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0); - if (mDistanceFromLastSample >= mMinPreviewSamplingDistance || isDownEvent) { + if (mDistanceFromLastSample >= mPreviewParams.mMinSamplingDistance || isDownEvent) { mDistanceFromLastSample = 0.0d; return true; } @@ -117,14 +145,14 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { * * @param lastInterpolatedIndex the start index of the last interpolated segment of * <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>. - * @param eventTimes the event time array of gesture preview trail to be drawn. - * @param xCoords the x-coordinates array of gesture preview trail to be drawn. - * @param yCoords the y-coordinates array of gesture preview trail to be drawn. + * @param eventTimes the event time array of gesture trail to be drawn. + * @param xCoords the x-coordinates array of gesture trail to be drawn. + * @param yCoords the y-coordinates array of gesture trail to be drawn. * @return the start index of the last interpolated segment of input arrays. */ public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, final ResizableIntArray eventTimes, final ResizableIntArray xCoords, - final ResizableIntArray yCoords) { + final ResizableIntArray yCoords, final ResizableIntArray types) { final int size = mPreviewEventTimes.getLength(); final int[] pt = mPreviewEventTimes.getPrimitiveArray(); final int[] px = mPreviewXCoordinates.getPrimitiveArray(); @@ -144,28 +172,34 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); final double deltaAngle = Math.abs(angularDiff(m2, m1)); - final int partitionsByAngle = (int)Math.ceil( - deltaAngle / INTERPOLATION_ANGULAR_THRESHOLD); + final int segmentsByAngle = (int)Math.ceil( + deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold); final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X, mInterpolator.mP1Y - mInterpolator.mP2Y); - final int partitionsByDistance = (int)Math.ceil(deltaDistance - / mInterpolationDistanceThreshold); - final int partitions = Math.min(MAX_INTERPOLATION_PARTITIONS, - Math.max(partitionsByAngle, partitionsByDistance)); + final int segmentsByDistance = (int)Math.ceil(deltaDistance + / mPreviewParams.mMaxInterpolationDistanceThreshold); + final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments, + Math.max(segmentsByAngle, segmentsByDistance)); final int t1 = eventTimes.get(d1); final int dt = pt[p2] - pt[p1]; d1++; - for (int i = 1; i < partitions; i++) { - final float t = i / (float)partitions; + for (int i = 1; i < segments; i++) { + final float t = i / (float)segments; mInterpolator.interpolate(t); eventTimes.add(d1, (int)(dt * t) + t1); xCoords.add(d1, (int)mInterpolator.mInterpolatedX); yCoords.add(d1, (int)mInterpolator.mInterpolatedY); + if (GestureTrail.DBG_SHOW_POINTS) { + types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED); + } d1++; } eventTimes.add(d1, pt[p2]); xCoords.add(d1, px[p2]); yCoords.add(d1, py[p2]); + if (GestureTrail.DBG_SHOW_POINTS) { + types.add(d1, GestureTrail.POINT_TYPE_SAMPLED); + } } return lastInterpolatedDrawIndex; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java index 7fd1bedcb..03dd1c372 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java @@ -18,6 +18,7 @@ package com.android.inputmethod.keyboard.internal; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; @@ -28,19 +29,26 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResizableIntArray; /* - * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailFadeoutStartDelay - * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailFadeoutDuration - * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailUpdateInterval - * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailColor - * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailWidth + * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay + * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutDuration + * @attr ref R.styleable#MainKeyboardView_gestureTrailUpdateInterval + * @attr ref R.styleable#MainKeyboardView_gestureTrailColor + * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth */ -final class GesturePreviewTrail { +final class GestureTrail { + public static final boolean DBG_SHOW_POINTS = false; + public static final int POINT_TYPE_SAMPLED = 0; + public static final int POINT_TYPE_INTERPOLATED = 1; + public static final int POINT_TYPE_COMPROMISED = 2; + 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); + private final ResizableIntArray mPointTypes = new ResizableIntArray( + DBG_SHOW_POINTS ? DEFAULT_CAPACITY : 0); private int mCurrentStrokeId = -1; // The wall time of the zero value in {@link #mEventTimes} private long mCurrentTimeBase; @@ -62,26 +70,26 @@ final class GesturePreviewTrail { public Params(final TypedArray mainKeyboardViewAttr) { mTrailColor = mainKeyboardViewAttr.getColor( - R.styleable.MainKeyboardView_gesturePreviewTrailColor, 0); + R.styleable.MainKeyboardView_gestureTrailColor, 0); mTrailStartWidth = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gesturePreviewTrailStartWidth, 0.0f); + R.styleable.MainKeyboardView_gestureTrailStartWidth, 0.0f); mTrailEndWidth = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gesturePreviewTrailEndWidth, 0.0f); + R.styleable.MainKeyboardView_gestureTrailEndWidth, 0.0f); final int PERCENTAGE_INT = 100; mTrailBodyRatio = (float)mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gesturePreviewTrailBodyRatio, PERCENTAGE_INT) + R.styleable.MainKeyboardView_gestureTrailBodyRatio, PERCENTAGE_INT) / (float)PERCENTAGE_INT; final int trailShadowRatioInt = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gesturePreviewTrailShadowRatio, 0); + R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0); mTrailShadowEnabled = (trailShadowRatioInt > 0); mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT; - mFadeoutStartDelay = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutStartDelay, 0); - mFadeoutDuration = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutDuration, 0); + mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0); + mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0); mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration; mUpdateInterval = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gesturePreviewTrailUpdateInterval, 0); + R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0); } } @@ -125,7 +133,7 @@ final class GesturePreviewTrail { final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId) ? mLastInterpolatedDrawIndex : trailSize; mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment( - lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates); + lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); if (strokeId != mCurrentStrokeId) { final int elapsedTime = (int)(downTime - mCurrentTimeBase); for (int i = mTrailStartIndex; i < trailSize; i++) { @@ -178,12 +186,12 @@ final class GesturePreviewTrail { private final Rect mRoundedLineBounds = new Rect(); /** - * Draw gesture preview trail - * @param canvas The canvas to draw the gesture preview trail - * @param paint The paint object to be used to draw the gesture preview trail + * Draw gesture trail + * @param canvas The canvas to draw the gesture trail + * @param paint The paint object to be used to draw the gesture trail * @param outBoundsRect the bounding box of this gesture trail drawing - * @param params The drawing parameters of gesture preview trail - * @return true if some gesture preview trails remain to be drawn + * @param params The drawing parameters of gesture trail + * @return true if some gesture trails remain to be drawn */ public boolean drawGestureTrail(final Canvas canvas, final Paint paint, final Rect outBoundsRect, final Params params) { @@ -204,6 +212,7 @@ final class GesturePreviewTrail { final int[] eventTimes = mEventTimes.getPrimitiveArray(); final int[] xCoords = mXCoordinates.getPrimitiveArray(); final int[] yCoords = mYCoordinates.getPrimitiveArray(); + final int[] pointTypes = mPointTypes.getPrimitiveArray(); final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase); int startIndex; for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) { @@ -246,6 +255,17 @@ final class GesturePreviewTrail { final int alpha = getAlpha(elapsedTime, params); paint.setAlpha(alpha); canvas.drawPath(path, paint); + if (DBG_SHOW_POINTS) { + if (pointTypes[i] == POINT_TYPE_INTERPOLATED) { + paint.setColor(Color.RED); + } else if (pointTypes[i] == POINT_TYPE_SAMPLED) { + paint.setColor(0xFFA000FF); + } else { + paint.setColor(Color.GREEN); + } + canvas.drawCircle(p1x - 1, p1y - 1, 2, paint); + paint.setColor(params.mTrailColor); + } } } p1x = p2x; @@ -265,6 +285,9 @@ final class GesturePreviewTrail { mEventTimes.setLength(newSize); mXCoordinates.setLength(newSize); mYCoordinates.setLength(newSize); + if (DBG_SHOW_POINTS) { + mPointTypes.setLength(newSize); + } // The start index of the last segment of the stroke // {@link mLastInterpolatedDrawIndex} should also be updated because all array // elements have just been shifted for compaction or been zeroed. diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java index 85558f1f6..1e4c43ee5 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java @@ -29,7 +29,7 @@ import android.util.SparseArray; import android.view.View; import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.keyboard.internal.GesturePreviewTrail.Params; +import com.android.inputmethod.keyboard.internal.GestureTrail.Params; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; @@ -37,9 +37,8 @@ import com.android.inputmethod.latin.StaticInnerHandlerWrapper; * Draw gesture trail preview graphics during gesture. */ public final class GestureTrailsPreview extends AbstractDrawingPreview { - private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails = - CollectionUtils.newSparseArray(); - private final Params mGesturePreviewTrailParams; + private final SparseArray<GestureTrail> mGestureTrails = CollectionUtils.newSparseArray(); + private final Params mGestureTrailParams; private final Paint mGesturePaint; private int mOffscreenWidth; private int mOffscreenHeight; @@ -48,20 +47,20 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview { private final Canvas mOffscreenCanvas = new Canvas(); private final Rect mOffscreenSrcRect = new Rect(); private final Rect mDirtyRect = new Rect(); - private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail + private final Rect mGestureTrailBoundsRect = new Rect(); // per trail private final DrawingHandler mDrawingHandler; private static final class DrawingHandler extends StaticInnerHandlerWrapper<GestureTrailsPreview> { - private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 0; + private static final int MSG_UPDATE_GESTURE_TRAIL = 0; - private final Params mGesturePreviewTrailParams; + private final Params mGestureTrailParams; public DrawingHandler(final GestureTrailsPreview outerInstance, - final Params gesturePreviewTrailParams) { + final Params gestureTrailParams) { super(outerInstance); - mGesturePreviewTrailParams = gesturePreviewTrailParams; + mGestureTrailParams = gestureTrailParams; } @Override @@ -69,23 +68,23 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview { final GestureTrailsPreview preview = getOuterInstance(); if (preview == null) return; switch (msg.what) { - case MSG_UPDATE_GESTURE_PREVIEW_TRAIL: + case MSG_UPDATE_GESTURE_TRAIL: preview.getDrawingView().invalidate(); break; } } public void postUpdateGestureTrailPreview() { - removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL); - sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL), - mGesturePreviewTrailParams.mUpdateInterval); + removeMessages(MSG_UPDATE_GESTURE_TRAIL); + sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_TRAIL), + mGestureTrailParams.mUpdateInterval); } } public GestureTrailsPreview(final View drawingView, final TypedArray mainKeyboardViewAttr) { super(drawingView); - mGesturePreviewTrailParams = new Params(mainKeyboardViewAttr); - mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams); + mGestureTrailParams = new Params(mainKeyboardViewAttr); + mDrawingHandler = new DrawingHandler(this, mGestureTrailParams); final Paint gesturePaint = new Paint(); gesturePaint.setAntiAlias(true); gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); @@ -133,21 +132,20 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview { offscreenCanvas.drawRect(dirtyRect, paint); } dirtyRect.setEmpty(); - boolean needsUpdatingGesturePreviewTrail = false; + boolean needsUpdatingGestureTrail = false; // Draw gesture trails to offscreen buffer. - synchronized (mGesturePreviewTrails) { + synchronized (mGestureTrails) { // Trails count == fingers count that have ever been active. - final int trailsCount = mGesturePreviewTrails.size(); + final int trailsCount = mGestureTrails.size(); for (int index = 0; index < trailsCount; index++) { - final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index); - needsUpdatingGesturePreviewTrail |= - trail.drawGestureTrail(offscreenCanvas, paint, - mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams); - // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail. - dirtyRect.union(mGesturePreviewTrailBoundsRect); + final GestureTrail trail = mGestureTrails.valueAt(index); + needsUpdatingGestureTrail |= trail.drawGestureTrail(offscreenCanvas, paint, + mGestureTrailBoundsRect, mGestureTrailParams); + // {@link #mGestureTrailBoundsRect} has bounding box of the trail. + dirtyRect.union(mGestureTrailBoundsRect); } } - return needsUpdatingGesturePreviewTrail; + return needsUpdatingGestureTrail; } /** @@ -161,9 +159,9 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview { } mayAllocateOffscreenBuffer(); // Draw gesture trails to offscreen buffer. - final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails( + final boolean needsUpdatingGestureTrail = drawGestureTrails( mOffscreenCanvas, mGesturePaint, mDirtyRect); - if (needsUpdatingGesturePreviewTrail) { + if (needsUpdatingGestureTrail) { mDrawingHandler.postUpdateGestureTrailPreview(); } // Transfer offscreen buffer to screen. @@ -185,12 +183,12 @@ public final class GestureTrailsPreview extends AbstractDrawingPreview { if (!isPreviewEnabled()) { return; } - GesturePreviewTrail trail; - synchronized (mGesturePreviewTrails) { - trail = mGesturePreviewTrails.get(tracker.mPointerId); + GestureTrail trail; + synchronized (mGestureTrails) { + trail = mGestureTrails.get(tracker.mPointerId); if (trail == null) { - trail = new GesturePreviewTrail(); - mGesturePreviewTrails.put(tracker.mPointerId, trail); + trail = new GestureTrail(); + mGestureTrails.put(tracker.mPointerId, trail); } } trail.addStroke(tracker.getGestureStrokeWithPreviewPoints(), tracker.getDownTime()); diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index 962bde91e..6af1bd75f 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -28,9 +28,9 @@ import com.android.inputmethod.latin.RecapitalizeStatus; * This class contains all keyboard state transition logic. * * The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()}, - * {@link #onPressKey(int, boolean, int)}, {@link #onReleaseKey(int, boolean)}, - * {@link #onCodeInput(int, boolean, int)}, {@link #onCancelInput(boolean)}, - * {@link #onUpdateShiftState(int, int)}, {@link #onLongPressTimeout(int)}. + * {@link #onPressKey(int,boolean,int)}, {@link #onReleaseKey(int,boolean)}, + * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()}, {@link #onCancelInput()}, + * {@link #onUpdateShiftState(int,int)}, {@link #onLongPressTimeout(int)}. * * The actions are {@link SwitchActions}'s methods. */ @@ -74,6 +74,7 @@ public final class KeyboardState { private static final int SWITCH_STATE_SYMBOL = 2; private static final int SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL = 3; private static final int SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE = 4; + private static final int SWITCH_STATE_MOMENTARY_ALPHA_SHIFT = 5; private int mSwitchState = SWITCH_STATE_ALPHA; private boolean mIsAlphabetMode; @@ -525,6 +526,9 @@ public final class KeyboardState { } else if (mAlphabetShiftState.isShiftLockShifted() && withSliding) { // In shift locked state, shift has been pressed and slid out to other key. setShiftLocked(true); + } else if (mAlphabetShiftState.isManualShifted() && withSliding) { + // Shift has been pressed and slid out to other key. + mSwitchState = SWITCH_STATE_MOMENTARY_ALPHA_SHIFT; } else if (isShiftLocked && !mAlphabetShiftState.isShiftLockShifted() && (mShiftKeyState.isPressing() || mShiftKeyState.isPressingOnShifted()) && !withSliding) { @@ -554,17 +558,21 @@ public final class KeyboardState { mShiftKeyState.onRelease(); } - public void onCancelInput(final boolean isSinglePointer) { + public void onFinishSlidingInput() { if (DEBUG_EVENT) { - Log.d(TAG, "onCancelInput: single=" + isSinglePointer + " " + this); + Log.d(TAG, "onFinishSlidingInput: " + this); } // Switch back to the previous keyboard mode if the user cancels sliding input. - if (isSinglePointer) { - if (mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL) { - toggleAlphabetAndSymbols(); - } else if (mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE) { - toggleShiftInSymbols(); - } + switch (mSwitchState) { + case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: + toggleAlphabetAndSymbols(); + break; + case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: + toggleShiftInSymbols(); + break; + case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: + setAlphabetKeyboard(); + break; } } @@ -577,10 +585,9 @@ public final class KeyboardState { return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER; } - public void onCodeInput(final int code, final boolean isSinglePointer, final int autoCaps) { + public void onCodeInput(final int code, final int autoCaps) { if (DEBUG_EVENT) { Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code) - + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this); } @@ -593,23 +600,12 @@ public final class KeyboardState { } else { mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; } - } else if (isSinglePointer) { - // Switch back to the previous keyboard mode if the user pressed the mode change key - // and slid to other key, then released the finger. - // If the user cancels the sliding input, switching back to the previous keyboard - // mode is handled by {@link #onCancelInput}. - toggleAlphabetAndSymbols(); } break; case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: if (code == Constants.CODE_SHIFT) { // Detected only the shift key has been pressed on symbol layout, and then released. mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; - } else if (isSinglePointer) { - // Switch back to the previous keyboard mode if the user pressed the shift key on - // symbol mode and slid to other key, then released the finger. - toggleShiftInSymbols(); - mSwitchState = SWITCH_STATE_SYMBOL; } break; case SWITCH_STATE_SYMBOL_BEGIN: @@ -650,6 +646,7 @@ public final class KeyboardState { case SWITCH_STATE_SYMBOL: return "SYMBOL"; case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: return "MOMENTARY-ALPHA-SYMBOL"; case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: return "MOMENTARY-SYMBOL-MORE"; + case SWITCH_STATE_MOMENTARY_ALPHA_SHIFT: return "MOMENTARY-ALPHA_SHIFT"; default: return null; } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java index 8901f99b7..31ef3cd8f 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java @@ -48,6 +48,9 @@ public final class PointerTrackerQueue { public void add(final Element pointer) { synchronized (mExpandableArrayOfActivePointers) { + if (DEBUG) { + Log.d(TAG, "add: " + pointer + " " + this); + } final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; final int arraySize = mArraySize; if (arraySize < expandableArray.size()) { @@ -61,24 +64,27 @@ public final class PointerTrackerQueue { public void remove(final Element pointer) { synchronized (mExpandableArrayOfActivePointers) { + if (DEBUG) { + Log.d(TAG, "remove: " + pointer + " " + this); + } final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; final int arraySize = mArraySize; - int newSize = 0; + int newIndex = 0; for (int index = 0; index < arraySize; index++) { final Element element = expandableArray.get(index); if (element == pointer) { - if (newSize != index) { + if (newIndex != index) { Log.w(TAG, "Found duplicated element in remove: " + pointer); } continue; // Remove this element from the expandableArray. } - if (newSize != index) { + if (newIndex != index) { // Shift this element toward the beginning of the expandableArray. - expandableArray.set(newSize, element); + expandableArray.set(newIndex, element); } - newSize++; + newIndex++; } - mArraySize = newSize; + mArraySize = newIndex; } } @@ -95,8 +101,8 @@ public final class PointerTrackerQueue { } final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; final int arraySize = mArraySize; - int newSize, index; - for (newSize = index = 0; index < arraySize; index++) { + int newIndex, index; + for (newIndex = index = 0; index < arraySize; index++) { final Element element = expandableArray.get(index); if (element == pointer) { break; // Stop releasing elements. @@ -105,29 +111,30 @@ public final class PointerTrackerQueue { element.onPhantomUpEvent(eventTime); continue; // Remove this element from the expandableArray. } - if (newSize != index) { + if (newIndex != index) { // Shift this element toward the beginning of the expandableArray. - expandableArray.set(newSize, element); + expandableArray.set(newIndex, element); } - newSize++; + newIndex++; } // Shift rest of the expandableArray. int count = 0; for (; index < arraySize; index++) { final Element element = expandableArray.get(index); if (element == pointer) { - if (count > 0) { + count++; + if (count > 1) { Log.w(TAG, "Found duplicated element in releaseAllPointersOlderThan: " + pointer); } - count++; } - if (newSize != index) { - expandableArray.set(newSize, expandableArray.get(index)); - newSize++; + if (newIndex != index) { + // Shift this element toward the beginning of the expandableArray. + expandableArray.set(newIndex, expandableArray.get(index)); } + newIndex++; } - mArraySize = newSize; + mArraySize = newIndex; } } @@ -146,26 +153,26 @@ public final class PointerTrackerQueue { } final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; final int arraySize = mArraySize; - int newSize = 0, count = 0; + int newIndex = 0, count = 0; for (int index = 0; index < arraySize; index++) { final Element element = expandableArray.get(index); if (element == pointer) { - if (count > 0) { + count++; + if (count > 1) { Log.w(TAG, "Found duplicated element in releaseAllPointersExcept: " + pointer); } - count++; } else { element.onPhantomUpEvent(eventTime); continue; // Remove this element from the expandableArray. } - if (newSize != index) { + if (newIndex != index) { // Shift this element toward the beginning of the expandableArray. - expandableArray.set(newSize, element); + expandableArray.set(newIndex, element); } - newSize++; + newIndex++; } - mArraySize = newSize; + mArraySize = newIndex; } } @@ -202,6 +209,9 @@ public final class PointerTrackerQueue { public void cancelAllPointerTracker() { synchronized (mExpandableArrayOfActivePointers) { + if (DEBUG) { + Log.d(TAG, "cancelAllPointerTracker: " + this); + } final ArrayList<Element> expandableArray = mExpandableArrayOfActivePointers; final int arraySize = mArraySize; for (int index = 0; index < arraySize; index++) { diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 75c2cf2c8..b9db9a092 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -16,21 +16,26 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.personalization.AccountUtils; + import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; +import android.net.Uri; import android.os.SystemClock; import android.provider.BaseColumns; +import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.text.TextUtils; import android.util.Log; +import java.util.List; import java.util.Locale; public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { - private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME,}; + private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME}; private static final String[] PROJECTION_ID_ONLY = {BaseColumns._ID}; private static final String TAG = ContactsBinaryDictionary.class.getSimpleName(); @@ -102,9 +107,32 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { @Override public void loadDictionaryAsync() { + clearFusionDictionary(); + loadDeviceAccountsEmailAddresses(); + loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI); + // TODO: Switch this URL to the newer ContactsContract too + loadDictionaryAsyncForUri(Contacts.CONTENT_URI); + } + + private void loadDeviceAccountsEmailAddresses() { + final List<String> accountVocabulary = + AccountUtils.getDeviceAccountsEmailAddresses(mContext); + if (accountVocabulary == null || accountVocabulary.isEmpty()) { + return; + } + for (String word : accountVocabulary) { + if (DEBUG) { + Log.d(TAG, "loadAccountVocabulary: " + word); + } + super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, + false /* isNotAWord */); + } + } + + private void loadDictionaryAsyncForUri(final Uri uri) { try { Cursor cursor = mContext.getContentResolver() - .query(Contacts.CONTENT_URI, PROJECTION, null, null, null); + .query(uri, PROJECTION, null, null, null); if (cursor != null) { try { if (cursor.moveToFirst()) { @@ -129,7 +157,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } private void addWords(final Cursor cursor) { - clearFusionDictionary(); int count = 0; while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) { String name = cursor.getString(INDEX_NAME); @@ -173,6 +200,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { // capitalization of i. final int wordLen = StringUtils.codePointCount(word); if (wordLen < MAX_WORD_LENGTH && wordLen > 1) { + if (DEBUG) { + Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord); + } super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, false /* isNotAWord */); if (!TextUtils.isEmpty(prevWord)) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 84c752934..9caec5592 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -250,6 +250,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } public void postResumeSuggestions() { + removeMessages(MSG_RESUME_SUGGESTIONS); sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions); } @@ -759,7 +760,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } mSuggestedWords = SuggestedWords.EMPTY; - mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart); + mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart, + false /* shouldFinishComposition */); if (isDifferentTextField) { mainKeyboardView.closing(); @@ -1148,13 +1150,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // This will reset the whole input state to the starting state. It will clear // the composing word, reset the last composed word, tell the inputconnection about it. private void resetEntireInputState(final int newCursorPosition) { + final boolean shouldFinishComposition = mWordComposer.isComposingWord(); resetComposingState(true /* alsoResetLastComposedWord */); if (mSettings.getCurrent().mBigramPredictionEnabled) { clearSuggestionStrip(); } else { setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false); } - mConnection.resetCachesUponCursorMove(newCursorPosition); + mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition); } private void resetComposingState(final boolean alsoResetLastComposedWord) { @@ -1749,9 +1752,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Called from PointerTracker through the KeyboardActionListener interface @Override + public void onFinishSlidingInput() { + // User finished sliding input. + mKeyboardSwitcher.onFinishSlidingInput(); + } + + // Called from PointerTracker through the KeyboardActionListener interface + @Override public void onCancelInput() { // User released a finger outside any key - mKeyboardSwitcher.onCancelInput(); + // Nothing to do so far. } @Override @@ -2436,10 +2446,15 @@ public final class LatinIME extends InputMethodService implements KeyboardAction 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 we don't know the cursor location, return. + if (mLastSelectionStart < 0) return; if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return; final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(), 0 /* additionalPrecedingWordsCount */); if (null == range) return; // Happens if we don't have an input connection at all + // If for some strange reason (editor bug or so) we measure the text before the cursor as + // longer than what the entire text is supposed to be, the safe thing to do is bail out. + if (range.mCharsBefore > mLastSelectionStart) return; final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); final String typedWord = range.mWord.toString(); if (range.mWord instanceof SpannableString) { @@ -2604,8 +2619,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Callback called by PointerTracker through the KeyboardActionListener. This is called when a // key is depressed; release matching call is onReleaseKey below. @Override - public void onPressKey(final int primaryCode) { - mKeyboardSwitcher.onPressKey(primaryCode); + public void onPressKey(final int primaryCode, final boolean isSinglePointer) { + mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer); } // Callback by PointerTracker through the KeyboardActionListener. This is called when a key diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java index b74b979b5..a9fba5348 100644 --- a/java/src/com/android/inputmethod/latin/ResourceUtils.java +++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java @@ -19,14 +19,17 @@ package com.android.inputmethod.latin; import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Build; +import android.text.TextUtils; import android.util.Log; import android.util.TypedValue; +import com.android.inputmethod.annotations.UsedForTesting; + +import java.util.ArrayList; import java.util.HashMap; public final class ResourceUtils { private static final String TAG = ResourceUtils.class.getSimpleName(); - private static final boolean DEBUG = false; public static final float UNDEFINED_RATIO = -1.0f; public static final int UNDEFINED_DIMENSION = -1; @@ -35,11 +38,32 @@ public final class ResourceUtils { // This utility class is not publicly instantiable. } - private static final String DEFAULT_PREFIX = "DEFAULT,"; - private static final String HARDWARE_PREFIX = Build.HARDWARE + ","; private static final HashMap<String, String> sDeviceOverrideValueMap = CollectionUtils.newHashMap(); + private static final String[] BUILD_KEYS_AND_VALUES = { + "HARDWARE", Build.HARDWARE, + "MODEL", Build.MODEL, + "BRAND", Build.BRAND, + "MANUFACTURER", Build.MANUFACTURER + }; + private static final HashMap<String, String> sBuildKeyValues; + private static final String sBuildKeyValuesDebugString; + + static { + sBuildKeyValues = CollectionUtils.newHashMap(); + final ArrayList<String> keyValuePairs = CollectionUtils.newArrayList(); + final int keyCount = BUILD_KEYS_AND_VALUES.length / 2; + for (int i = 0; i < keyCount; i++) { + final int index = i * 2; + final String key = BUILD_KEYS_AND_VALUES[index]; + final String value = BUILD_KEYS_AND_VALUES[index + 1]; + sBuildKeyValues.put(key, value); + keyValuePairs.add(key + '=' + value); + } + sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]"; + } + public static String getDeviceOverrideValue(final Resources res, final int overrideResId) { final int orientation = res.getConfiguration().orientation; final String key = overrideResId + "-" + orientation; @@ -48,33 +72,114 @@ public final class ResourceUtils { } final String[] overrideArray = res.getStringArray(overrideResId); - final String overrideValue = StringUtils.findPrefixedString(HARDWARE_PREFIX, overrideArray); + final String overrideValue = findConstantForKeyValuePairs(sBuildKeyValues, overrideArray); // The overrideValue might be an empty string. if (overrideValue != null) { - if (DEBUG) { - Log.d(TAG, "Find override value:" - + " resource="+ res.getResourceEntryName(overrideResId) - + " Build.HARDWARE=" + Build.HARDWARE + " override=" + overrideValue); - } + Log.i(TAG, "Find override value:" + + " resource="+ res.getResourceEntryName(overrideResId) + + " build=" + sBuildKeyValuesDebugString + + " override=" + overrideValue); sDeviceOverrideValueMap.put(key, overrideValue); return overrideValue; } - final String defaultValue = StringUtils.findPrefixedString(DEFAULT_PREFIX, overrideArray); + final String defaultValue = findDefaultConstant(overrideArray); // The defaultValue might be an empty string. if (defaultValue == null) { Log.w(TAG, "Couldn't find override value nor default value:" + " resource="+ res.getResourceEntryName(overrideResId) - + " Build.HARDWARE=" + Build.HARDWARE); - } else if (DEBUG) { - Log.d(TAG, "Found default value:" - + " resource="+ res.getResourceEntryName(overrideResId) - + " Build.HARDWARE=" + Build.HARDWARE + " default=" + defaultValue); + + " build=" + sBuildKeyValuesDebugString); + } else { + Log.i(TAG, "Found default value:" + + " resource="+ res.getResourceEntryName(overrideResId) + + " build=" + sBuildKeyValuesDebugString + + " default=" + defaultValue); } sDeviceOverrideValueMap.put(key, defaultValue); return defaultValue; } + /** + * Find the condition that fulfills specified key value pairs from an array of + * "condition,constant", and return the corresponding string constant. A condition is + * "pattern1[:pattern2...] (or an empty string for the default). A pattern is + * "key=regexp_value" string. The condition matches only if all patterns of the condition + * are true for the specified key value pairs. + * + * For example, "condition,constant" has the following format. + * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()}) + * - HARDWARE=mako,constantForNexus4 + * - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4 + * - ,defaultConstant + * + * @param keyValuePairs attributes to be used to look for a matched condition. + * @param conditionConstantArray an array of "condition,constant" elements to be searched. + * @return the constant part of the matched "condition,constant" element. Returns null if no + * condition matches. + */ + @UsedForTesting + static String findConstantForKeyValuePairs(final HashMap<String, String> keyValuePairs, + final String[] conditionConstantArray) { + if (conditionConstantArray == null || keyValuePairs == null) { + return null; + } + for (final String conditionConstant : conditionConstantArray) { + final int posComma = conditionConstant.indexOf(','); + if (posComma < 0) { + throw new RuntimeException("Array element has no comma: " + conditionConstant); + } + final String condition = conditionConstant.substring(0, posComma); + if (condition.isEmpty()) { + // Default condition. The default condition should be searched by + // {@link #findConstantForDefault(String[])}. + continue; + } + if (fulfillsCondition(keyValuePairs, condition)) { + return conditionConstant.substring(posComma + 1); + } + } + return null; + } + + private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs, + final String condition) { + final String[] patterns = condition.split(":"); + // Check all patterns in a condition are true + for (final String pattern : patterns) { + final int posEqual = pattern.indexOf('='); + if (posEqual < 0) { + throw new RuntimeException("Pattern has no '=': " + condition); + } + final String key = pattern.substring(0, posEqual); + final String value = keyValuePairs.get(key); + if (value == null) { + throw new RuntimeException("Found unknown key: " + condition); + } + final String patternRegexpValue = pattern.substring(posEqual + 1); + if (!value.matches(patternRegexpValue)) { + return false; + } + } + return true; + } + + @UsedForTesting + static String findDefaultConstant(final String[] conditionConstantArray) { + if (conditionConstantArray == null) { + return null; + } + for (final String condition : conditionConstantArray) { + final int posComma = condition.indexOf(','); + if (posComma < 0) { + throw new RuntimeException("Array element has no comma: " + condition); + } + if (posComma == 0) { // condition is empty. + return condition.substring(posComma + 1); + } + } + return null; + } + public static boolean isValidFraction(final float fraction) { return fraction >= 0.0f; } diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 8ed7ab264..980215de6 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -135,13 +135,14 @@ public final class RichInputConnection { if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); } - public void resetCachesUponCursorMove(final int newCursorPosition) { + public void resetCachesUponCursorMove(final int newCursorPosition, + final boolean shouldFinishComposition) { mCurrentCursorPosition = newCursorPosition; mComposingText.setLength(0); mCommittedTextBeforeComposingText.setLength(0); final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0); if (null != textBeforeCursor) mCommittedTextBeforeComposingText.append(textBeforeCursor); - if (null != mIC) { + if (null != mIC && shouldFinishComposition) { mIC.finishComposingText(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.richInputConnection_finishComposingText(); diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index 3f7be99e5..94513e635 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -100,6 +100,12 @@ public final class RichInputMethodManager { throw new RuntimeException("Input method id for " + packageName + " not found."); } + public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList( + boolean allowsImplicitlySelectedSubtypes) { + return mImmWrapper.mImm.getEnabledInputMethodSubtypeList( + mInputMethodInfoOfThisIme, allowsImplicitlySelectedSubtypes); + } + public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) { return true; @@ -116,8 +122,8 @@ public final class RichInputMethodManager { final boolean onlyCurrentIme) { final InputMethodManager imm = mImmWrapper.mImm; final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); - final List<InputMethodSubtype> enabledSubtypes = imm.getEnabledInputMethodSubtypeList( - mInputMethodInfoOfThisIme, true /* allowsImplicitlySelectedSubtypes */); + final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList( + true /* allowsImplicitlySelectedSubtypes */); final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes); if (currentIndex == INDEX_NOT_FOUND) { Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype=" @@ -214,8 +220,8 @@ public final class RichInputMethodManager { final InputMethodSubtype subtype) { final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype); final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList( - subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList( - mInputMethodInfoOfThisIme, false /* allowsImplicitlySelectedSubtypes */)); + subtype, getMyEnabledInputMethodSubtypeList( + false /* allowsImplicitlySelectedSubtypes */)); return subtypeEnabled && !subtypeExplicitlyEnabled; } @@ -312,8 +318,7 @@ public final class RichInputMethodManager { if (filteredImisCount > 1) { return true; } - final List<InputMethodSubtype> subtypes = - mImmWrapper.mImm.getEnabledInputMethodSubtypeList(null, true); + final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true); int keyboardCount = 0; // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's // both explicitly and implicitly enabled input method subtype. diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java index 9819a02ef..7c4156c48 100644 --- a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java +++ b/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java @@ -59,7 +59,7 @@ public final class SeekBarDialogPreference extends DialogPreference public void setInterface(final ValueProxy proxy) { mValueProxy = proxy; - setSummary(getValueText(proxy.readValue(getKey()))); + setSummary(getValueText(clipValue(proxy.readValue(getKey())))); } private String getValueText(final int value) { diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index d5ee58a63..ab050d7a3 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -65,23 +65,6 @@ public final class StringUtils { } /** - * Find a string that start with specified prefix from an array. - * - * @param prefix a prefix string to find. - * @param array an string array to be searched. - * @return the rest part of the string that starts with the prefix. - * Returns null if it couldn't be found. - */ - public static String findPrefixedString(final String prefix, final String[] array) { - for (final String element : array) { - if (element.startsWith(prefix)) { - return element.substring(prefix.length()); - } - } - return null; - } - - /** * Remove duplicates from an array of strings. * * This method will always keep the first occurrence of all strings at their position diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index bef8a3cf1..282b5794f 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -115,7 +115,7 @@ public final class SubtypeSwitcher { */ public void updateParametersOnStartInputView() { final List<InputMethodSubtype> enabledSubtypesOfThisIme = - mRichImm.getInputMethodManager().getEnabledInputMethodSubtypeList(null, true); + mRichImm.getMyEnabledInputMethodSubtypeList(true); mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); updateShortcutIME(); } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 51bd901fb..e078f03f4 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -16,7 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; @@ -211,9 +210,8 @@ public final class WordComposer { } /** - * Internal method to retrieve reasonable proximity info for a character. + * Add a dummy key by retrieving reasonable coordinates */ - @UsedForTesting public void addKeyInfo(final int codePoint, final Keyboard keyboard) { final int x, y; final Key key; diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java new file mode 100644 index 000000000..93687e193 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java @@ -0,0 +1,47 @@ +/* + * 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.personalization; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; +import android.util.Patterns; + +import java.util.ArrayList; +import java.util.List; + +public class AccountUtils { + private AccountUtils() { + // This utility class is not publicly instantiable. + } + + private static Account[] getAccounts(final Context context) { + return AccountManager.get(context).getAccounts(); + } + + public static List<String> getDeviceAccountsEmailAddresses(final Context context) { + final ArrayList<String> retval = new ArrayList<String>(); + for (final Account account : getAccounts(context)) { + final String name = account.name; + if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) { + retval.add(name); + retval.add(name.split("@")[0]); + } + } + return retval; + } +} diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java index affe3a348..8a2de887d 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java @@ -17,270 +17,27 @@ package com.android.inputmethod.latin.setup; import android.app.Activity; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; -import android.media.MediaPlayer; -import android.net.Uri; import android.os.Bundle; -import android.os.Message; import android.provider.Settings; -import android.util.Log; -import android.view.View; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.VideoView; -import com.android.inputmethod.compat.TextViewCompatUtils; -import com.android.inputmethod.compat.ViewCompatUtils; -import com.android.inputmethod.latin.CollectionUtils; -import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.SettingsActivity; -import com.android.inputmethod.latin.StaticInnerHandlerWrapper; - -import java.util.ArrayList; - -// TODO: Use Fragment to implement welcome screen and setup steps. -public final class SetupActivity extends Activity implements View.OnClickListener { - private static final String TAG = SetupActivity.class.getSimpleName(); - - private View mWelcomeScreen; - private View mSetupScreen; - private Uri mWelcomeVideoUri; - private VideoView mWelcomeVideoView; - private View mActionStart; - private View mActionNext; - private TextView mStep1Bullet; - private TextView mActionFinish; - private SetupStepGroup mSetupStepGroup; - private static final String STATE_STEP = "step"; - private int mStepNumber; - private static final int STEP_WELCOME = 0; - private static final int STEP_1 = 1; - private static final int STEP_2 = 2; - private static final int STEP_3 = 3; - private boolean mWasLanguageAndInputSettingsInvoked; - - private final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this); - - static final class SettingsPoolingHandler extends StaticInnerHandlerWrapper<SetupActivity> { - private static final int MSG_POLLING_IME_SETTINGS = 0; - private static final long IME_SETTINGS_POLLING_INTERVAL = 200; - - public SettingsPoolingHandler(final SetupActivity outerInstance) { - super(outerInstance); - } - - @Override - public void handleMessage(final Message msg) { - final SetupActivity setupActivity = getOuterInstance(); - if (setupActivity == null) { - return; - } - switch (msg.what) { - case MSG_POLLING_IME_SETTINGS: - if (SetupActivity.isThisImeEnabled(setupActivity)) { - setupActivity.invokeSetupWizardOfThisIme(); - return; - } - startPollingImeSettings(); - break; - } - } - - public void startPollingImeSettings() { - sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS), - IME_SETTINGS_POLLING_INTERVAL); - } - - public void cancelPollingImeSettings() { - removeMessages(MSG_POLLING_IME_SETTINGS); - } - } +public final class SetupActivity extends Activity { @Override protected void onCreate(final Bundle savedInstanceState) { - setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar); super.onCreate(savedInstanceState); - - setContentView(R.layout.setup_wizard); - - RichInputMethodManager.init(this); - - if (savedInstanceState == null) { - mStepNumber = determineSetupStepNumber(); - if (mStepNumber == STEP_1 && !mWasLanguageAndInputSettingsInvoked) { - mStepNumber = STEP_WELCOME; - } - if (mStepNumber == STEP_3) { - // This IME already has been enabled and set as current IME. - // TODO: Implement tutorial. - invokeSettingsOfThisIme(); - finish(); - return; - } - } else { - mStepNumber = savedInstanceState.getInt(STATE_STEP); - } - - final String applicationName = getResources().getString(getApplicationInfo().labelRes); - mWelcomeScreen = findViewById(R.id.setup_welcome_screen); - final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title); - welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName)); - - mSetupScreen = findViewById(R.id.setup_steps_screen); - final TextView stepsTitle = (TextView)findViewById(R.id.setup_title); - stepsTitle.setText(getString(R.string.setup_steps_title, applicationName)); - - final SetupStepIndicatorView indicatorView = - (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator); - mSetupStepGroup = new SetupStepGroup(indicatorView); - - mStep1Bullet = (TextView)findViewById(R.id.setup_step1_bullet); - mStep1Bullet.setOnClickListener(this); - final SetupStep step1 = new SetupStep(STEP_1, applicationName, - mStep1Bullet, findViewById(R.id.setup_step1), - R.string.setup_step1_title, R.string.setup_step1_instruction, - R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1, - R.string.setup_step1_action); - step1.setAction(new Runnable() { - @Override - public void run() { - invokeLanguageAndInputSettings(); - mHandler.startPollingImeSettings(); - } - }); - mSetupStepGroup.addStep(step1); - - final SetupStep step2 = new SetupStep(STEP_2, applicationName, - (TextView)findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2), - R.string.setup_step2_title, R.string.setup_step2_instruction, - 0 /* finishedInstruction */, R.drawable.ic_setup_step2, - R.string.setup_step2_action); - step2.setAction(new Runnable() { - @Override - public void run() { - // Invoke input method picker. - RichInputMethodManager.getInstance().getInputMethodManager() - .showInputMethodPicker(); - } - }); - mSetupStepGroup.addStep(step2); - - final SetupStep step3 = new SetupStep(STEP_3, applicationName, - (TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3), - R.string.setup_step3_title, R.string.setup_step3_instruction, - 0 /* finishedInstruction */, R.drawable.ic_setup_step3, - R.string.setup_step3_action); - step3.setAction(new Runnable() { - @Override - public void run() { - invokeSubtypeEnablerOfThisIme(); - } - }); - mSetupStepGroup.addStep(step3); - - mWelcomeVideoUri = new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(getPackageName()) - .path(Integer.toString(R.raw.setup_welcome_video)) - .build(); - mWelcomeVideoView = (VideoView)findViewById(R.id.setup_welcome_video); - mWelcomeVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(final MediaPlayer mp) { - mp.start(); - } - }); - mWelcomeVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(final MediaPlayer mp) { - // Now VideoView has been laid-out and ready to play, remove background of it to - // reveal the video. - mWelcomeVideoView.setBackgroundResource(0); - } - }); - final ImageView welcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image); - mWelcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { - @Override - public boolean onError(final MediaPlayer mp, final int what, final int extra) { - Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra); - mWelcomeVideoView.setVisibility(View.GONE); - welcomeImageView.setImageResource(R.raw.setup_welcome_image); - welcomeImageView.setVisibility(View.VISIBLE); - return true; - } - }); - - mActionStart = findViewById(R.id.setup_start_label); - mActionStart.setOnClickListener(this); - mActionNext = findViewById(R.id.setup_next); - mActionNext.setOnClickListener(this); - mActionFinish = (TextView)findViewById(R.id.setup_finish); - TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mActionFinish, - getResources().getDrawable(R.drawable.ic_setup_finish), null, null, null); - mActionFinish.setOnClickListener(this); - } - - @Override - public void onClick(final View v) { - if (v == mActionFinish) { - finish(); - return; - } - final int currentStep = determineSetupStepNumber(); - final int nextStep; - if (v == mActionStart) { - nextStep = STEP_1; - } else if (v == mActionNext) { - nextStep = mStepNumber + 1; - } else if (v == mStep1Bullet && currentStep == STEP_2) { - nextStep = STEP_1; - } else { - nextStep = mStepNumber; - } - if (mStepNumber != nextStep) { - mStepNumber = nextStep; - updateSetupStepView(); - } - } - - private void invokeSetupWizardOfThisIme() { - final Intent intent = new Intent(); - intent.setClass(this, SetupActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } - - private void invokeSettingsOfThisIme() { - final Intent intent = new Intent(); - intent.setClass(this, SettingsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } - - private void invokeLanguageAndInputSettings() { final Intent intent = new Intent(); - intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS); - intent.addCategory(Intent.CATEGORY_DEFAULT); - startActivity(intent); - mWasLanguageAndInputSettingsInvoked = true; - } - - private void invokeSubtypeEnablerOfThisIme() { - final InputMethodInfo imi = - RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme(); - final Intent intent = new Intent(); - intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId()); + intent.setClass(this, SetupWizardActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); + if (!isFinishing()) { + finish(); + } } /** @@ -317,164 +74,4 @@ public final class SetupActivity extends Activity implements View.OnClickListene context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); return myImi.getId().equals(currentImeId); } - - private int determineSetupStepNumber() { - mHandler.cancelPollingImeSettings(); - if (!isThisImeEnabled(this)) { - return STEP_1; - } - if (!isThisImeCurrent(this)) { - return STEP_2; - } - return STEP_3; - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(STATE_STEP, mStepNumber); - } - - @Override - protected void onRestoreInstanceState(final Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mStepNumber = savedInstanceState.getInt(STATE_STEP); - } - - @Override - protected void onRestart() { - super.onRestart(); - if (mStepNumber != STEP_WELCOME) { - mStepNumber = determineSetupStepNumber(); - } - } - - @Override - protected void onResume() { - super.onResume(); - updateSetupStepView(); - } - - @Override - public void onBackPressed() { - if (mStepNumber == STEP_1) { - mStepNumber = STEP_WELCOME; - updateSetupStepView(); - return; - } - super.onBackPressed(); - } - - @Override - protected void onPause() { - mWelcomeVideoView.stopPlayback(); - super.onPause(); - } - - @Override - public void onWindowFocusChanged(final boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus && mStepNumber != STEP_WELCOME) { - mStepNumber = determineSetupStepNumber(); - updateSetupStepView(); - } - } - - private void updateSetupStepView() { - final boolean welcomeScreen = (mStepNumber == STEP_WELCOME); - mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE); - mSetupScreen.setVisibility(welcomeScreen ? View.GONE: View.VISIBLE); - if (welcomeScreen) { - mWelcomeVideoView.setVideoURI(mWelcomeVideoUri); - mWelcomeVideoView.start(); - return; - } - mWelcomeVideoView.stopPlayback(); - final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber(); - mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone); - mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE); - mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE); - } - - static final class SetupStep implements View.OnClickListener { - public final int mStepNo; - private final View mStepView; - private final TextView mBulletView; - private final int mActivatedColor; - private final int mDeactivatedColor; - private final String mInstruction; - private final String mFinishedInstruction; - private final TextView mActionLabel; - private Runnable mAction; - - public SetupStep(final int stepNo, final String applicationName, final TextView bulletView, - final View stepView, final int title, final int instruction, - final int finishedInstruction,final int actionIcon, final int actionLabel) { - mStepNo = stepNo; - mStepView = stepView; - mBulletView = bulletView; - final Resources res = stepView.getResources(); - mActivatedColor = res.getColor(R.color.setup_text_action); - mDeactivatedColor = res.getColor(R.color.setup_text_dark); - - final TextView titleView = (TextView)mStepView.findViewById(R.id.setup_step_title); - titleView.setText(res.getString(title, applicationName)); - mInstruction = (instruction == 0) ? null - : res.getString(instruction, applicationName); - mFinishedInstruction = (finishedInstruction == 0) ? null - : res.getString(finishedInstruction, applicationName); - - mActionLabel = (TextView)mStepView.findViewById(R.id.setup_step_action_label); - mActionLabel.setText(res.getString(actionLabel)); - if (actionIcon == 0) { - final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel); - ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0); - } else { - TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds( - mActionLabel, res.getDrawable(actionIcon), null, null, null); - } - } - - public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) { - mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE); - mBulletView.setTextColor(enabled ? mActivatedColor : mDeactivatedColor); - final TextView instructionView = (TextView)mStepView.findViewById( - R.id.setup_step_instruction); - instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction); - mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE); - } - - public void setAction(final Runnable action) { - mActionLabel.setOnClickListener(this); - mAction = action; - } - - @Override - public void onClick(final View v) { - if (v == mActionLabel && mAction != null) { - mAction.run(); - return; - } - } - } - - static final class SetupStepGroup { - private final SetupStepIndicatorView mIndicatorView; - private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList(); - - public SetupStepGroup(final SetupStepIndicatorView indicatorView) { - mIndicatorView = indicatorView; - } - - public void addStep(final SetupStep step) { - mGroup.add(step); - } - - public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) { - for (final SetupStep step : mGroup) { - step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone); - } - mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size()); - } - } } diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java new file mode 100644 index 000000000..0d25bc338 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java @@ -0,0 +1,472 @@ +/* + * 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.setup; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.res.Resources; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.provider.Settings; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodInfo; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.VideoView; + +import com.android.inputmethod.compat.TextViewCompatUtils; +import com.android.inputmethod.compat.ViewCompatUtils; +import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.RichInputMethodManager; +import com.android.inputmethod.latin.SettingsActivity; +import com.android.inputmethod.latin.StaticInnerHandlerWrapper; + +import java.util.ArrayList; + +// TODO: Use Fragment to implement welcome screen and setup steps. +public final class SetupWizardActivity extends Activity implements View.OnClickListener { + static final String TAG = SetupWizardActivity.class.getSimpleName(); + + private View mSetupWizard; + private View mWelcomeScreen; + private View mSetupScreen; + private Uri mWelcomeVideoUri; + private VideoView mWelcomeVideoView; + private View mActionStart; + private View mActionNext; + private TextView mStep1Bullet; + private TextView mActionFinish; + private SetupStepGroup mSetupStepGroup; + private static final String STATE_STEP = "step"; + private int mStepNumber; + private static final int STEP_WELCOME = 0; + private static final int STEP_1 = 1; + private static final int STEP_2 = 2; + private static final int STEP_3 = 3; + private static final int STEP_LAUNCHING_IME_SETTINGS = 4; + private static final int STEP_BACK_FROM_IME_SETTINGS = 5; + + final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this); + + static final class SettingsPoolingHandler + extends StaticInnerHandlerWrapper<SetupWizardActivity> { + private static final int MSG_POLLING_IME_SETTINGS = 0; + private static final long IME_SETTINGS_POLLING_INTERVAL = 200; + + public SettingsPoolingHandler(final SetupWizardActivity outerInstance) { + super(outerInstance); + } + + @Override + public void handleMessage(final Message msg) { + final SetupWizardActivity setupWizardActivity = getOuterInstance(); + if (setupWizardActivity == null) { + return; + } + switch (msg.what) { + case MSG_POLLING_IME_SETTINGS: + if (SetupActivity.isThisImeEnabled(setupWizardActivity)) { + setupWizardActivity.invokeSetupWizardOfThisIme(); + return; + } + startPollingImeSettings(); + break; + } + } + + public void startPollingImeSettings() { + sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS), + IME_SETTINGS_POLLING_INTERVAL); + } + + public void cancelPollingImeSettings() { + removeMessages(MSG_POLLING_IME_SETTINGS); + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + setTheme(android.R.style.Theme_Translucent_NoTitleBar); + super.onCreate(savedInstanceState); + + setContentView(R.layout.setup_wizard); + mSetupWizard = findViewById(R.id.setup_wizard); + + RichInputMethodManager.init(this); + + if (savedInstanceState == null) { + mStepNumber = determineSetupStepNumberFromLauncher(); + } else { + mStepNumber = savedInstanceState.getInt(STATE_STEP); + } + + final String applicationName = getResources().getString(getApplicationInfo().labelRes); + mWelcomeScreen = findViewById(R.id.setup_welcome_screen); + final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title); + welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName)); + + mSetupScreen = findViewById(R.id.setup_steps_screen); + final TextView stepsTitle = (TextView)findViewById(R.id.setup_title); + stepsTitle.setText(getString(R.string.setup_steps_title, applicationName)); + + final SetupStepIndicatorView indicatorView = + (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator); + mSetupStepGroup = new SetupStepGroup(indicatorView); + + mStep1Bullet = (TextView)findViewById(R.id.setup_step1_bullet); + mStep1Bullet.setOnClickListener(this); + final SetupStep step1 = new SetupStep(STEP_1, applicationName, + mStep1Bullet, findViewById(R.id.setup_step1), + R.string.setup_step1_title, R.string.setup_step1_instruction, + R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1, + R.string.setup_step1_action); + step1.setAction(new Runnable() { + @Override + public void run() { + invokeLanguageAndInputSettings(); + mHandler.startPollingImeSettings(); + } + }); + mSetupStepGroup.addStep(step1); + + final SetupStep step2 = new SetupStep(STEP_2, applicationName, + (TextView)findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2), + R.string.setup_step2_title, R.string.setup_step2_instruction, + 0 /* finishedInstruction */, R.drawable.ic_setup_step2, + R.string.setup_step2_action); + step2.setAction(new Runnable() { + @Override + public void run() { + // Invoke input method picker. + RichInputMethodManager.getInstance().getInputMethodManager() + .showInputMethodPicker(); + } + }); + mSetupStepGroup.addStep(step2); + + final SetupStep step3 = new SetupStep(STEP_3, applicationName, + (TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3), + R.string.setup_step3_title, R.string.setup_step3_instruction, + 0 /* finishedInstruction */, R.drawable.ic_setup_step3, + R.string.setup_step3_action); + step3.setAction(new Runnable() { + @Override + public void run() { + invokeSubtypeEnablerOfThisIme(); + } + }); + mSetupStepGroup.addStep(step3); + + mWelcomeVideoUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(getPackageName()) + .path(Integer.toString(R.raw.setup_welcome_video)) + .build(); + final VideoView welcomeVideoView = (VideoView)findViewById(R.id.setup_welcome_video); + welcomeVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(final MediaPlayer mp) { + // Now VideoView has been laid-out and ready to play, remove background of it to + // reveal the video. + welcomeVideoView.setBackgroundResource(0); + mp.setLooping(true); + } + }); + final ImageView welcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image); + welcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { + @Override + public boolean onError(final MediaPlayer mp, final int what, final int extra) { + Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra); + welcomeVideoView.setVisibility(View.GONE); + welcomeImageView.setImageResource(R.raw.setup_welcome_image); + welcomeImageView.setVisibility(View.VISIBLE); + // Remove unnecessary light gray background around still image. + final ViewGroup videoFrame = (ViewGroup)findViewById( + R.id.setup_welcome_video_frame); + videoFrame.setBackgroundColor(getResources().getColor(R.color.setup_background)); + videoFrame.requestLayout(); + return true; + } + }); + mWelcomeVideoView = welcomeVideoView; + + mActionStart = findViewById(R.id.setup_start_label); + mActionStart.setOnClickListener(this); + mActionNext = findViewById(R.id.setup_next); + mActionNext.setOnClickListener(this); + mActionFinish = (TextView)findViewById(R.id.setup_finish); + TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mActionFinish, + getResources().getDrawable(R.drawable.ic_setup_finish), null, null, null); + mActionFinish.setOnClickListener(this); + } + + @Override + public void onClick(final View v) { + if (v == mActionFinish) { + finish(); + return; + } + final int currentStep = determineSetupStepNumber(); + final int nextStep; + if (v == mActionStart) { + nextStep = STEP_1; + } else if (v == mActionNext) { + nextStep = mStepNumber + 1; + } else if (v == mStep1Bullet && currentStep == STEP_2) { + nextStep = STEP_1; + } else { + nextStep = mStepNumber; + } + if (mStepNumber != nextStep) { + mStepNumber = nextStep; + updateSetupStepView(); + } + } + + void invokeSetupWizardOfThisIme() { + final Intent intent = new Intent(); + intent.setClass(this, SetupWizardActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + + private void invokeSettingsOfThisIme() { + final Intent intent = new Intent(); + intent.setClass(this, SettingsActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + + void invokeLanguageAndInputSettings() { + final Intent intent = new Intent(); + intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + startActivity(intent); + } + + void invokeSubtypeEnablerOfThisIme() { + final InputMethodInfo imi = + RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme(); + final Intent intent = new Intent(); + intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId()); + startActivity(intent); + } + + private int determineSetupStepNumberFromLauncher() { + final int stepNumber = determineSetupStepNumber(); + if (stepNumber == STEP_1) { + return STEP_WELCOME; + } + if (stepNumber == STEP_3) { + return STEP_LAUNCHING_IME_SETTINGS; + } + return stepNumber; + } + + private int determineSetupStepNumber() { + mHandler.cancelPollingImeSettings(); + if (!SetupActivity.isThisImeEnabled(this)) { + return STEP_1; + } + if (!SetupActivity.isThisImeCurrent(this)) { + return STEP_2; + } + return STEP_3; + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_STEP, mStepNumber); + } + + @Override + protected void onRestoreInstanceState(final Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mStepNumber = savedInstanceState.getInt(STATE_STEP); + } + + private static boolean isInSetupSteps(final int stepNumber) { + return stepNumber >= STEP_1 && stepNumber <= STEP_3; + } + + @Override + protected void onRestart() { + super.onRestart(); + if (isInSetupSteps(mStepNumber)) { + mStepNumber = determineSetupStepNumber(); + } + } + + @Override + protected void onResume() { + super.onResume(); + if (mStepNumber == STEP_LAUNCHING_IME_SETTINGS) { + // Prevent white screen flashing while launching settings activity. + mSetupWizard.setVisibility(View.INVISIBLE); + invokeSettingsOfThisIme(); + mStepNumber = STEP_BACK_FROM_IME_SETTINGS; + return; + } + if (mStepNumber == STEP_BACK_FROM_IME_SETTINGS) { + finish(); + return; + } + updateSetupStepView(); + } + + @Override + public void onBackPressed() { + if (mStepNumber == STEP_1) { + mStepNumber = STEP_WELCOME; + updateSetupStepView(); + return; + } + super.onBackPressed(); + } + + private static void hideAndStopVideo(final VideoView videoView) { + videoView.stopPlayback(); + videoView.setVisibility(View.INVISIBLE); + } + + @Override + protected void onPause() { + hideAndStopVideo(mWelcomeVideoView); + super.onPause(); + } + + @Override + public void onWindowFocusChanged(final boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus && isInSetupSteps(mStepNumber)) { + mStepNumber = determineSetupStepNumber(); + updateSetupStepView(); + } + } + + private void updateSetupStepView() { + mSetupWizard.setVisibility(View.VISIBLE); + final boolean welcomeScreen = (mStepNumber == STEP_WELCOME); + mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE); + mSetupScreen.setVisibility(welcomeScreen ? View.GONE : View.VISIBLE); + if (welcomeScreen) { + mWelcomeVideoView.setVisibility(View.VISIBLE); + mWelcomeVideoView.setVideoURI(mWelcomeVideoUri); + mWelcomeVideoView.start(); + return; + } + hideAndStopVideo(mWelcomeVideoView); + final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber(); + mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone); + mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE); + mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE); + } + + static final class SetupStep implements View.OnClickListener { + public final int mStepNo; + private final View mStepView; + private final TextView mBulletView; + private final int mActivatedColor; + private final int mDeactivatedColor; + private final String mInstruction; + private final String mFinishedInstruction; + private final TextView mActionLabel; + private Runnable mAction; + + public SetupStep(final int stepNo, final String applicationName, final TextView bulletView, + final View stepView, final int title, final int instruction, + final int finishedInstruction, final int actionIcon, final int actionLabel) { + mStepNo = stepNo; + mStepView = stepView; + mBulletView = bulletView; + final Resources res = stepView.getResources(); + mActivatedColor = res.getColor(R.color.setup_text_action); + mDeactivatedColor = res.getColor(R.color.setup_text_dark); + + final TextView titleView = (TextView)mStepView.findViewById(R.id.setup_step_title); + titleView.setText(res.getString(title, applicationName)); + mInstruction = (instruction == 0) ? null + : res.getString(instruction, applicationName); + mFinishedInstruction = (finishedInstruction == 0) ? null + : res.getString(finishedInstruction, applicationName); + + mActionLabel = (TextView)mStepView.findViewById(R.id.setup_step_action_label); + mActionLabel.setText(res.getString(actionLabel)); + if (actionIcon == 0) { + final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel); + ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0); + } else { + TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds( + mActionLabel, res.getDrawable(actionIcon), null, null, null); + } + } + + public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) { + mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE); + mBulletView.setTextColor(enabled ? mActivatedColor : mDeactivatedColor); + final TextView instructionView = (TextView)mStepView.findViewById( + R.id.setup_step_instruction); + instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction); + mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE); + } + + public void setAction(final Runnable action) { + mActionLabel.setOnClickListener(this); + mAction = action; + } + + @Override + public void onClick(final View v) { + if (v == mActionLabel && mAction != null) { + mAction.run(); + return; + } + } + } + + static final class SetupStepGroup { + private final SetupStepIndicatorView mIndicatorView; + private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList(); + + public SetupStepGroup(final SetupStepIndicatorView indicatorView) { + mIndicatorView = indicatorView; + } + + public void addStep(final SetupStep step) { + mGroup.add(step); + } + + public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) { + for (final SetupStep step : mGroup) { + step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone); + } + mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size()); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index aa60496ae..13fcaf48a 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -23,7 +23,7 @@ import android.service.textservice.SpellCheckerService; import android.util.Log; import android.view.textservice.SuggestionsInfo; -import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.ContactsBinaryDictionary; @@ -126,6 +126,19 @@ public final class AndroidSpellCheckerService extends SpellCheckerService return script; } + private static String getKeyboardLayoutNameForScript(final int script) { + switch (script) { + case AndroidSpellCheckerService.SCRIPT_LATIN: + return "qwerty"; + case AndroidSpellCheckerService.SCRIPT_CYRILLIC: + return "east_slavic"; + case AndroidSpellCheckerService.SCRIPT_GREEK: + return "greek"; + default: + throw new RuntimeException("Wrong script supplied: " + script); + } + } + @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { if (!PREF_USE_CONTACTS_KEY.equals(key)) return; @@ -385,9 +398,13 @@ public final class AndroidSpellCheckerService extends SpellCheckerService return pool; } - public DictAndProximity createDictAndProximity(final Locale locale) { + public DictAndKeyboard createDictAndKeyboard(final Locale locale) { final int script = getScriptFromLocale(locale); - final ProximityInfo proximityInfo = new SpellCheckerProximityInfo(script); + final String keyboardLayoutName = getKeyboardLayoutNameForScript(script); + final KeyboardLayoutSet keyboardLayoutSet = + KeyboardLayoutSet.createKeyboardSetForSpellChecker(this, locale.toString(), + keyboardLayoutName); + final DictionaryCollection dictionaryCollection = DictionaryFactory.createMainDictionaryFromManager(this, locale, true /* useFullEditDistance */); @@ -412,6 +429,6 @@ public final class AndroidSpellCheckerService extends SpellCheckerService mDictionaryCollectionsList.add( new WeakReference<DictionaryCollection>(dictionaryCollection)); } - return new DictAndProximity(dictionaryCollection, proximityInfo); + return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet); } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 61850e42e..16e9fb77e 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -257,7 +257,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { } if (shouldFilterOut(inText, mScript)) { - DictAndProximity dictInfo = null; + DictAndKeyboard dictInfo = null; try { dictInfo = mDictionaryPool.pollWithDefaultTimeout(); if (!DictionaryPool.isAValidDictionary(dictInfo)) { @@ -286,7 +286,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { final int capitalizeType = StringUtils.getCapitalizationType(text); boolean isInDict = true; - DictAndProximity dictInfo = null; + DictAndKeyboard dictInfo = null; try { dictInfo = mDictionaryPool.pollWithDefaultTimeout(); if (!DictionaryPool.isAValidDictionary(dictInfo)) { @@ -296,20 +296,13 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { final int length = text.length(); for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) { final int codePoint = text.codePointAt(i); - // The getXYForCodePointAndScript method returns (Y << 16) + X - final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript( - codePoint, mScript); - if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) { - composer.add(codePoint, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } else { - composer.add(codePoint, xy & 0xFFFF, xy >> 16); - } + composer.addKeyInfo(codePoint, dictInfo.getKeyboard(codePoint)); } // TODO: make a spell checker option to block offensive words or not final ArrayList<SuggestedWordInfo> suggestions = dictInfo.mDictionary.getSuggestions(composer, prevWord, - dictInfo.mProximityInfo, true /* blockOffensiveWords */); + dictInfo.getProximityInfo(), + true /* blockOffensiveWords */); for (final SuggestedWordInfo suggestion : suggestions) { final String suggestionStr = suggestion.mWord; suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0, diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java new file mode 100644 index 000000000..b77f3e2c5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 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.spellcheck; + +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; +import com.android.inputmethod.keyboard.ProximityInfo; + +/** + * A container for a Dictionary and a Keyboard. + */ +public final class DictAndKeyboard { + public final Dictionary mDictionary; + private final Keyboard mKeyboard; + private final Keyboard mManualShiftedKeyboard; + + public DictAndKeyboard( + final Dictionary dictionary, final KeyboardLayoutSet keyboardLayoutSet) { + mDictionary = dictionary; + if (keyboardLayoutSet == null) { + mKeyboard = null; + mManualShiftedKeyboard = null; + return; + } + mKeyboard = keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); + mManualShiftedKeyboard = + keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED); + } + + public Keyboard getKeyboard(final int codePoint) { + if (mKeyboard == null) { + return null; + } + return mKeyboard.getKey(codePoint) != null ? mKeyboard : mManualShiftedKeyboard; + } + + public ProximityInfo getProximityInfo() { + return mKeyboard == null ? null : mKeyboard.getProximityInfo(); + } +} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java deleted file mode 100644 index 017a4f555..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2011 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.spellcheck; - -import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.keyboard.ProximityInfo; - -/** - * A simple container for both a Dictionary and a ProximityInfo. - */ -public final class DictAndProximity { - public final Dictionary mDictionary; - public final ProximityInfo mProximityInfo; - public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) { - mDictionary = dictionary; - mProximityInfo = proximityInfo; - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index 27964b3c6..a20e09ee8 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit; * the client code, but may help with sloppy clients. */ @SuppressWarnings("serial") -public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { +public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> { private final static String TAG = DictionaryPool.class.getSimpleName(); // How many seconds we wait for a dictionary to become available. Past this delay, we give up in // fear some bug caused a deadlock, and reset the whole pool. @@ -47,7 +47,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> private int mSize; private volatile boolean mClosed; final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList(); - private final static DictAndProximity dummyDict = new DictAndProximity( + private final static DictAndKeyboard dummyDict = new DictAndKeyboard( new Dictionary(Dictionary.TYPE_MAIN) { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, @@ -64,7 +64,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> } }, null); - static public boolean isAValidDictionary(final DictAndProximity dictInfo) { + static public boolean isAValidDictionary(final DictAndKeyboard dictInfo) { return null != dictInfo && dummyDict != dictInfo; } @@ -79,32 +79,32 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> } @Override - public DictAndProximity poll(final long timeout, final TimeUnit unit) + public DictAndKeyboard poll(final long timeout, final TimeUnit unit) throws InterruptedException { - final DictAndProximity dict = poll(); + final DictAndKeyboard dict = poll(); if (null != dict) return dict; synchronized(this) { if (mSize >= mMaxSize) { // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT // expires to avoid a deadlock. - final DictAndProximity result = super.poll(timeout, unit); + final DictAndKeyboard result = super.poll(timeout, unit); if (null == result) { Log.e(TAG, "Deadlock detected ! Resetting dictionary pool"); clear(); mSize = 1; - return mService.createDictAndProximity(mLocale); + return mService.createDictAndKeyboard(mLocale); } else { return result; } } else { ++mSize; - return mService.createDictAndProximity(mLocale); + return mService.createDictAndKeyboard(mLocale); } } } // Convenience method - public DictAndProximity pollWithDefaultTimeout() { + public DictAndKeyboard pollWithDefaultTimeout() { try { return poll(TIMEOUT, TimeUnit.SECONDS); } catch (InterruptedException e) { @@ -115,7 +115,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> public void close() { synchronized(this) { mClosed = true; - for (DictAndProximity dict : this) { + for (DictAndKeyboard dict : this) { dict.mDictionary.close(); } clear(); @@ -123,7 +123,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> } @Override - public boolean offer(final DictAndProximity dict) { + public boolean offer(final DictAndKeyboard dict) { if (mClosed) { dict.mDictionary.close(); return super.offer(dummyDict); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java deleted file mode 100644 index 0c480eaba..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2011 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.spellcheck; - -import android.util.SparseIntArray; - -import com.android.inputmethod.keyboard.ProximityInfo; -import com.android.inputmethod.latin.Constants; - -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. - 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. - private static final int PROXIMITY_GRID_WIDTH = 11; - // The number of rows in the grid used by the spell checker. - private static final int PROXIMITY_GRID_HEIGHT = 3; - - private static final int NOT_AN_INDEX = -1; - public static final int NOT_A_COORDINATE_PAIR = -1; - - // Helper methods - 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); - } - } - - private static final class Latin { - // The proximity here is the union of - // - the proximity for a QWERTY keyboard. - // - the proximity for an AZERTY keyboard. - // - the proximity for a QWERTZ keyboard. - // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other. - // - // The reasoning behind this construction is, almost any alphabetic text we may want - // to spell check has been entered with one of the keyboards above. Also, specifically - // to English, many spelling errors consist of the last vowel of the word being wrong - // because in English vowels tend to merge with each other in pronunciation. - /* - The Qwerty layout this represents looks like the following: - q w e r t y u i o p - a s d f g h j k l - z x c v b n m - */ - 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. - 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, - 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL, - 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'p', 'o', 'l', 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, - - // Proximity for row 2. See comment above about size. - 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, - 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'l', 'i', 'k', 'p', 'o', 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, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - // Proximity for row 3. See comment above about size. - 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL, - 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', 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, 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, 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, ROW_SIZE, INDICES); - } - } - - private static final class Cyrillic { - // TODO: The following table is solely based on the keyboard layout. Consult with Russian - // speakers on commonly misspelled words/letters. - /* - The Russian layout this represents looks like the following: - й ц у к е н г ш щ з х - ф ы в а п р о л д ж э - я ч с м и т ь б ю - - This gives us the following table: - 'й', 'ц', 'ф', 'ы', 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, - 'к', 'у', 'в', 'а', 'п', 'е', 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, 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, 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, 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, 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, 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, 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, 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, NUL, NUL, NUL, NUL, NUL, NUL, - - Using the following characters: - */ - private static final int CY_SHORT_I = '\u0439'; // й - private static final int CY_TSE = '\u0446'; // ц - private static final int CY_U = '\u0443'; // у - private static final int CY_KA = '\u043A'; // к - private static final int CY_IE = '\u0435'; // е - private static final int CY_EN = '\u043D'; // н - private static final int CY_GHE = '\u0433'; // г - private static final int CY_SHA = '\u0448'; // ш - private static final int CY_SHCHA = '\u0449'; // щ - private static final int CY_ZE = '\u0437'; // з - private static final int CY_HA = '\u0445'; // х - private static final int CY_EF = '\u0444'; // ф - private static final int CY_YERU = '\u044B'; // ы - private static final int CY_VE = '\u0432'; // в - private static final int CY_A = '\u0430'; // а - private static final int CY_PE = '\u043F'; // п - private static final int CY_ER = '\u0440'; // р - private static final int CY_O = '\u043E'; // о - private static final int CY_EL = '\u043B'; // л - private static final int CY_DE = '\u0434'; // д - private static final int CY_ZHE = '\u0436'; // ж - private static final int CY_E = '\u044D'; // э - private static final int CY_YA = '\u044F'; // я - private static final int CY_CHE = '\u0447'; // ч - private static final int CY_ES = '\u0441'; // с - private static final int CY_EM = '\u043C'; // м - private static final int CY_I = '\u0438'; // и - private static final int CY_TE = '\u0442'; // т - private static final int CY_SOFT_SIGN = '\u044C'; // ь - private static final int CY_BE = '\u0431'; // б - private static final int CY_YU = '\u044E'; // ю - 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. - CY_SHORT_I, CY_TSE, CY_EF, CY_YERU, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_TSE, CY_SHORT_I, CY_EF, CY_YERU, CY_VE, CY_U, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_U, CY_TSE, CY_YERU, CY_VE, CY_A, CY_KA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_KA, CY_U, CY_VE, CY_A, CY_PE, CY_IE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_IE, CY_KA, CY_A, CY_PE, CY_ER, CY_EN, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_EN, CY_IE, CY_PE, CY_ER, CY_O, CY_GHE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_GHE, CY_EN, CY_ER, CY_O, CY_EL, CY_SHA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_SHA, CY_GHE, CY_O, CY_EL, CY_DE, CY_SHCHA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_SHCHA, CY_SHA, CY_EL, CY_DE, CY_ZHE, CY_ZE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_ZE, CY_SHCHA, CY_DE, CY_ZHE, CY_E, CY_HA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_HA, CY_ZE, CY_ZHE, CY_E, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - // Proximity for row 2. See comment above about size. - CY_EF, CY_SHORT_I, CY_TSE, CY_YERU, CY_YA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_YERU, CY_SHORT_I, CY_TSE, CY_U, CY_EF, CY_VE, CY_YA, CY_CHE, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_VE, CY_TSE, CY_U, CY_KA, CY_YERU, CY_A, CY_YA, CY_CHE, - CY_ES, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_A, CY_U, CY_KA, CY_IE, CY_VE, CY_PE, CY_CHE, CY_ES, - CY_EM, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_PE, CY_KA, CY_IE, CY_EN, CY_A, CY_ER, CY_ES, CY_EM, - CY_I, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_ER, CY_IE, CY_EN, CY_GHE, CY_PE, CY_O, CY_EM, CY_I, - CY_TE, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_O, CY_EN, CY_GHE, CY_SHA, CY_ER, CY_EL, CY_I, CY_TE, - CY_SOFT_SIGN, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_EL, CY_GHE, CY_SHA, CY_SHCHA, CY_O, CY_DE, CY_TE, CY_SOFT_SIGN, - CY_BE, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_DE, CY_SHA, CY_SHCHA, CY_ZE, CY_EL, CY_ZHE, CY_SOFT_SIGN, CY_BE, - CY_YU, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_ZHE, CY_SHCHA, CY_ZE, CY_HA, CY_DE, CY_E, CY_BE, CY_YU, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_E, CY_ZE, CY_HA, CY_YU, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - // Proximity for row 3. See comment above about size. - CY_YA, CY_EF, CY_YERU, CY_VE, CY_CHE, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_CHE, CY_YERU, CY_VE, CY_A, CY_YA, CY_ES, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_ES, CY_VE, CY_A, CY_PE, CY_CHE, CY_EM, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_EM, CY_A, CY_PE, CY_ER, CY_ES, CY_I, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_I, CY_PE, CY_ER, CY_O, CY_EM, CY_TE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_TE, CY_ER, CY_O, CY_EL, CY_I, CY_SOFT_SIGN, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_SOFT_SIGN, CY_O, CY_EL, CY_DE, CY_TE, CY_BE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_BE, CY_EL, CY_DE, CY_ZHE, CY_SOFT_SIGN, CY_YU, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_YU, CY_DE, CY_ZHE, CY_E, CY_BE, 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, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - }; - - static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE); - - static { - buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES); - } - } - - private static final class Greek { - // TODO: The following table is solely based on the keyboard layout. Consult with Greek - // speakers on commonly misspelled words/letters. - /* - The Greek layout this represents looks like the following: - ; ς ε ρ τ υ θ ι ο π - α σ δ φ γ η ξ κ λ - ζ χ ψ ω β ν μ - - This gives us the following table: - 'ς', 'ε', 'α', 'σ', 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, 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, 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, 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, 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, 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, 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, - 'β', 'γ', 'η', 'ξ', 'ω', 'ν', 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, - - Using the following characters: - */ - private static final int GR_FINAL_SIGMA = '\u03C2'; // ς - private static final int GR_EPSILON = '\u03B5'; // ε - private static final int GR_RHO = '\u03C1'; // ρ - private static final int GR_TAU = '\u03C4'; // τ - private static final int GR_UPSILON = '\u03C5'; // υ - private static final int GR_THETA = '\u03B8'; // θ - private static final int GR_IOTA = '\u03B9'; // ι - private static final int GR_OMICRON = '\u03BF'; // ο - private static final int GR_PI = '\u03C0'; // π - private static final int GR_ALPHA = '\u03B1'; // α - private static final int GR_SIGMA = '\u03C3'; // σ - private static final int GR_DELTA = '\u03B4'; // δ - private static final int GR_PHI = '\u03C6'; // φ - private static final int GR_GAMMA = '\u03B3'; // γ - private static final int GR_ETA = '\u03B7'; // η - private static final int GR_XI = '\u03BE'; // ξ - private static final int GR_KAPPA = '\u03BA'; // κ - private static final int GR_LAMDA = '\u03BB'; // λ - private static final int GR_ZETA = '\u03B6'; // ζ - private static final int GR_CHI = '\u03C7'; // χ - private static final int GR_PSI = '\u03C8'; // ψ - private static final int GR_OMEGA = '\u03C9'; // ω - private static final int GR_BETA = '\u03B2'; // β - private static final int GR_NU = '\u03BD'; // ν - private static final int GR_MU = '\u03BC'; // μ - 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. - GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_SIGMA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_EPSILON, GR_FINAL_SIGMA, GR_RHO, GR_SIGMA, GR_DELTA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_RHO, GR_EPSILON, GR_TAU, GR_DELTA, GR_PHI, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_TAU, GR_RHO, GR_UPSILON, GR_PHI, GR_GAMMA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_UPSILON, GR_TAU, GR_THETA, GR_GAMMA, GR_ETA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_THETA, GR_UPSILON, GR_IOTA, GR_ETA, GR_XI, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_IOTA, GR_THETA, GR_OMICRON, GR_XI, GR_KAPPA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_OMICRON, GR_IOTA, GR_PI, GR_KAPPA, GR_LAMDA, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_PI, GR_OMICRON, GR_LAMDA, 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, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - GR_ALPHA, GR_FINAL_SIGMA, GR_SIGMA, GR_ZETA, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_SIGMA, GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_DELTA, GR_ZETA, GR_CHI, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_DELTA, GR_EPSILON, GR_RHO, GR_SIGMA, GR_PHI, GR_ZETA, GR_CHI, GR_PSI, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_PHI, GR_RHO, GR_TAU, GR_DELTA, GR_GAMMA, GR_CHI, GR_PSI, GR_OMEGA, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_GAMMA, GR_TAU, GR_UPSILON, GR_PHI, GR_ETA, GR_PSI, GR_OMEGA, GR_BETA, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_ETA, GR_UPSILON, GR_THETA, GR_GAMMA, GR_XI, GR_OMEGA, GR_BETA, GR_NU, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_XI, GR_THETA, GR_IOTA, GR_ETA, GR_KAPPA, GR_BETA, GR_NU, GR_MU, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_KAPPA, GR_IOTA, GR_OMICRON, GR_XI, GR_LAMDA, GR_NU, GR_MU, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_LAMDA, GR_OMICRON, GR_PI, GR_KAPPA, GR_MU, 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, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - GR_ZETA, GR_ALPHA, GR_SIGMA, GR_DELTA, GR_CHI, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_CHI, GR_SIGMA, GR_DELTA, GR_PHI, GR_ZETA, GR_PSI, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_PSI, GR_DELTA, GR_PHI, GR_GAMMA, GR_CHI, GR_OMEGA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_OMEGA, GR_PHI, GR_GAMMA, GR_ETA, GR_PSI, GR_BETA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_BETA, GR_GAMMA, GR_ETA, GR_XI, GR_OMEGA, GR_NU, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_NU, GR_ETA, GR_XI, GR_KAPPA, GR_BETA, GR_MU, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_MU, GR_XI, GR_KAPPA, GR_LAMDA, GR_NU, 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, 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, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - }; - - static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE); - - static { - buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES); - } - } - - 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); - } - } - - private static int getIndexOfCodeForScript(final int codePoint, final int script) { - switch (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); - } - } - - // Returns (Y << 16) + X to avoid creating a temporary object. This is okay because - // X and Y are limited to PROXIMITY_GRID_WIDTH resp. PROXIMITY_GRID_HEIGHT which is very - // inferior to 1 << 16 - // As an exception, this returns NOT_A_COORDINATE_PAIR if the key is not on the grid - public static int getXYForCodePointAndScript(final int codePoint, final int script) { - final int index = getIndexOfCodeForScript(codePoint, script); - if (NOT_AN_INDEX == index) return NOT_A_COORDINATE_PAIR; - final int y = index / PROXIMITY_GRID_WIDTH; - final int x = index % PROXIMITY_GRID_WIDTH; - if (y > PROXIMITY_GRID_HEIGHT) { - // Safety check, should be entirely useless - throw new RuntimeException("Wrong y coordinate in spell checker proximity"); - } - return (y << 16) + x; - } -} diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java index 5f4c44636..58c8f266c 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java @@ -74,14 +74,14 @@ public class UserDictionaryAddWordFragment extends Fragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0, + R.string.user_dict_settings_add_menu_title).setIcon(R.drawable.ic_menu_add); + actionItemAdd.setShowAsAction( + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); final MenuItem actionItemDelete = menu.add(0, OPTIONS_MENU_DELETE, 0, R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete); actionItemDelete.setShowAsAction( MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0, - R.string.user_dict_settings_delete).setIcon(R.drawable.ic_menu_add); - actionItemAdd.setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); } /** diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java index 36bc5ba49..50dda9663 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java @@ -108,6 +108,7 @@ public class UserDictionarySettings extends ListFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary); } @Override diff --git a/java/src/com/android/inputmethod/research/FixedLogBuffer.java b/java/src/com/android/inputmethod/research/FixedLogBuffer.java index 4249af544..8b64de8ae 100644 --- a/java/src/com/android/inputmethod/research/FixedLogBuffer.java +++ b/java/src/com/android/inputmethod/research/FixedLogBuffer.java @@ -65,6 +65,7 @@ public class FixedLogBuffer extends LogBuffer { final int numWordsIncoming = newLogUnit.getNumWords(); if (mNumActualWords >= mWordCapacity) { // Give subclass a chance to handle the buffer full condition by shifting out logUnits. + // TODO: Tell onBufferFull() how much space it needs to make to avoid forced eviction. onBufferFull(); // If still full, evict. if (mNumActualWords >= mWordCapacity) { @@ -119,21 +120,19 @@ public class FixedLogBuffer extends LogBuffer { /** * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed. * - * If there are less than {@code numWords} word-containing {@link LogUnit}s, shifts out - * all {@code LogUnit}s in the buffer. + * If there are less than {@code numWords} in the buffer, shifts out all {@code LogUnit}s. * - * @param numWords the minimum number of word-containing {@link LogUnit}s to shift out - * @return the number of actual {@code LogUnit}s shifted out + * @param numWords the minimum number of words in {@link LogUnit}s to shift out + * @return the number of actual words LogUnit}s shifted out */ protected int shiftOutWords(final int numWords) { - int numWordContainingLogUnitsShiftedOut = 0; - for (LogUnit logUnit = shiftOut(); logUnit != null - && numWordContainingLogUnitsShiftedOut < numWords; logUnit = shiftOut()) { - if (logUnit.hasOneOrMoreWords()) { - numWordContainingLogUnitsShiftedOut += logUnit.getNumWords(); - } - } - return numWordContainingLogUnitsShiftedOut; + int numWordsShiftedOut = 0; + do { + final LogUnit logUnit = shiftOut(); + if (logUnit == null) break; + numWordsShiftedOut += logUnit.getNumWords(); + } while (numWordsShiftedOut < numWords); + return numWordsShiftedOut; } public void shiftOutAll() { diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java index 4d60bda53..cf1388f46 100644 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ b/java/src/com/android/inputmethod/research/LogUnit.java @@ -25,6 +25,7 @@ import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -135,9 +136,11 @@ public class LogUnit { * @param researchLog where to publish the contents of this {@code LogUnit} * @param canIncludePrivateData whether the private data in this {@code LogUnit} should be * included + * + * @throws IOException if publication to the log file is not possible */ public synchronized void publishTo(final ResearchLog researchLog, - final boolean canIncludePrivateData) { + final boolean canIncludePrivateData) throws IOException { // Write out any logStatement that passes the privacy filter. final int size = mLogStatementList.size(); if (size != 0) { diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 42ef5d3b6..9aa349906 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -23,6 +23,7 @@ import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.Suggest; import com.android.inputmethod.latin.define.ProductionFlag; +import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; @@ -177,7 +178,7 @@ public abstract class MainLogBuffer extends FixedLogBuffer { return numWordsInLogUnitList == minNGramSize; } - public void shiftAndPublishAll() { + public void shiftAndPublishAll() throws IOException { final LinkedList<LogUnit> logUnits = getLogUnits(); while (!logUnits.isEmpty()) { publishLogUnitsAtFrontOfBuffer(); @@ -186,26 +187,40 @@ public abstract class MainLogBuffer extends FixedLogBuffer { @Override protected final void onBufferFull() { - publishLogUnitsAtFrontOfBuffer(); + try { + publishLogUnitsAtFrontOfBuffer(); + } catch (final IOException e) { + if (DEBUG) { + Log.w(TAG, "IOException when publishing front of LogBuffer", e); + } + } } - protected final void publishLogUnitsAtFrontOfBuffer() { + protected final void publishLogUnitsAtFrontOfBuffer() throws IOException { + // TODO: Refactor this method to require fewer passes through the LogUnits. Should really + // require only one pass. ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE); if (isSafeNGram(logUnits, N_GRAM_SIZE)) { // Good n-gram at the front of the buffer. Publish it, disclosing details. publish(logUnits, true /* canIncludePrivateData */); shiftOutWords(N_GRAM_SIZE); mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams; - } else { - // No good n-gram at front, and buffer is full. Shift out up through the first logUnit - // with associated words (or if there is none, all the existing logUnits). - logUnits.clear(); - for (LogUnit logUnit = shiftOut(); logUnit != null && !logUnit.hasOneOrMoreWords(); - logUnit = shiftOut()) { - logUnits.add(logUnit); + return; + } + // No good n-gram at front, and buffer is full. Shift out up through the first logUnit + // with associated words (or if there is none, all the existing logUnits). + logUnits.clear(); + LogUnit logUnit = shiftOut(); + while (logUnit != null) { + logUnits.add(logUnit); + final int numWords = logUnit.getNumWords(); + if (numWords > 0) { + mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWords); + break; } - publish(logUnits, false /* canIncludePrivateData */); + logUnit = shiftOut(); } + publish(logUnits, false /* canIncludePrivateData */); } /** @@ -216,18 +231,19 @@ public abstract class MainLogBuffer extends FixedLogBuffer { * @param logUnits The list of logUnits to be published. * @param canIncludePrivateData Whether the private data in the logUnits can be included in * publication. + * + * @throws IOException if publication to the log file is not possible */ protected abstract void publish(final ArrayList<LogUnit> logUnits, - final boolean canIncludePrivateData); + final boolean canIncludePrivateData) throws IOException; @Override protected int shiftOutWords(final int numWords) { - final int numWordContainingLogUnitsShiftedOut = super.shiftOutWords(numWords); - mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - - numWordContainingLogUnitsShiftedOut); + final int numWordsShiftedOut = super.shiftOutWords(numWords); + mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWordsShiftedOut); if (DEBUG) { Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample); } - return numWordContainingLogUnitsShiftedOut; + return numWordsShiftedOut; } } diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index 18bf7ba54..3e82139a6 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -25,6 +25,7 @@ import com.android.inputmethod.latin.define.ProductionFlag; import java.io.BufferedWriter; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; @@ -61,7 +62,11 @@ public class ResearchLog { /* package */ final File mFile; private final Context mContext; - private JsonWriter mJsonWriter = NULL_JSON_WRITER; + // Earlier implementations used a dummy JsonWriter that just swallowed what it was given, but + // this was tricky to do well, because JsonWriter throws an exception if it is passed more than + // one top-level object. + private JsonWriter mJsonWriter = null; + // true if at least one byte of data has been written out to the log file. This must be // remembered because JsonWriter requires that calls matching calls to beginObject and // endObject, as well as beginArray and endArray, and the file is opened lazily, only when @@ -69,26 +74,6 @@ public class ResearchLog { // could be caught, but this might suppress other errors. private boolean mHasWrittenData = false; - private static final JsonWriter NULL_JSON_WRITER = new JsonWriter( - new OutputStreamWriter(new NullOutputStream())); - private static class NullOutputStream extends OutputStream { - /** {@inheritDoc} */ - @Override - public void write(byte[] buffer, int offset, int count) { - // nop - } - - /** {@inheritDoc} */ - @Override - public void write(byte[] buffer) { - // nop - } - - @Override - public void write(int oneByte) { - } - } - public ResearchLog(final File outputFile, final Context context) { mExecutor = Executors.newSingleThreadScheduledExecutor(); mFile = outputFile; @@ -108,6 +93,7 @@ public class ResearchLog { @Override public Object call() throws Exception { try { + if (mJsonWriter == null) return null; // TODO: This is necessary to avoid an exception. Better would be to not even // open the JsonWriter if the file is not even opened unless there is valid data // to write. @@ -119,9 +105,9 @@ public class ResearchLog { mJsonWriter.flush(); mJsonWriter.close(); if (DEBUG) { - Log.d(TAG, "wrote log to " + mFile); + Log.d(TAG, "closed " + mFile); } - } catch (Exception e) { + } catch (final Exception e) { Log.d(TAG, "error when closing ResearchLog:", e); } finally { // Marking the file as read-only signals that this log file is ready to be @@ -162,6 +148,7 @@ public class ResearchLog { @Override public Object call() throws Exception { try { + if (mJsonWriter == null) return null; if (mHasWrittenData) { // TODO: This is necessary to avoid an exception. Better would be to not // even open the JsonWriter if the file is not even opened unless there is @@ -217,7 +204,7 @@ public class ResearchLog { private final Callable<Object> mFlushCallable = new Callable<Object>() { @Override public Object call() throws Exception { - mJsonWriter.flush(); + if (mJsonWriter != null) mJsonWriter.flush(); return null; } }; @@ -263,30 +250,29 @@ public class ResearchLog { /** * Return a JsonWriter for this ResearchLog. It is initialized the first time this method is * called. The cached value is returned in future calls. + * + * @throws IOException if opening the JsonWriter is not possible */ - public JsonWriter getInitializedJsonWriterLocked() { - if (mJsonWriter != NULL_JSON_WRITER || mFile == null) return mJsonWriter; + public JsonWriter getInitializedJsonWriterLocked() throws IOException { + if (mJsonWriter != null) return mJsonWriter; + if (mFile == null) throw new FileNotFoundException(); try { final JsonWriter jsonWriter = createJsonWriter(mContext, mFile); - if (jsonWriter != null) { - jsonWriter.beginArray(); - mJsonWriter = jsonWriter; - mHasWrittenData = true; - } + if (jsonWriter == null) throw new IOException("Could not create JsonWriter"); + + jsonWriter.beginArray(); + mJsonWriter = jsonWriter; + mHasWrittenData = true; + return mJsonWriter; } catch (final IOException e) { - Log.w(TAG, "Error in JsonWriter; disabling logging", e); - try { - mJsonWriter.close(); - } catch (final IllegalStateException e1) { - // Assume that this is just the json not being terminated properly. - // Ignore - } catch (final IOException e1) { - Log.w(TAG, "Error in closing JsonWriter; disabling logging", e1); - } finally { - mJsonWriter = NULL_JSON_WRITER; + if (DEBUG) { + Log.w(TAG, "Exception when creating JsonWriter", e); + Log.w(TAG, "Closing JsonWriter"); } + if (mJsonWriter != null) mJsonWriter.close(); + mJsonWriter = null; + throw e; } - return mJsonWriter; } /** diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 1f6845c8b..8b8ea21e9 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -118,7 +118,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private static final boolean FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD = false; /* package */ static boolean sIsLogging = false; private static final int OUTPUT_FORMAT_VERSION = 5; - private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; // Whether all words should be recorded, leaving unsampled word between bigrams. Useful for // testing. /* package for test */ static final boolean IS_LOGGING_EVERYTHING = false @@ -150,24 +149,18 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private static final ResearchLogger sInstance = new ResearchLogger(); private static String sAccountType = null; private static String sAllowedAccountDomain = null; - /* package */ ResearchLog mMainResearchLog; + private ResearchLog mMainResearchLog; // always non-null after init() is called // mFeedbackLog records all events for the session, private or not (excepting // passwords). It is written to permanent storage only if the user explicitly commands // the system to do so. // LogUnits are queued in the LogBuffers and published to the ResearchLogs when words are // complete. - /* package */ MainLogBuffer mMainLogBuffer; - // TODO: Remove the feedback log. The feedback log continuously captured user data in case the - // user wanted to submit it. We now use the mUserRecordingLogBuffer to allow the user to - // explicitly reproduce a problem. - /* package */ ResearchLog mFeedbackLog; - /* package */ LogBuffer mFeedbackLogBuffer; + /* package for test */ MainLogBuffer mMainLogBuffer; // always non-null after init() is called /* package */ ResearchLog mUserRecordingLog; /* package */ LogBuffer mUserRecordingLogBuffer; private File mUserRecordingFile = null; private boolean mIsPasswordView = false; - private boolean mIsLoggingSuspended = false; private SharedPreferences mPrefs; // digits entered by the user are replaced with this codepoint. @@ -202,15 +195,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private long mSavedDownEventTime; private Bundle mFeedbackDialogBundle = null; private boolean mInFeedbackDialog = false; - // The feedback dialog causes stop() to be called for the keyboard connected to the original - // window. This is because the feedback dialog must present its own EditText box that displays - // a keyboard. stop() normally causes mFeedbackLogBuffer, which contains the user's data, to be - // cleared, and causes mFeedbackLog, which is ready to collect information in case the user - // wants to upload, to be closed. This is good because we don't need to log information about - // what the user is typing in the feedback dialog, but bad because this data must be uploaded. - // Here we save the LogBuffer and Log so the feedback dialog can later access their data. - private LogBuffer mSavedFeedbackLogBuffer; - private ResearchLog mSavedFeedbackLog; private Handler mUserRecordingTimeoutHandler; private static final long USER_RECORDING_TIMEOUT_MS = 30L * DateUtils.SECOND_IN_MILLIS; @@ -241,6 +225,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mResearchLogDirectory = new ResearchLogDirectory(mLatinIME); cleanLogDirectoryIfNeeded(mResearchLogDirectory, System.currentTimeMillis()); + // Initialize log buffers + resetLogBuffers(); + // Initialize external services mUploadIntent = new Intent(mLatinIME, UploaderService.class); mUploadNowIntent = new Intent(mLatinIME, UploaderService.class); @@ -252,6 +239,35 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mReplayer.setKeyboardSwitcher(keyboardSwitcher); } + private void resetLogBuffers() { + mMainResearchLog = new ResearchLog(mResearchLogDirectory.getLogFilePath( + System.currentTimeMillis(), System.nanoTime()), mLatinIME); + final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1); + mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore, + mSuggest) { + @Override + protected void publish(final ArrayList<LogUnit> logUnits, + boolean canIncludePrivateData) { + canIncludePrivateData |= IS_LOGGING_EVERYTHING; + for (final LogUnit logUnit : logUnits) { + if (DEBUG) { + final String wordsString = logUnit.getWordsAsString(); + Log.d(TAG, "onPublish: '" + wordsString + + "', hc: " + logUnit.containsCorrection() + + ", cipd: " + canIncludePrivateData); + } + for (final String word : logUnit.getWordsAsStringArray()) { + final Dictionary dictionary = getDictionary(); + mStatistics.recordWordEntered( + dictionary != null && dictionary.isValidWord(word), + logUnit.containsCorrection()); + } + } + publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData); + } + }; + } + private void cleanLogDirectoryIfNeeded(final ResearchLogDirectory researchLogDirectory, final long now) { final long lastCleanupTime = ResearchSettings.readResearchLastDirCleanupTime(mPrefs); @@ -376,53 +392,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang Log.d(TAG, "start called"); } maybeShowSplashScreen(); - updateSuspendedState(); requestIndicatorRedraw(); mStatistics.reset(); checkForEmptyEditor(); - if (mFeedbackLogBuffer == null) { - resetFeedbackLogging(); - } - if (!isAllowedToLog()) { - // Log.w(TAG, "not in usability mode; not logging"); - return; - } - if (mMainLogBuffer == null) { - mMainResearchLog = new ResearchLog(mResearchLogDirectory.getLogFilePath( - System.currentTimeMillis(), System.nanoTime()), mLatinIME); - final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1); - mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore, - mSuggest) { - @Override - protected void publish(final ArrayList<LogUnit> logUnits, - boolean canIncludePrivateData) { - canIncludePrivateData |= IS_LOGGING_EVERYTHING; - for (final LogUnit logUnit : logUnits) { - if (DEBUG) { - final String wordsString = logUnit.getWordsAsString(); - Log.d(TAG, "onPublish: '" + wordsString - + "', hc: " + logUnit.containsCorrection() - + ", cipd: " + canIncludePrivateData); - } - for (final String word : logUnit.getWordsAsStringArray()) { - final Dictionary dictionary = getDictionary(); - mStatistics.recordWordEntered( - dictionary != null && dictionary.isValidWord(word), - logUnit.containsCorrection()); - } - } - if (mMainResearchLog != null) { - publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData); - } - } - }; - } - } - - private void resetFeedbackLogging() { - mFeedbackLog = new ResearchLog(mResearchLogDirectory.getLogFilePath( - System.currentTimeMillis(), System.nanoTime()), mLatinIME); - mFeedbackLogBuffer = new FixedLogBuffer(FEEDBACK_WORD_BUFFER_SIZE); } /* package */ void stop() { @@ -432,35 +404,32 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // Commit mCurrentLogUnit before closing. commitCurrentLogUnit(); - if (mMainLogBuffer != null) { - mMainLogBuffer.shiftAndPublishAll(); - logStatistics(); - commitCurrentLogUnit(); - mMainLogBuffer.setIsStopping(); + try { mMainLogBuffer.shiftAndPublishAll(); - mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); - mMainLogBuffer = null; + } catch (final IOException e) { + Log.w(TAG, "IOException when publishing LogBuffer", e); } - if (mFeedbackLogBuffer != null) { - mFeedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); - mFeedbackLogBuffer = null; + logStatistics(); + commitCurrentLogUnit(); + mMainLogBuffer.setIsStopping(); + try { + mMainLogBuffer.shiftAndPublishAll(); + } catch (final IOException e) { + Log.w(TAG, "IOException when publishing LogBuffer", e); } + mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); + + resetLogBuffers(); } public void abort() { if (DEBUG) { Log.d(TAG, "abort called"); } - if (mMainLogBuffer != null) { - mMainLogBuffer.clear(); - mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); - mMainLogBuffer = null; - } - if (mFeedbackLogBuffer != null) { - mFeedbackLogBuffer.clear(); - mFeedbackLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); - mFeedbackLogBuffer = null; - } + mMainLogBuffer.clear(); + mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); + + resetLogBuffers(); } private void restart() { @@ -468,23 +437,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang start(); } - private long mResumeTime = 0L; - private void updateSuspendedState() { - final long time = System.currentTimeMillis(); - if (time > mResumeTime) { - mIsLoggingSuspended = false; - } - } - @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { if (key == null || prefs == null) { return; } - sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); - if (sIsLogging == false) { - abort(); - } requestIndicatorRedraw(); mPrefs = prefs; prefsChanged(prefs); @@ -504,12 +461,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang saveRecording(); } mInFeedbackDialog = true; - mSavedFeedbackLogBuffer = mFeedbackLogBuffer; - mSavedFeedbackLog = mFeedbackLog; - // Set the non-saved versions to null so that the stop() caused by switching to the - // Feedback dialog will not close them. - mFeedbackLogBuffer = null; - mFeedbackLog = null; final Intent intent = new Intent(); intent.setClass(mLatinIME, FeedbackActivity.class); @@ -667,12 +618,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang new LogStatement("UserFeedback", false, false, "contents", "accountName", "recording"); public void sendFeedback(final String feedbackContents, final boolean includeHistory, final boolean isIncludingAccountName, final boolean isIncludingRecording) { - if (mSavedFeedbackLogBuffer == null) { - return; - } - if (!includeHistory) { - mSavedFeedbackLogBuffer.clear(); - } String recording = ""; if (isIncludingRecording) { // Try to read recording from recently written json file @@ -704,9 +649,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final String accountName = isIncludingAccountName ? getAccountName() : ""; feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(), feedbackContents, accountName, recording); - mFeedbackLogBuffer.shiftIn(feedbackLogUnit); - publishLogBuffer(mFeedbackLogBuffer, mSavedFeedbackLog, true /* isIncludingPrivateData */); - mSavedFeedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); + + final ResearchLog feedbackLog = new ResearchLog(mResearchLogDirectory.getLogFilePath( + System.currentTimeMillis(), System.nanoTime()), mLatinIME); + final LogBuffer feedbackLogBuffer = new LogBuffer(); + feedbackLogBuffer.shiftIn(feedbackLogUnit); + publishLogBuffer(feedbackLogBuffer, feedbackLog, true /* isIncludingPrivateData */); + feedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); uploadNow(); if (isIncludingRecording && DEBUG_REPLAY_AFTER_FEEDBACK) { @@ -745,8 +694,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public void initSuggest(final Suggest suggest) { mSuggest = suggest; - // MainLogBuffer has out-of-date Suggest object. Need to close it down and create a new - // one. + // MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create + // a new one. if (mMainLogBuffer != null) { stop(); start(); @@ -765,7 +714,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } private boolean isAllowedToLog() { - return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging && !mInFeedbackDialog; + return !mIsPasswordView && sIsLogging && !mInFeedbackDialog; } public void requestIndicatorRedraw() { @@ -857,12 +806,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang ": " + mCurrentLogUnit.getWordsAsString() : "")); } if (!mCurrentLogUnit.isEmpty()) { - if (mMainLogBuffer != null) { - mMainLogBuffer.shiftIn(mCurrentLogUnit); - } - if (mFeedbackLogBuffer != null) { - mFeedbackLogBuffer.shiftIn(mCurrentLogUnit); - } + mMainLogBuffer.shiftIn(mCurrentLogUnit); if (mUserRecordingLogBuffer != null) { mUserRecordingLogBuffer.shiftIn(mCurrentLogUnit); } @@ -887,9 +831,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // // Note that we don't use mLastLogUnit here, because it only goes one word back and is only // needed for reverts, which only happen one back. - if (mMainLogBuffer == null) { - return; - } final LogUnit oldLogUnit = mMainLogBuffer.peekLastLogUnit(); // Check that expected word matches. @@ -911,9 +852,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } else { mCurrentLogUnit = oldLogUnit; } - if (mFeedbackLogBuffer != null) { - mFeedbackLogBuffer.unshiftIn(); - } enqueueEvent(LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT); if (DEBUG) { Log.d(TAG, "uncommitCurrentLogUnit (dump=" + dumpCurrentLogUnit + ") back to " @@ -943,6 +881,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLog researchLog, final boolean canIncludePrivateData) { final LogUnit openingLogUnit = new LogUnit(); if (logUnits.isEmpty()) return; + if (!isAllowedToLog()) return; // LogUnits not containing private data, such as contextual data for the log, do not require // logSegment boundary statements. if (canIncludePrivateData) { @@ -1376,11 +1315,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static void latinIME_promotePhantomSpace() { final ResearchLogger researchLogger = getInstance(); final LogUnit logUnit; - if (researchLogger.mMainLogBuffer == null) { - logUnit = researchLogger.mCurrentLogUnit; - } else { - logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); - } + logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE); } @@ -1397,11 +1332,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final String charactersAfterSwap) { final ResearchLogger researchLogger = getInstance(); final LogUnit logUnit; - if (researchLogger.mMainLogBuffer == null) { - logUnit = null; - } else { - logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); - } + logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); if (logUnit != null) { researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE, originalCharacters, charactersAfterSwap); @@ -1474,11 +1405,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final ResearchLogger researchLogger = getInstance(); // TODO: Verify that mCurrentLogUnit has been restored and contains the reverted word. final LogUnit logUnit; - if (researchLogger.mMainLogBuffer == null) { - logUnit = null; - } else { - logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); - } + logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); if (originallyTypedWord.length() > 0 && hasLetters(originallyTypedWord)) { if (logUnit != null) { logUnit.setWords(originallyTypedWord); |