diff options
Diffstat (limited to 'java/src')
21 files changed, 475 insertions, 390 deletions
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/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 2d791648e..174239325 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -84,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 { @@ -459,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); @@ -527,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)); @@ -737,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) { @@ -753,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) { @@ -794,7 +801,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { if (mIsTrackingForActionDisabled) { return; } - mDrawingProxy.showGesturePreviewTrail(this); + mDrawingProxy.showGestureTrail(this); } private void cancelBatchInput() { @@ -1036,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(); } @@ -1168,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; @@ -1188,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; @@ -1197,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(); } } @@ -1247,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) { @@ -1303,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) { @@ -1316,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/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index ccb8802c6..b31f00b62 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -63,18 +63,18 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) { mMinSamplingDistance = mainKeyboardViewAttr.getDimension( - R.styleable.MainKeyboardView_gesturePreviewTrailMinSamplingDistance, + R.styleable.MainKeyboardView_gestureTrailMinSamplingDistance, (float)DEFAULT.mMinSamplingDistance); final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable - .MainKeyboardView_gesturePreviewTrailMaxInterpolationAngularThreshold, 0); + .MainKeyboardView_gestureTrailMaxInterpolationAngularThreshold, 0); mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0) ? DEFAULT.mMaxInterpolationAngularThreshold : degreeToRadian(interpolationAngularDegree); mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable - .MainKeyboardView_gesturePreviewTrailMaxInterpolationDistanceThreshold, + .MainKeyboardView_gestureTrailMaxInterpolationDistanceThreshold, (float)DEFAULT.mMaxInterpolationDistanceThreshold); mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger( - R.styleable.MainKeyboardView_gesturePreviewTrailMaxInterpolationSegments, + R.styleable.MainKeyboardView_gestureTrailMaxInterpolationSegments, DEFAULT.mMaxInterpolationSegments); } } @@ -145,9 +145,9 @@ 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, @@ -189,16 +189,16 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { eventTimes.add(d1, (int)(dt * t) + t1); xCoords.add(d1, (int)mInterpolator.mInterpolatedX); yCoords.add(d1, (int)mInterpolator.mInterpolatedY); - if (GesturePreviewTrail.DBG_SHOW_POINTS) { - types.add(d1, GesturePreviewTrail.POINT_TYPE_INTERPOLATED); + 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 (GesturePreviewTrail.DBG_SHOW_POINTS) { - types.add(d1, GesturePreviewTrail.POINT_TYPE_SAMPLED); + 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 761d9dcd6..03dd1c372 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java @@ -29,13 +29,13 @@ 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; @@ -70,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 = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutStartDelay, 0); + R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0); mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutDuration, 0); + R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0); mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration; mUpdateInterval = mainKeyboardViewAttr.getInt( - R.styleable.MainKeyboardView_gesturePreviewTrailUpdateInterval, 0); + R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0); } } @@ -186,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) { 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/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index fdd470cf1..347a4c63a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -90,7 +90,7 @@ import java.util.TreeSet; /** * Input method implementation for Qwerty'ish keyboard. */ -public final class LatinIME extends InputMethodService implements KeyboardActionListener, +public class LatinIME extends InputMethodService implements KeyboardActionListener, SuggestionStripView.Listener, TargetApplicationGetter.OnTargetApplicationKnownListener, Suggest.SuggestInitializationListener { private static final String TAG = LatinIME.class.getSimpleName(); @@ -188,6 +188,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Keeps track of most recently inserted text (multi-character key) for reverting private String mEnteredText; + // TODO: This boolean is persistent state and causes large side effects at unexpected times. + // Find a way to remove it for readability. private boolean mIsAutoCorrectionIndicatorOn; private AlertDialog mOptionsDialog; @@ -902,7 +904,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // we know for sure the cursor moved while we were composing and we should reset // the state. final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1; - if (!mExpectingUpdateSelection + // If the keyboard is not visible, we don't need to do all the housekeeping work, as it + // will be reset when the keyboard shows up anyway. + // TODO: revisit this when LatinIME supports hardware keyboards. + // NOTE: the test harness subclasses LatinIME and overrides isInputViewShown(). + // TODO: find a better way to simulate actual execution. + if (isInputViewShown() && !mExpectingUpdateSelection && !mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart)) { // TAKE CARE: there is a race condition when we enter this test even when the user // did not explicitly move the cursor. This happens when typing fast, where two keys @@ -1752,9 +1759,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 @@ -2500,7 +2514,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Note that it's very important here that suggestedWords.mWillAutoCorrect is false. // We never want to auto-correct on a resumed suggestion. Please refer to the three - // places above where suggestedWords is affected. + // places above where suggestedWords is affected. We also need to reset + // mIsAutoCorrectionIndicatorOn to avoid showSuggestionStrip touching the text to adapt it. + // TODO: remove mIsAutoCorrectionIndicator on (see comment on definition) + mIsAutoCorrectionIndicatorOn = false; showSuggestionStrip(suggestedWords, typedWord); } @@ -2612,8 +2629,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/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/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java index 3406ecf34..78a6478c6 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java @@ -46,11 +46,14 @@ import java.util.ArrayList; public final class SetupWizardActivity extends Activity implements View.OnClickListener { static final String TAG = SetupWizardActivity.class.getSimpleName(); + private static final boolean ENABLE_WELCOME_VIDEO = true; + private View mSetupWizard; private View mWelcomeScreen; private View mSetupScreen; private Uri mWelcomeVideoUri; private VideoView mWelcomeVideoView; + private ImageView mWelcomeImageView; private View mActionStart; private View mActionNext; private TextView mStep1Bullet; @@ -58,6 +61,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL private SetupStepGroup mSetupStepGroup; private static final String STATE_STEP = "step"; private int mStepNumber; + private boolean mNeedsToAdjustStepNumberToSystemState; private static final int STEP_WELCOME = 0; private static final int STEP_1 = 1; private static final int STEP_2 = 2; @@ -156,9 +160,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL step2.setAction(new Runnable() { @Override public void run() { - // Invoke input method picker. - RichInputMethodManager.getInstance().getInputMethodManager() - .showInputMethodPicker(); + invokeInputMethodPicker(); } }); mSetupStepGroup.addStep(step2); @@ -191,18 +193,16 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL 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); + hideWelcomeVideoAndShowWelcomeImage(); return true; } }); mWelcomeVideoView = welcomeVideoView; + mWelcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image); mActionStart = findViewById(R.id.setup_start_label); mActionStart.setOnClickListener(this); @@ -244,6 +244,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); + mNeedsToAdjustStepNumberToSystemState = true; } private void invokeSettingsOfThisIme() { @@ -259,6 +260,14 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); startActivity(intent); + mNeedsToAdjustStepNumberToSystemState = true; + } + + void invokeInputMethodPicker() { + // Invoke input method picker. + RichInputMethodManager.getInstance().getInputMethodManager() + .showInputMethodPicker(); + mNeedsToAdjustStepNumberToSystemState = true; } void invokeSubtypeEnablerOfThisIme() { @@ -312,6 +321,9 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL @Override protected void onRestart() { super.onRestart(); + // Probably the setup wizard has been invoked from "Recent" menu. The setup step number + // needs to be adjusted to system state, because the state (IME is enabled and/or current) + // may have been changed. if (isInSetupSteps(mStepNumber)) { mStepNumber = determineSetupStepNumber(); } @@ -344,21 +356,34 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL super.onBackPressed(); } - private static void hideAndStopVideo(final VideoView videoView) { - videoView.stopPlayback(); - videoView.setVisibility(View.INVISIBLE); + void hideWelcomeVideoAndShowWelcomeImage() { + mWelcomeVideoView.setVisibility(View.GONE); + mWelcomeImageView.setImageResource(R.raw.setup_welcome_image); + mWelcomeImageView.setVisibility(View.VISIBLE); + } + + private void showAndStartWelcomeVideo() { + mWelcomeVideoView.setVisibility(View.VISIBLE); + mWelcomeVideoView.setVideoURI(mWelcomeVideoUri); + mWelcomeVideoView.start(); + } + + private void hideAndStopWelcomeVideo() { + mWelcomeVideoView.stopPlayback(); + mWelcomeVideoView.setVisibility(View.GONE); } @Override protected void onPause() { - hideAndStopVideo(mWelcomeVideoView); + hideAndStopWelcomeVideo(); super.onPause(); } @Override public void onWindowFocusChanged(final boolean hasFocus) { super.onWindowFocusChanged(hasFocus); - if (hasFocus && isInSetupSteps(mStepNumber)) { + if (hasFocus && mNeedsToAdjustStepNumberToSystemState) { + mNeedsToAdjustStepNumberToSystemState = false; mStepNumber = determineSetupStepNumber(); updateSetupStepView(); } @@ -370,12 +395,14 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL 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(); + if (ENABLE_WELCOME_VIDEO) { + showAndStartWelcomeVideo(); + } else { + hideWelcomeVideoAndShowWelcomeImage(); + } return; } - hideAndStopVideo(mWelcomeVideoView); + hideAndStopWelcomeVideo(); final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber(); mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone); mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE); 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 9bdedbf6d..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,10 +187,16 @@ 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); @@ -224,9 +231,11 @@ 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) { 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); diff --git a/java/src/com/android/inputmethod/research/Uploader.java b/java/src/com/android/inputmethod/research/Uploader.java index ba05ec12b..c7ea3e69d 100644 --- a/java/src/com/android/inputmethod/research/Uploader.java +++ b/java/src/com/android/inputmethod/research/Uploader.java @@ -49,7 +49,7 @@ public final class Uploader { private static final boolean DEBUG = false && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; // Set IS_INHIBITING_AUTO_UPLOAD to true for local testing - private static final boolean IS_INHIBITING_AUTO_UPLOAD = false + private static final boolean IS_INHIBITING_UPLOAD = false && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; private static final int BUF_SIZE = 1024 * 8; @@ -76,7 +76,7 @@ public final class Uploader { } public boolean isPossibleToUpload() { - return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_AUTO_UPLOAD; + return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_UPLOAD; } private boolean hasUploadingPermission() { |