diff options
Diffstat (limited to 'java/src')
32 files changed, 1795 insertions, 813 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java index f6376d5f4..4ecbf827a 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java @@ -29,7 +29,7 @@ import android.view.ViewConfiguration; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.LatinKeyboardView; +import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.latin.R; @@ -37,7 +37,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy(); private InputMethodService mInputMethod; - private LatinKeyboardView mView; + private MainKeyboardView mView; private AccessibilityEntityProvider mAccessibilityNodeProvider; private Key mLastHoverKey = null; @@ -70,7 +70,7 @@ public class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat { * * @param view The view to wrap. */ - public void setView(LatinKeyboardView view) { + public void setView(MainKeyboardView view) { if (view == null) { // Ignore null views. return; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java index 1f3ee7680..b1621a55b 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -17,7 +17,6 @@ package com.android.inputmethod.keyboard; import com.android.inputmethod.latin.InputPointers; -import com.android.inputmethod.latin.SuggestedWords; public interface KeyboardActionListener { @@ -73,18 +72,17 @@ public interface KeyboardActionListener { public void onStartBatchInput(); /** - * Sends the batch input points data to get updated suggestions + * Sends the ongoing batch input points data. * @param batchPointers the batch input points representing the user input - * @return updated suggestions that reflects the user input */ - public SuggestedWords onUpdateBatchInput(InputPointers batchPointers); + public void onUpdateBatchInput(InputPointers batchPointers); /** - * Sends a sequence of characters to the listener as batch input. + * Sends the final batch input points data. * - * @param text the sequence of characters to be displayed as composing text. + * @param batchPointers the batch input points representing the user input */ - public void onEndBatchInput(CharSequence text); + public void onEndBatchInput(InputPointers batchPointers); /** * Called when user released a finger outside any key. @@ -109,9 +107,9 @@ public interface KeyboardActionListener { @Override public void onStartBatchInput() {} @Override - public SuggestedWords onUpdateBatchInput(InputPointers batchPointers) { return null; } + public void onUpdateBatchInput(InputPointers batchPointers) {} @Override - public void onEndBatchInput(CharSequence text) {} + public void onEndBatchInput(InputPointers batchPointers) {} @Override public void onCancelInput() {} @Override diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 2e4ce199e..d637ab5f1 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -61,7 +61,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { new KeyboardTheme("Basic", 0, R.style.KeyboardTheme), new KeyboardTheme("HighContrast", 1, R.style.KeyboardTheme_HighContrast), new KeyboardTheme("Stone", 6, R.style.KeyboardTheme_Stone), - new KeyboardTheme("Stne.Bold", 7, R.style.KeyboardTheme_Stone_Bold), + new KeyboardTheme("Stone.Bold", 7, R.style.KeyboardTheme_Stone_Bold), new KeyboardTheme("GingerBread", 8, R.style.KeyboardTheme_Gingerbread), new KeyboardTheme("IceCreamSandwich", 5, R.style.KeyboardTheme_IceCreamSandwich), }; @@ -71,9 +71,10 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { private boolean mForceNonDistinctMultitouch; private InputView mCurrentInputView; - private LatinKeyboardView mKeyboardView; + private MainKeyboardView mKeyboardView; private LatinIME mLatinIME; private Resources mResources; + private SettingsValues mCurrentSettingsValues; private KeyboardState mState; @@ -135,6 +136,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { } public void loadKeyboard(EditorInfo editorInfo, SettingsValues settingsValues) { + mCurrentSettingsValues = settingsValues; final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( mThemeContext, editorInfo); builder.setScreenGeometry(mThemeContext.getResources().getConfiguration().orientation, @@ -170,6 +172,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { private void setKeyboard(final Keyboard keyboard) { final Keyboard oldKeyboard = mKeyboardView.getKeyboard(); + mKeyboardView.setGestureInputEnabled(mCurrentSettingsValues.mGestureInputEnabled); mKeyboardView.setKeyboard(keyboard); mCurrentInputView.setKeyboardGeometry(keyboard.mTopPadding); mKeyboardView.setKeyPreviewPopupEnabled( @@ -265,7 +268,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void startDoubleTapTimer() { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); if (keyboardView != null) { final TimerProxy timer = keyboardView.getTimerProxy(); timer.startDoubleTapTimer(); @@ -275,7 +278,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void cancelDoubleTapTimer() { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); if (keyboardView != null) { final TimerProxy timer = keyboardView.getTimerProxy(); timer.cancelDoubleTapTimer(); @@ -285,7 +288,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public boolean isInDoubleTapTimeout() { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); return (keyboardView != null) ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false; } @@ -293,7 +296,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void startLongPressTimer(int code) { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); if (keyboardView != null) { final TimerProxy timer = keyboardView.getTimerProxy(); timer.startLongPressTimer(code); @@ -303,7 +306,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void cancelLongPressTimer() { - final LatinKeyboardView keyboardView = getKeyboardView(); + final MainKeyboardView keyboardView = getKeyboardView(); if (keyboardView != null) { final TimerProxy timer = keyboardView.getTimerProxy(); timer.cancelLongPressTimer(); @@ -343,7 +346,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { mState.onCodeInput(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState()); } - public LatinKeyboardView getKeyboardView() { + public MainKeyboardView getKeyboardView() { return mKeyboardView; } @@ -369,7 +372,7 @@ public class KeyboardSwitcher implements KeyboardState.SwitchActions { } } - mKeyboardView = (LatinKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); + mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view); mKeyboardView.setKeyboardActionListener(mLatinIME); if (mForceNonDistinctMultitouch) { mKeyboardView.setDistinctMultitouch(false); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index fb98af3e6..f751fa53c 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -38,10 +38,13 @@ import android.view.ViewGroup; import android.widget.RelativeLayout; import android.widget.TextView; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; import java.util.HashSet; @@ -94,7 +97,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // The maximum key label width in the proportion to the key width. private static final float MAX_LABEL_RATIO = 0.90f; - private final static int ALPHA_OPAQUE = 255; + private final static int GESTURE_DRAWING_WIDTH = 5; + private final static int GESTURE_DRAWING_COLOR = 0xff33b5e5; // Main keyboard private Keyboard mKeyboard; @@ -107,6 +111,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { private int mDelayAfterPreview; private ViewGroup mPreviewPlacer; + /** True if the gesture input is enabled. */ + protected boolean mGestureInputEnabled; + // Drawing /** True if the entire keyboard needs to be dimmed. */ private boolean mNeedsToDimEntireKeyboard; @@ -118,11 +125,14 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { private final HashSet<Key> mInvalidatedKeys = new HashSet<Key>(); /** The region of invalidated keys */ private final Rect mInvalidatedKeysRect = new Rect(); + /** The region of invalidated gestures */ + private final Rect mInvalidatedGesturesRect = new Rect(); /** The keyboard bitmap buffer for faster updates */ private Bitmap mBuffer; /** The canvas for the above mutable keyboard bitmap */ private Canvas mCanvas; private final Paint mPaint = new Paint(); + private final Paint mGesturePaint = new Paint(); private final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics(); // This sparse array caches key label text height in pixel indexed by key label text size. private static final SparseArray<Float> sTextHeightCache = new SparseArray<Float>(); @@ -264,7 +274,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { public void blendAlpha(Paint paint) { final int color = paint.getColor(); - paint.setARGB((paint.getAlpha() * mAnimAlpha) / ALPHA_OPAQUE, + paint.setARGB((paint.getAlpha() * mAnimAlpha) / Constants.Color.ALPHA_OPAQUE, Color.red(color), Color.green(color), Color.blue(color)); } } @@ -372,6 +382,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { mDelayAfterPreview = mKeyPreviewDrawParams.mLingerTimeout; mPaint.setAntiAlias(true); + + // TODO: These paint parameters should be specified via attribute of the view and styleable. + mGesturePaint.setAntiAlias(true); + mGesturePaint.setStyle(Paint.Style.STROKE); + mGesturePaint.setStrokeJoin(Paint.Join.ROUND); + mGesturePaint.setColor(GESTURE_DRAWING_COLOR); + mGesturePaint.setStrokeWidth(GESTURE_DRAWING_WIDTH); } // Read fraction value in TypedArray as float. @@ -426,6 +443,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { return mShowKeyPreviewPopup; } + public void setGestureInputEnabled(boolean gestureInputEnabled) { + mGestureInputEnabled = gestureInputEnabled; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mKeyboard != null) { @@ -499,6 +520,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } } + // ResearchLogging indicator. + // TODO: Reimplement using a keyboard background image specific to the ResearchLogger, + // and remove this call. + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height); + } + mInvalidatedKeys.clear(); mInvalidatedKeysRect.setEmpty(); mInvalidateAllKeys = false; @@ -517,7 +545,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { final int keyDrawY = key.mY + getPaddingTop(); canvas.translate(keyDrawX, keyDrawY); - params.mAnimAlpha = ALPHA_OPAQUE; + params.mAnimAlpha = Constants.Color.ALPHA_OPAQUE; if (!key.isSpacer()) { onDrawKeyBackground(key, canvas, params); } @@ -860,17 +888,60 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker); } + private static class PreviewView extends RelativeLayout { + KeyPreviewDrawParams mParams; + Paint mGesturePaint; + + public PreviewView(Context context, KeyPreviewDrawParams params, Paint gesturePaint) { + super(context); + setWillNotDraw(false); + mParams = params; + mGesturePaint = gesturePaint; + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.translate(mParams.mCoordinates[0], mParams.mCoordinates[1]); + PointerTracker.drawGestureTrailForAllPointerTrackers(canvas, mGesturePaint); + canvas.translate(-mParams.mCoordinates[0], -mParams.mCoordinates[1]); + } + } + private void addKeyPreview(TextView keyPreview) { if (mPreviewPlacer == null) { - mPreviewPlacer = new RelativeLayout(getContext()); - final ViewGroup windowContentView = - (ViewGroup)getRootView().findViewById(android.R.id.content); - windowContentView.addView(mPreviewPlacer); + createPreviewPlacer(); } mPreviewPlacer.addView( keyPreview, ViewLayoutUtils.newLayoutParam(mPreviewPlacer, 0, 0)); } + private void createPreviewPlacer() { + mPreviewPlacer = new PreviewView(getContext(), mKeyPreviewDrawParams, mGesturePaint); + final ViewGroup windowContentView = + (ViewGroup)getRootView().findViewById(android.R.id.content); + windowContentView.addView(mPreviewPlacer); + } + + @Override + public void showGestureTrail(PointerTracker tracker) { + if (mPreviewPlacer == null) { + createPreviewPlacer(); + } + final Rect r = tracker.getBoundingBox(); + if (!r.isEmpty()) { + // Invalidate the rectangular region encompassing the gesture. This is needed because + // past points along the gesture will fade and gradually disappear. + final KeyPreviewDrawParams params = mKeyPreviewDrawParams; + mInvalidatedGesturesRect.set(r); + mInvalidatedGesturesRect.offset(params.mCoordinates[0], params.mCoordinates[1]); + mInvalidatedGesturesRect.inset(-GESTURE_DRAWING_WIDTH, -GESTURE_DRAWING_WIDTH); + mPreviewPlacer.invalidate(mInvalidatedGesturesRect); + } else { + mPreviewPlacer.invalidate(); + } + } + @SuppressWarnings("deprecation") // setBackgroundDrawable is replaced by setBackground in API16 @Override public void showKeyPreview(PointerTracker tracker) { diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 7714ba892..8c234e4e6 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -43,16 +43,17 @@ import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResearchLogger; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.SubtypeLocale; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; import java.util.Locale; import java.util.WeakHashMap; @@ -64,9 +65,9 @@ import java.util.WeakHashMap; * @attr ref R.styleable#KeyboardView_verticalCorrection * @attr ref R.styleable#KeyboardView_popupLayout */ -public class LatinKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, +public class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, SuddenJumpingTouchEventHandler.ProcessMotionEvent { - private static final String TAG = LatinKeyboardView.class.getSimpleName(); + private static final String TAG = MainKeyboardView.class.getSimpleName(); // TODO: Kill process when the usability study mode was changed. private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy; @@ -80,10 +81,9 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke // Stuff to draw language name on spacebar. private final int mLanguageOnSpacebarFinalAlpha; private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator; - private static final int ALPHA_OPAQUE = 255; private boolean mNeedsToDisplayLanguage; private boolean mHasMultipleEnabledIMEsOrSubtypes; - private int mLanguageOnSpacebarAnimAlpha = ALPHA_OPAQUE; + private int mLanguageOnSpacebarAnimAlpha = Constants.Color.ALPHA_OPAQUE; private final float mSpacebarTextRatio; private float mSpacebarTextSize; private final int mSpacebarTextColor; @@ -99,7 +99,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke // Stuff to draw altCodeWhileTyping keys. private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator; private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator; - private int mAltCodeKeyWhileTypingAnimAlpha = ALPHA_OPAQUE; + private int mAltCodeKeyWhileTypingAnimAlpha = Constants.Color.ALPHA_OPAQUE; // More keys keyboard private PopupWindow mMoreKeysWindow; @@ -119,7 +119,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke private final KeyTimerHandler mKeyTimerHandler; - private static class KeyTimerHandler extends StaticInnerHandlerWrapper<LatinKeyboardView> + private static class KeyTimerHandler extends StaticInnerHandlerWrapper<MainKeyboardView> implements TimerProxy { private static final int MSG_REPEAT_KEY = 1; private static final int MSG_LONGPRESS_KEY = 2; @@ -128,14 +128,14 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke private final KeyTimerParams mParams; - public KeyTimerHandler(LatinKeyboardView outerInstance, KeyTimerParams params) { + public KeyTimerHandler(MainKeyboardView outerInstance, KeyTimerParams params) { super(outerInstance); mParams = params; } @Override public void handleMessage(Message msg) { - final LatinKeyboardView keyboardView = getOuterInstance(); + final MainKeyboardView keyboardView = getOuterInstance(); final PointerTracker tracker = (PointerTracker) msg.obj; switch (msg.what) { case MSG_REPEAT_KEY: @@ -249,7 +249,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke if (isTyping) { return; } - final LatinKeyboardView keyboardView = getOuterInstance(); + final MainKeyboardView keyboardView = getOuterInstance(); cancelAndStartAnimators(keyboardView.mAltCodeKeyWhileTypingFadeinAnimator, keyboardView.mAltCodeKeyWhileTypingFadeoutAnimator); } @@ -299,13 +299,13 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke mTouchNoiseThresholdDistance = 0; } - public PointerTrackerParams(TypedArray latinKeyboardViewAttr) { - mSlidingKeyInputEnabled = latinKeyboardViewAttr.getBoolean( - R.styleable.LatinKeyboardView_slidingKeyInputEnable, false); - mTouchNoiseThresholdTime = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_touchNoiseThresholdTime, 0); - mTouchNoiseThresholdDistance = latinKeyboardViewAttr.getDimension( - R.styleable.LatinKeyboardView_touchNoiseThresholdDistance, 0); + public PointerTrackerParams(TypedArray mainKeyboardViewAttr) { + mSlidingKeyInputEnabled = mainKeyboardViewAttr.getBoolean( + R.styleable.MainKeyboardView_slidingKeyInputEnable, false); + mTouchNoiseThresholdTime = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_touchNoiseThresholdTime, 0); + mTouchNoiseThresholdDistance = mainKeyboardViewAttr.getDimension( + R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0); } } @@ -316,65 +316,67 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke public final int mLongPressShiftKeyTimeout; public final int mIgnoreAltCodeKeyTimeout; - public KeyTimerParams(TypedArray latinKeyboardViewAttr) { - mKeyRepeatStartTimeout = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_keyRepeatStartTimeout, 0); - mKeyRepeatInterval = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_keyRepeatInterval, 0); - mLongPressKeyTimeout = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_longPressKeyTimeout, 0); - mLongPressShiftKeyTimeout = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_longPressShiftKeyTimeout, 0); - mIgnoreAltCodeKeyTimeout = latinKeyboardViewAttr.getInt( - R.styleable.LatinKeyboardView_ignoreAltCodeKeyTimeout, 0); + public KeyTimerParams(TypedArray mainKeyboardViewAttr) { + mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0); + mKeyRepeatInterval = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_keyRepeatInterval, 0); + mLongPressKeyTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_longPressKeyTimeout, 0); + mLongPressShiftKeyTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_longPressShiftKeyTimeout, 0); + mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt( + R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0); } } - public LatinKeyboardView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.latinKeyboardViewStyle); + public MainKeyboardView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.mainKeyboardViewStyle); } - public LatinKeyboardView(Context context, AttributeSet attrs, int defStyle) { + public MainKeyboardView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this); mHasDistinctMultitouch = context.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); + final Resources res = getResources(); final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( - Utils.getDeviceOverrideValue(context.getResources(), + Utils.getDeviceOverrideValue(res, R.array.phantom_sudden_move_event_device_list, "false")); PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack); final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.LatinKeyboardView, defStyle, R.style.LatinKeyboardView); + attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); mAutoCorrectionSpacebarLedEnabled = a.getBoolean( - R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedEnabled, false); + R.styleable.MainKeyboardView_autoCorrectionSpacebarLedEnabled, false); mAutoCorrectionSpacebarLedIcon = a.getDrawable( - R.styleable.LatinKeyboardView_autoCorrectionSpacebarLedIcon); - mSpacebarTextRatio = a.getFraction(R.styleable.LatinKeyboardView_spacebarTextRatio, + R.styleable.MainKeyboardView_autoCorrectionSpacebarLedIcon); + mSpacebarTextRatio = a.getFraction(R.styleable.MainKeyboardView_spacebarTextRatio, 1000, 1000, 1) / 1000.0f; - mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboardView_spacebarTextColor, 0); + mSpacebarTextColor = a.getColor(R.styleable.MainKeyboardView_spacebarTextColor, 0); mSpacebarTextShadowColor = a.getColor( - R.styleable.LatinKeyboardView_spacebarTextShadowColor, 0); + R.styleable.MainKeyboardView_spacebarTextShadowColor, 0); mLanguageOnSpacebarFinalAlpha = a.getInt( - R.styleable.LatinKeyboardView_languageOnSpacebarFinalAlpha, ALPHA_OPAQUE); + R.styleable.MainKeyboardView_languageOnSpacebarFinalAlpha, + Constants.Color.ALPHA_OPAQUE); final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId( - R.styleable.LatinKeyboardView_languageOnSpacebarFadeoutAnimator, 0); + R.styleable.MainKeyboardView_languageOnSpacebarFadeoutAnimator, 0); final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId( - R.styleable.LatinKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); + R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeoutAnimator, 0); final int altCodeKeyWhileTypingFadeinAnimatorResId = a.getResourceId( - R.styleable.LatinKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); + R.styleable.MainKeyboardView_altCodeKeyWhileTypingFadeinAnimator, 0); final KeyTimerParams keyTimerParams = new KeyTimerParams(a); mPointerTrackerParams = new PointerTrackerParams(a); final float keyHysteresisDistance = a.getDimension( - R.styleable.LatinKeyboardView_keyHysteresisDistance, 0); + R.styleable.MainKeyboardView_keyHysteresisDistance, 0); mKeyDetector = new KeyDetector(keyHysteresisDistance); mKeyTimerHandler = new KeyTimerHandler(this, keyTimerParams); mConfigShowMoreKeysKeyboardAtTouchedPoint = a.getBoolean( - R.styleable.LatinKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); + R.styleable.MainKeyboardView_showMoreKeysKeyboardAtTouchedPoint, false); a.recycle(); PointerTracker.setParameters(mPointerTrackerParams); @@ -459,17 +461,17 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke super.setKeyboard(keyboard); mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); - PointerTracker.setKeyDetector(mKeyDetector); + PointerTracker.setKeyDetector(mKeyDetector, mGestureInputEnabled); mTouchScreenRegulator.setKeyboard(keyboard); mMoreKeysPanelCache.clear(); mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE); mSpaceIcon = (mSpaceKey != null) - ? mSpaceKey.getIcon(keyboard.mIconsSet, ALPHA_OPAQUE) : null; + ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; mSpacebarTextSize = keyHeight * mSpacebarTextRatio; if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinKeyboardView_setKeyboard(keyboard); + ResearchLogger.mainKeyboardView_setKeyboard(keyboard); } // This always needs to be set since the accessibility state can @@ -507,6 +509,17 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + // Notify the research logger that the keyboard view has been attached. This is needed + // to properly show the splash screen, which requires that the window token of the + // KeyboardView be non-null. + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(); + } + } + + @Override public void cancelAllMessages() { mKeyTimerHandler.cancelAllMessages(); super.cancelAllMessages(); @@ -555,7 +568,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke */ protected boolean onLongPress(Key parentKey, PointerTracker tracker) { if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinKeyboardView_onLongPress(); + ResearchLogger.mainKeyboardView_onLongPress(); } final int primaryCode = parentKey.mCode; if (parentKey.hasEmbeddedMoreKey()) { @@ -706,7 +719,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke } } if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinKeyboardView_processMotionEvent(me, action, eventTime, index, id, + ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, index, id, x, y); } @@ -778,7 +791,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke + pointerSize + "," + pointerPressure); } if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinKeyboardView_processMotionEvent(me, action, eventTime, + ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, i, pointerId, px, py); } } @@ -867,7 +880,7 @@ public class LatinKeyboardView extends KeyboardView implements PointerTracker.Ke mNeedsToDisplayLanguage = false; } else { if (subtypeChanged && needsToDisplayLanguage) { - setLanguageOnSpacebarAnimAlpha(ALPHA_OPAQUE); + setLanguageOnSpacebarAnimAlpha(Constants.Color.ALPHA_OPAQUE); if (animator.isStarted()) { animator.cancel(); } diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java index 9c8069194..870eff29f 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java @@ -25,6 +25,7 @@ import android.widget.PopupWindow; import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; +import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.R; /** @@ -63,8 +64,13 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel } @Override - public void onEndBatchInput(CharSequence text) { - mListener.onEndBatchInput(text); + public void onUpdateBatchInput(InputPointers batchPointers) { + mListener.onUpdateBatchInput(batchPointers); + } + + @Override + public void onEndBatchInput(InputPointers batchPointers) { + mListener.onEndBatchInput(batchPointers); } @Override diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 733d3b09b..4a5ecf986 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -16,17 +16,22 @@ package com.android.inputmethod.keyboard; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; -import com.android.inputmethod.keyboard.internal.GestureTracker; +import com.android.inputmethod.accessibility.AccessibilityUtils; +import com.android.inputmethod.keyboard.internal.GestureStroke; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; +import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.ResearchLogger; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; @@ -37,6 +42,11 @@ public class PointerTracker { private static final boolean DEBUG_LISTENER = false; private static boolean DEBUG_MODE = LatinImeLogger.sDBG; + // TODO: There should be an option to turn on/off the gesture input. + private static boolean sIsGestureEnabled = true; + + private static final int MIN_GESTURE_RECOGNITION_TIME = 100; // msec + public interface KeyEventHandler { /** * Get KeyDetector object that is used for this PointerTracker. @@ -69,6 +79,7 @@ public class PointerTracker { public TextView inflateKeyPreviewText(); public void showKeyPreview(PointerTracker tracker); public void dismissKeyPreview(PointerTracker tracker); + public void showGestureTrail(PointerTracker tracker); } public interface TimerProxy { @@ -108,12 +119,18 @@ public class PointerTracker { } // Parameters for pointer handling. - private static LatinKeyboardView.PointerTrackerParams sParams; + private static MainKeyboardView.PointerTrackerParams sParams; private static int sTouchNoiseThresholdDistanceSquared; private static boolean sNeedsPhantomSuddenMoveEventHack; private static final ArrayList<PointerTracker> sTrackers = new ArrayList<PointerTracker>(); + private static final InputPointers sAggregratedPointers = new InputPointers( + GestureStroke.DEFAULT_CAPACITY); private static PointerTrackerQueue sPointerTrackerQueue; + // HACK: Change gesture detection criteria depending on this variable. + // TODO: Find more comprehensive ways to detect a gesture start. + // True when the previous user input was a gesture input, not a typing input. + private static boolean sWasInGesture; public final int mPointerId; @@ -126,6 +143,14 @@ public class PointerTracker { private int mKeyQuarterWidthSquared; private final TextView mKeyPreviewText; + private boolean mIsAlphabetKeyboard; + private boolean mIsPossibleGesture = false; + private boolean mInGesture = false; + + // TODO: Remove these variables + private int mLastRecognitionPointSize = 0; + private long mLastRecognitionTime = 0; + // The position and time at which first down event occurred. private long mDownTime; private long mUpTime; @@ -162,8 +187,7 @@ public class PointerTracker { private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener.Adapter(); - // Gesture tracker singleton instance - private static final GestureTracker sGestureTracker = GestureTracker.getInstance(); + private final GestureStroke mGestureStroke; public static void init(boolean hasDistinctMultitouch, boolean needsPhantomSuddenMoveEventHack) { @@ -174,15 +198,27 @@ public class PointerTracker { } sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; - setParameters(LatinKeyboardView.PointerTrackerParams.DEFAULT); + setParameters(MainKeyboardView.PointerTrackerParams.DEFAULT); + updateGestureInputEnabledState(null, false /* gestureInputEnabled */); } - public static void setParameters(LatinKeyboardView.PointerTrackerParams params) { + public static void setParameters(MainKeyboardView.PointerTrackerParams params) { sParams = params; sTouchNoiseThresholdDistanceSquared = (int)( params.mTouchNoiseThresholdDistance * params.mTouchNoiseThresholdDistance); } + private static void updateGestureInputEnabledState(Keyboard keyboard, + boolean gestureInputEnabled) { + if (!gestureInputEnabled + || AccessibilityUtils.getInstance().isTouchExplorationEnabled() + || (keyboard != null && keyboard.mId.passwordInput())) { + sIsGestureEnabled = false; + } else { + sIsGestureEnabled = true; + } + } + public static PointerTracker getPointerTracker(final int id, KeyEventHandler handler) { final ArrayList<PointerTracker> trackers = sTrackers; @@ -200,32 +236,83 @@ public class PointerTracker { } public static void setKeyboardActionListener(KeyboardActionListener listener) { - for (final PointerTracker tracker : sTrackers) { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); tracker.mListener = listener; } - GestureTracker.init(listener); } - public static void setKeyDetector(KeyDetector keyDetector) { - for (final PointerTracker tracker : sTrackers) { + public static void setKeyDetector(KeyDetector keyDetector, boolean gestureInputEnabledByUser) { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); tracker.setKeyDetectorInner(keyDetector); // Mark that keyboard layout has been changed. tracker.mKeyboardLayoutHasBeenChanged = true; } - sGestureTracker.setKeyboard(keyDetector.getKeyboard()); + final Keyboard keyboard = keyDetector.getKeyboard(); + updateGestureInputEnabledState(keyboard, gestureInputEnabledByUser); } public static void dismissAllKeyPreviews() { - for (final PointerTracker tracker : sTrackers) { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); tracker.getKeyPreviewText().setVisibility(View.INVISIBLE); tracker.setReleasedKeyGraphics(tracker.mCurrentKey); } } - public PointerTracker(int id, KeyEventHandler handler) { + // TODO: To handle multi-touch gestures we may want to move this method to + // {@link PointerTrackerQueue}. + private static InputPointers getIncrementalBatchPoints() { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); + tracker.mGestureStroke.appendIncrementalBatchPoints(sAggregratedPointers); + } + return sAggregratedPointers; + } + + // TODO: To handle multi-touch gestures we may want to move this method to + // {@link PointerTrackerQueue}. + private static InputPointers getAllBatchPoints() { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); + tracker.mGestureStroke.appendAllBatchPoints(sAggregratedPointers); + } + return sAggregratedPointers; + } + + // TODO: To handle multi-touch gestures we may want to move this method to + // {@link PointerTrackerQueue}. + public static void clearBatchInputPointsOfAllPointerTrackers() { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); + tracker.mGestureStroke.reset(); + } + sAggregratedPointers.reset(); + } + + // TODO: To handle multi-touch gestures we may want to move this method to + // {@link PointerTrackerQueue}. + public static void drawGestureTrailForAllPointerTrackers(Canvas canvas, Paint paint) { + final int trackersSize = sTrackers.size(); + for (int i = 0; i < trackersSize; ++i) { + final PointerTracker tracker = sTrackers.get(i); + tracker.mGestureStroke.drawGestureTrail(canvas, paint, tracker.getLastX(), + tracker.getLastY()); + } + } + + private PointerTracker(int id, KeyEventHandler handler) { if (handler == null) throw new NullPointerException(); mPointerId = id; + mGestureStroke = new GestureStroke(id); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); @@ -239,7 +326,7 @@ public class PointerTracker { // Returns true if keyboard has been changed by this callback. private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { - if (sGestureTracker.isInGesture()) { + if (mInGesture) { return false; } final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); @@ -295,7 +382,7 @@ public class PointerTracker { // Note that we need primaryCode argument because the keyboard may in shifted state and the // primaryCode is different from {@link Key#mCode}. private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) { - if (sGestureTracker.isInGesture()) { + if (mInGesture) { return; } final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier(); @@ -328,6 +415,9 @@ public class PointerTracker { private void setKeyDetectorInner(KeyDetector keyDetector) { mKeyDetector = keyDetector; mKeyboard = keyDetector.getKeyboard(); + mIsAlphabetKeyboard = mKeyboard.mId.isAlphabetKeyboard(); + mGestureStroke.setGestureSampleLength( + mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); final Key newKey = mKeyDetector.detectHitKey(mKeyX, mKeyY); if (newKey != mCurrentKey) { if (mDrawingProxy != null) { @@ -398,7 +488,7 @@ public class PointerTracker { return; } - if (!key.noKeyPreview() && !sGestureTracker.isInGesture()) { + if (!key.noKeyPreview() && !mInGesture) { mDrawingProxy.showKeyPreview(this); } updatePressKeyGraphics(key); @@ -446,6 +536,9 @@ public class PointerTracker { public long getDownTime() { return mDownTime; } + public Rect getBoundingBox() { + return mGestureStroke.getBoundingBox(); + } private Key onDownKey(int x, int y, long eventTime) { mDownTime = eventTime; @@ -469,6 +562,53 @@ public class PointerTracker { return newKey; } + private void startBatchInput() { + if (DEBUG_LISTENER) { + Log.d(TAG, "onStartBatchInput"); + } + mInGesture = true; + mListener.onStartBatchInput(); + } + + private void updateBatchInput(InputPointers batchPoints) { + if (DEBUG_LISTENER) { + Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); + } + mListener.onUpdateBatchInput(batchPoints); + } + + private void endBatchInput(InputPointers batchPoints) { + if (DEBUG_LISTENER) { + Log.d(TAG, "onEndBatchInput: batchPoints=" + batchPoints.getPointerSize()); + } + mListener.onEndBatchInput(batchPoints); + clearBatchInputRecognitionStateOfThisPointerTracker(); + clearBatchInputPointsOfAllPointerTrackers(); + sWasInGesture = true; + } + + private void abortBatchInput() { + clearBatchInputRecognitionStateOfThisPointerTracker(); + clearBatchInputPointsOfAllPointerTrackers(); + } + + private void clearBatchInputRecognitionStateOfThisPointerTracker() { + mIsPossibleGesture = false; + mInGesture = false; + mLastRecognitionPointSize = 0; + mLastRecognitionTime = 0; + } + + private boolean updateBatchInputRecognitionState(long eventTime, int size) { + if (size > mLastRecognitionPointSize + && eventTime > mLastRecognitionTime + MIN_GESTURE_RECOGNITION_TIME) { + mLastRecognitionPointSize = size; + mLastRecognitionTime = eventTime; + return true; + } + return false; + } + public void processMotionEvent(int action, int x, int y, long eventTime, KeyEventHandler handler) { switch (action) { @@ -527,7 +667,15 @@ public class PointerTracker { } onDownEventInternal(x, y, eventTime); if (queue != null && queue.size() == 1) { - sGestureTracker.onDownEvent(this, x, y, eventTime, key); + mIsPossibleGesture = false; + // A gesture should start only from the letter key. + if (sIsGestureEnabled && mIsAlphabetKeyboard && !mIsShowingMoreKeysPanel && key != null + && Keyboard.isLetterCode(key.mCode)) { + mIsPossibleGesture = true; + // TODO: pointer times should be relative to first down even in entire batch input + // instead of resetting to 0 for each new down event. + mGestureStroke.addPoint(x, y, 0, false); + } } } @@ -563,6 +711,26 @@ public class PointerTracker { mIsInSlidingKeyInput = true; } + private void onGestureMoveEvent(PointerTracker tracker, int x, int y, long eventTime, + boolean isHistorical, Key key) { + final int gestureTime = (int)(eventTime - tracker.getDownTime()); + if (sIsGestureEnabled && mIsPossibleGesture) { + final GestureStroke stroke = mGestureStroke; + stroke.addPoint(x, y, gestureTime, isHistorical); + if (!mInGesture && stroke.isStartOfAGesture(gestureTime, sWasInGesture)) { + startBatchInput(); + } + } + + if (key != null && mInGesture) { + final InputPointers batchPoints = getIncrementalBatchPoints(); + mDrawingProxy.showGestureTrail(this); + if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) { + updateBatchInput(batchPoints); + } + } + } + public void onMoveEvent(int x, int y, long eventTime, MotionEvent me) { if (DEBUG_MOVE_EVENT) printTouchEvent("onMoveEvent:", x, y, eventTime); @@ -577,7 +745,7 @@ public class PointerTracker { final int historicalX = (int)me.getHistoricalX(pointerIndex, h); final int historicalY = (int)me.getHistoricalY(pointerIndex, h); final long historicalTime = me.getHistoricalEventTime(h); - sGestureTracker.onMoveEvent(this, historicalX, historicalY, historicalTime, + onGestureMoveEvent(this, historicalX, historicalY, historicalTime, true /* isHistorical */, null); } } @@ -588,8 +756,8 @@ public class PointerTracker { Key key = onMoveKey(x, y); // Register move event on gesture tracker. - sGestureTracker.onMoveEvent(this, x, y, eventTime, false, key); - if (sGestureTracker.isInGesture()) { + onGestureMoveEvent(this, x, y, eventTime, false /* isHistorical */, key); + if (mInGesture) { mIgnoreModifierKey = true; mTimerProxy.cancelLongPressTimer(); mIsInSlidingKeyInput = true; @@ -686,7 +854,7 @@ public class PointerTracker { final PointerTrackerQueue queue = sPointerTrackerQueue; if (queue != null) { - if (!sGestureTracker.isInGesture()) { + if (!mInGesture) { if (mCurrentKey != null && mCurrentKey.isModifier()) { // Before processing an up event of modifier key, all pointers already being // tracked should be released. @@ -720,23 +888,21 @@ public class PointerTracker { mIsShowingMoreKeysPanel = false; } - if (sGestureTracker.isInGesture()) { + if (mInGesture) { // Register up event on gesture tracker. - sGestureTracker.onUpEvent(this, x, y, eventTime); - if (!sPointerTrackerQueue.isAnyInSlidingKeyInput()) { - // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code. - sGestureTracker.endBatchInput(); - } + // TODO: Figure out how to deal with multiple fingers that are in gesture, sliding, + // and/or tapping mode? + endBatchInput(getAllBatchPoints()); if (mCurrentKey != null) { callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true); + mCurrentKey = null; } - mCurrentKey = null; + mDrawingProxy.showGestureTrail(this); return; - } else { - // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code. - sGestureTracker.endBatchInput(); } - + // This event will be recognized as a regular code input. Clear unused batch points so they + // are not mistakenly included in the next batch event. + clearBatchInputPointsOfAllPointerTrackers(); if (mKeyAlreadyProcessed) return; if (mCurrentKey != null && !mCurrentKey.isRepeatable()) { @@ -745,11 +911,10 @@ public class PointerTracker { } public void onShowMoreKeysPanel(int x, int y, KeyEventHandler handler) { + abortBatchInput(); onLongPressed(); - onDownEvent(x, y, SystemClock.uptimeMillis(), handler); mIsShowingMoreKeysPanel = true; - // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code. - sGestureTracker.abortBatchInput(); + onDownEvent(x, y, SystemClock.uptimeMillis(), handler); } public void onLongPressed() { @@ -784,7 +949,7 @@ public class PointerTracker { } private void startRepeatKey(Key key) { - if (key != null && key.isRepeatable() && !sGestureTracker.isInGesture()) { + if (key != null && key.isRepeatable() && !mInGesture) { onRegisterKey(key); mTimerProxy.startKeyRepeatTimer(this); } @@ -814,7 +979,7 @@ public class PointerTracker { } private void startLongPressTimer(Key key) { - if (key != null && key.isLongPressEnabled() && !sGestureTracker.isInGesture()) { + if (key != null && key.isLongPressEnabled() && !mInGesture) { mTimerProxy.startLongPressTimer(this); } } @@ -828,6 +993,7 @@ public class PointerTracker { int code = key.mCode; callListenerOnCodeInput(key, code, x, y); callListenerOnRelease(key, code, false); + sWasInGesture = false; } private void printTouchEvent(String title, int x, int y, long eventTime) { diff --git a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java index 107138395..2398c0850 100644 --- a/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java +++ b/java/src/com/android/inputmethod/keyboard/SuddenJumpingTouchEventHandler.java @@ -22,9 +22,9 @@ import android.view.MotionEvent; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResearchLogger; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; public class SuddenJumpingTouchEventHandler { private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName(); @@ -70,7 +70,7 @@ public class SuddenJumpingTouchEventHandler { * the sudden moves subside, a DOWN event is simulated for the second key. * @param me the motion event * @return true if the event was consumed, so that it doesn't continue to be handled by - * {@link LatinKeyboardView}. + * {@link MainKeyboardView}. */ private boolean handleSuddenJumping(MotionEvent me) { if (!mNeedsSuddenJumpingHack) diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java new file mode 100644 index 000000000..c16b70ef0 --- /dev/null +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.keyboard.internal; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.FloatMath; + +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.InputPointers; +import com.android.inputmethod.latin.ResizableIntArray; + +public class GestureStroke { + public static final int DEFAULT_CAPACITY = 128; + + private final int mPointerId; + private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); + private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); + private float mLength; + private float mAngle; + private int mIncrementalRecognitionSize; + private int mLastIncrementalBatchSize; + private long mLastPointTime; + private int mLastPointX; + private int mLastPointY; + + private int mMinGestureLength; + private int mMinGestureLengthWhileInGesture; + private int mMinGestureSampleLength; + private final Rect mDrawingRect = new Rect(); + + // TODO: Move some of these to resource. + private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH = 1.0f; + private static final float MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE = 0.5f; + private static final int MIN_GESTURE_DURATION = 150; // msec + private static final int MIN_GESTURE_DURATION_WHILE_IN_GESTURE = 75; // msec + private static final float MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT = 1.0f / 6.0f; + private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; // dip/msec + private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float)(Math.PI / 4.0f); + + private static final float DOUBLE_PI = (float)(2 * Math.PI); + + // Fade based on number of gesture samples, see MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT + private static final int DRAWING_GESTURE_FADE_START = 10; + private static final int DRAWING_GESTURE_FADE_RATE = 6; + + public GestureStroke(int pointerId) { + mPointerId = pointerId; + reset(); + } + + public void setGestureSampleLength(final int keyWidth, final int keyHeight) { + // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? + mMinGestureLength = (int)(keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH); + mMinGestureLengthWhileInGesture = (int)( + keyWidth * MIN_GESTURE_LENGTH_RATIO_TO_KEY_WIDTH_WHILE_IN_GESTURE); + mMinGestureSampleLength = (int)(keyHeight * MIN_GESTURE_SAMPLING_RATIO_TO_KEY_HEIGHT); + } + + public boolean isStartOfAGesture(final int downDuration, final boolean wasInGesture) { + // The tolerance of the time duration and the stroke length to detect the start of a + // gesture stroke should be eased when the previous input was a gesture input. + if (wasInGesture) { + return downDuration > MIN_GESTURE_DURATION_WHILE_IN_GESTURE + && mLength > mMinGestureLengthWhileInGesture; + } + return downDuration > MIN_GESTURE_DURATION && mLength > mMinGestureLength; + } + + public void reset() { + mLength = 0; + mAngle = 0; + mIncrementalRecognitionSize = 0; + mLastIncrementalBatchSize = 0; + mLastPointTime = 0; + mEventTimes.setLength(0); + mXCoordinates.setLength(0); + mYCoordinates.setLength(0); + mDrawingRect.setEmpty(); + } + + private void updateLastPoint(final int x, final int y, final int time) { + mLastPointTime = time; + mLastPointX = x; + mLastPointY = y; + } + + public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { + final int size = mEventTimes.getLength(); + if (size == 0) { + mEventTimes.add(time); + mXCoordinates.add(x); + mYCoordinates.add(y); + if (!isHistorical) { + updateLastPoint(x, y, time); + } + return; + } + + final int lastX = mXCoordinates.get(size - 1); + final int lastY = mYCoordinates.get(size - 1); + final float dist = getDistance(lastX, lastY, x, y); + if (dist > mMinGestureSampleLength) { + mEventTimes.add(time); + mXCoordinates.add(x); + mYCoordinates.add(y); + mLength += dist; + final float angle = getAngle(lastX, lastY, x, y); + if (size > 1) { + final float curvature = getAngleDiff(angle, mAngle); + if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) { + if (size > mIncrementalRecognitionSize) { + mIncrementalRecognitionSize = size; + } + } + } + mAngle = angle; + } + + if (!isHistorical) { + final int duration = (int)(time - mLastPointTime); + if (mLastPointTime != 0 && duration > 0) { + final float speed = getDistance(mLastPointX, mLastPointY, x, y) / duration; + if (speed < GESTURE_RECOG_SPEED_THRESHOLD) { + mIncrementalRecognitionSize = size; + } + } + updateLastPoint(x, y, time); + } + } + + public void appendAllBatchPoints(final InputPointers out) { + appendBatchPoints(out, mEventTimes.getLength()); + } + + public void appendIncrementalBatchPoints(final InputPointers out) { + appendBatchPoints(out, mIncrementalRecognitionSize); + } + + private void appendBatchPoints(final InputPointers out, final int size) { + out.append(mPointerId, mEventTimes, mXCoordinates, mYCoordinates, + mLastIncrementalBatchSize, size - mLastIncrementalBatchSize); + mLastIncrementalBatchSize = size; + } + + private static float getDistance(final int p1x, final int p1y, + final int p2x, final int p2y) { + final float dx = p1x - p2x; + final float dy = p1y - p2y; + // TODO: Optimize out this {@link FloatMath#sqrt(float)} call. + return FloatMath.sqrt(dx * dx + dy * dy); + } + + private static float getAngle(final int p1x, final int p1y, final int p2x, final int p2y) { + final int dx = p1x - p2x; + final int dy = p1y - p2y; + if (dx == 0 && dy == 0) return 0; + return (float)Math.atan2(dy, dx); + } + + private static float getAngleDiff(final float a1, final float a2) { + final float diff = Math.abs(a1 - a2); + if (diff > Math.PI) { + return DOUBLE_PI - diff; + } + return diff; + } + + public void drawGestureTrail(Canvas canvas, Paint paint, int lastX, int lastY) { + // TODO: These paint parameter interpolation should be tunable, possibly introduce an object + // that implements an interface such as Paint getPaint(int step, int strokePoints) + final int size = mXCoordinates.getLength(); + int[] xCoords = mXCoordinates.getPrimitiveArray(); + int[] yCoords = mYCoordinates.getPrimitiveArray(); + int alpha = Constants.Color.ALPHA_OPAQUE; + for (int i = size - 1; i > 0 && alpha > 0; i--) { + paint.setAlpha(alpha); + if (size - i > DRAWING_GESTURE_FADE_START) { + alpha -= DRAWING_GESTURE_FADE_RATE; + } + canvas.drawLine(xCoords[i - 1], yCoords[i - 1], xCoords[i], yCoords[i], paint); + if (i == size - 1) { + canvas.drawLine(lastX, lastY, xCoords[i], yCoords[i], paint); + } + } + } + + public Rect getBoundingBox() { + return mDrawingRect; + } +} diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java deleted file mode 100644 index dfd697a7a..000000000 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.keyboard.internal; - -import android.util.Log; -import android.util.SparseArray; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.InputPointers; -import com.android.inputmethod.latin.SuggestedWords; - -// TODO: Remove this class by consolidating with PointerTracker -public class GestureTracker { - private static final String TAG = GestureTracker.class.getSimpleName(); - private static final boolean DEBUG_LISTENER = false; - - // TODO: There should be an option to turn on/off the gesture input. - private static final boolean GESTURE_ON = true; - - private static final GestureTracker sInstance = new GestureTracker(); - - private static final int MIN_RECOGNITION_TIME = 100; - private static final int MIN_GESTURE_DURATION = 200; - - private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f; - private static final float SQUARED_GESTURE_RECOG_SPEED_THRESHOLD = - GESTURE_RECOG_SPEED_THRESHOLD * GESTURE_RECOG_SPEED_THRESHOLD; - private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float) (Math.PI / 4); - - private boolean mIsAlphabetKeyboard; - private boolean mIsPossibleGesture = false; - private boolean mInGesture = false; - - private KeyboardActionListener mListener; - private SuggestedWords mSuggestions; - - private final SparseArray<GestureStroke> mGestureStrokes = new SparseArray<GestureStroke>(); - - private int mLastRecognitionPointSize = 0; - private long mLastRecognitionTime = 0; - - public static void init(KeyboardActionListener listner) { - sInstance.mListener = listner; - } - - public static GestureTracker getInstance() { - return sInstance; - } - - private GestureTracker() { - } - - public void setKeyboard(Keyboard keyboard) { - mIsAlphabetKeyboard = keyboard.mId.isAlphabetKeyboard(); - GestureStroke.setGestureSampleLength(keyboard.mMostCommonKeyWidth / 2, - keyboard.mMostCommonKeyHeight / 6); - } - - private void startBatchInput() { - if (DEBUG_LISTENER) { - Log.d(TAG, "onStartBatchInput"); - } - mInGesture = true; - mListener.onStartBatchInput(); - mSuggestions = null; - } - - // TODO: The corresponding startBatchInput() is a private method. Reorganize the code. - public void endBatchInput() { - if (isInGesture() && mSuggestions != null && mSuggestions.size() > 0) { - final CharSequence text = mSuggestions.getWord(0); - if (DEBUG_LISTENER) { - Log.d(TAG, "onEndBatchInput: text=" + text); - } - mListener.onEndBatchInput(text); - } - mInGesture = false; - clearBatchInputPoints(); - } - - public void abortBatchInput() { - mIsPossibleGesture = false; - mInGesture = false; - } - - public boolean isInGesture() { - return mInGesture; - } - - public void onDownEvent(PointerTracker tracker, int x, int y, long eventTime, Key key) { - mIsPossibleGesture = false; - // A gesture should start only from the letter key. - if (GESTURE_ON && mIsAlphabetKeyboard && key != null && Keyboard.isLetterCode(key.mCode)) { - mIsPossibleGesture = true; - addPointToStroke(x, y, 0, tracker.mPointerId, false); - } - } - - public void onMoveEvent(PointerTracker tracker, int x, int y, long eventTime, - boolean isHistorical, Key key) { - final int gestureTime = (int)(eventTime - tracker.getDownTime()); - if (GESTURE_ON && mIsPossibleGesture) { - final GestureStroke stroke = addPointToStroke(x, y, gestureTime, tracker.mPointerId, - isHistorical); - if (!isInGesture() && stroke.isStartOfAGesture(gestureTime)) { - startBatchInput(); - } - } - - if (key != null && isInGesture()) { - final InputPointers batchPoints = getIncrementalBatchPoints(); - if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) { - if (DEBUG_LISTENER) { - Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); - } - mSuggestions = mListener.onUpdateBatchInput(batchPoints); - } - } - } - - public void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) { - if (isInGesture()) { - final InputPointers batchPoints = getAllBatchPoints(); - if (DEBUG_LISTENER) { - Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize()); - } - mSuggestions = mListener.onUpdateBatchInput(batchPoints); - } - } - - private GestureStroke addPointToStroke(int x, int y, int time, int pointerId, - boolean isHistorical) { - GestureStroke stroke = mGestureStrokes.get(pointerId); - if (stroke == null) { - stroke = new GestureStroke(pointerId); - mGestureStrokes.put(pointerId, stroke); - } - stroke.addPoint(x, y, time, isHistorical); - return stroke; - } - - // The working and return object of the following methods, {@link #getIncrementalBatchPoints()} - // and {@link #getAllBatchPoints()}. - private final InputPointers mAggregatedPointers = new InputPointers(); - - private InputPointers getIncrementalBatchPoints() { - final InputPointers pointers = mAggregatedPointers; - pointers.reset(); - final int strokeSize = mGestureStrokes.size(); - for (int index = 0; index < strokeSize; index++) { - final GestureStroke stroke = mGestureStrokes.valueAt(index); - stroke.appendIncrementalBatchPoints(pointers); - } - return pointers; - } - - private InputPointers getAllBatchPoints() { - final InputPointers pointers = mAggregatedPointers; - pointers.reset(); - final int strokeSize = mGestureStrokes.size(); - for (int index = 0; index < strokeSize; index++) { - final GestureStroke stroke = mGestureStrokes.valueAt(index); - stroke.appendAllBatchPoints(pointers); - } - return pointers; - } - - private void clearBatchInputPoints() { - final int strokeSize = mGestureStrokes.size(); - for (int index = 0; index < strokeSize; index++) { - final GestureStroke stroke = mGestureStrokes.valueAt(index); - stroke.reset(); - } - mLastRecognitionPointSize = 0; - mLastRecognitionTime = 0; - } - - private boolean updateBatchInputRecognitionState(long eventTime, int size) { - if (size > mLastRecognitionPointSize - && eventTime > mLastRecognitionTime + MIN_RECOGNITION_TIME) { - mLastRecognitionPointSize = size; - mLastRecognitionTime = eventTime; - return true; - } - return false; - } - - private static class GestureStroke { - private final int mPointerId; - private final InputPointers mInputPointers = new InputPointers(); - private float mLength; - private float mAngle; - private int mIncrementalRecognitionPoint; - private boolean mHasSharpCorner; - private long mLastPointTime; - private int mLastPointX; - private int mLastPointY; - - private static int sMinGestureLength; - private static int sSquaredGestureSampleLength; - - private static final float DOUBLE_PI = (float)(2 * Math.PI); - - public static void setGestureSampleLength(final int minGestureLength, - final int sampleLength) { - sMinGestureLength = minGestureLength; - sSquaredGestureSampleLength = sampleLength * sampleLength; - } - - public GestureStroke(int pointerId) { - mPointerId = pointerId; - reset(); - } - - public boolean isStartOfAGesture(int downDuration) { - return downDuration > MIN_GESTURE_DURATION / 2 && mLength > sMinGestureLength / 2; - } - - public void reset() { - mLength = 0; - mAngle = 0; - mIncrementalRecognitionPoint = 0; - mHasSharpCorner = false; - mLastPointTime = 0; - mInputPointers.reset(); - } - - private void updateLastPoint(final int x, final int y, final int time) { - mLastPointTime = time; - mLastPointX = x; - mLastPointY = y; - } - - public void addPoint(final int x, final int y, final int time, final boolean isHistorical) { - final int size = mInputPointers.getPointerSize(); - if (size == 0) { - mInputPointers.addPointer(x, y, mPointerId, time); - if (!isHistorical) { - updateLastPoint(x, y, time); - } - return; - } - - final int[] xCoords = mInputPointers.getXCoordinates(); - final int[] yCoords = mInputPointers.getYCoordinates(); - final int lastX = xCoords[size - 1]; - final int lastY = yCoords[size - 1]; - final float dist = squaredDistance(lastX, lastY, x, y); - if (dist > sSquaredGestureSampleLength) { - mInputPointers.addPointer(x, y, mPointerId, time); - mLength += dist; - final float angle = angle(lastX, lastY, x, y); - if (size > 1) { - float curvature = getAngleDiff(angle, mAngle); - if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) { - if (size > mIncrementalRecognitionPoint) { - mIncrementalRecognitionPoint = size; - } - mHasSharpCorner = true; - } - if (!mHasSharpCorner) { - mIncrementalRecognitionPoint = size; - } - } - mAngle = angle; - } - - if (!isHistorical) { - final int duration = (int)(time - mLastPointTime); - if (mLastPointTime != 0 && duration > 0) { - final int squaredDuration = duration * duration; - final float squaredSpeed = - squaredDistance(mLastPointX, mLastPointY, x, y) / squaredDuration; - if (squaredSpeed < SQUARED_GESTURE_RECOG_SPEED_THRESHOLD) { - mIncrementalRecognitionPoint = size; - } - } - updateLastPoint(x, y, time); - } - } - - private float getAngleDiff(float a1, float a2) { - final float diff = Math.abs(a1 - a2); - if (diff > Math.PI) { - return DOUBLE_PI - diff; - } - return diff; - } - - public void appendAllBatchPoints(InputPointers out) { - out.append(mInputPointers, 0, mInputPointers.getPointerSize()); - } - - public void appendIncrementalBatchPoints(InputPointers out) { - out.append(mInputPointers, 0, mIncrementalRecognitionPoint); - } - } - - static float squaredDistance(int p1x, int p1y, int p2x, int p2y) { - final float dx = p1x - p2x; - final float dy = p1y - p2y; - return dx * dx + dy * dy; - } - - static float angle(int p1x, int p1y, int p2x, int p2y) { - final int dx = p1x - p2x; - final int dy = p1y - p2y; - if (dx == 0 && dy == 0) return 0; - return (float)Math.atan2(dy, dx); - } -} diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index e79db367c..1242967ad 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -19,6 +19,13 @@ package com.android.inputmethod.latin; import android.view.inputmethod.EditorInfo; public final class Constants { + public static final class Color { + /** + * The alpha value for fully opaque. + */ + public final static int ALPHA_OPAQUE = 255; + } + public static final class ImeOption { /** * The private IME option used to indicate that no microphone should be shown for a given diff --git a/java/src/com/android/inputmethod/latin/ImfUtils.java b/java/src/com/android/inputmethod/latin/ImfUtils.java index b882a4860..1461c0240 100644 --- a/java/src/com/android/inputmethod/latin/ImfUtils.java +++ b/java/src/com/android/inputmethod/latin/ImfUtils.java @@ -90,6 +90,13 @@ public class ImfUtils { return false; } + public static InputMethodSubtype getCurrentInputMethodSubtype(Context context, + InputMethodSubtype defaultSubtype) { + final InputMethodManager imm = getInputMethodManager(context); + final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); + return (currentSubtype != null) ? currentSubtype : defaultSubtype; + } + public static boolean hasMultipleEnabledIMEsOrSubtypes(Context context, final boolean shouldIncludeAuxiliarySubtypes) { final InputMethodManager imm = getInputMethodManager(context); diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index 9c32f947c..e561f5956 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -63,7 +63,7 @@ public class InputAttributes { final boolean flagAutoComplete = 0 != (inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE); - // Make sure that passwords are not displayed in {@link SuggestionsView}. + // Make sure that passwords are not displayed in {@link SuggestionStripView}. if (InputTypeUtils.isPasswordInputType(inputType) || InputTypeUtils.isVisiblePasswordInputType(inputType) || InputTypeUtils.isEmailVariation(variation) diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java index 5ad53480f..cbc916a7e 100644 --- a/java/src/com/android/inputmethod/latin/InputPointers.java +++ b/java/src/com/android/inputmethod/latin/InputPointers.java @@ -16,14 +16,21 @@ package com.android.inputmethod.latin; -import java.util.Arrays; - // TODO: This class is not thread-safe. public class InputPointers { - private final ScalableIntArray mXCoordinates = new ScalableIntArray(); - private final ScalableIntArray mYCoordinates = new ScalableIntArray(); - private final ScalableIntArray mPointerIds = new ScalableIntArray(); - private final ScalableIntArray mTimes = new ScalableIntArray(); + private final int mDefaultCapacity; + private final ResizableIntArray mXCoordinates; + private final ResizableIntArray mYCoordinates; + private final ResizableIntArray mPointerIds; + private final ResizableIntArray mTimes; + + public InputPointers(int defaultCapacity) { + mDefaultCapacity = defaultCapacity; + mXCoordinates = new ResizableIntArray(defaultCapacity); + mYCoordinates = new ResizableIntArray(defaultCapacity); + mPointerIds = new ResizableIntArray(defaultCapacity); + mTimes = new ResizableIntArray(defaultCapacity); + } public void addPointer(int index, int x, int y, int pointerId, int time) { mXCoordinates.add(index, x); @@ -60,17 +67,42 @@ public class InputPointers { * @param length the number of pointers to be appended. */ public void append(InputPointers src, int startPos, int length) { + if (length == 0) { + return; + } mXCoordinates.append(src.mXCoordinates, startPos, length); mYCoordinates.append(src.mYCoordinates, startPos, length); mPointerIds.append(src.mPointerIds, startPos, length); mTimes.append(src.mTimes, startPos, length); } + /** + * Append the times, x-coordinates and y-coordinates in the specified {@link ResizableIntArray} + * to the end of this. + * @param pointerId the pointer id of the source. + * @param times the source {@link ResizableIntArray} to read the event times from. + * @param xCoordinates the source {@link ResizableIntArray} to read the x-coordinates from. + * @param yCoordinates the source {@link ResizableIntArray} to read the y-coordinates from. + * @param startPos the starting index of the data in {@code times} and etc. + * @param length the number of data to be appended. + */ + public void append(int pointerId, ResizableIntArray times, ResizableIntArray xCoordinates, + ResizableIntArray yCoordinates, int startPos, int length) { + if (length == 0) { + return; + } + mXCoordinates.append(xCoordinates, startPos, length); + mYCoordinates.append(yCoordinates, startPos, length); + mPointerIds.fill(pointerId, startPos, length); + mTimes.append(times, startPos, length); + } + public void reset() { - mXCoordinates.reset(); - mYCoordinates.reset(); - mPointerIds.reset(); - mTimes.reset(); + final int defaultCapacity = mDefaultCapacity; + mXCoordinates.reset(defaultCapacity); + mYCoordinates.reset(defaultCapacity); + mPointerIds.reset(defaultCapacity); + mTimes.reset(defaultCapacity); } public int getPointerSize() { @@ -92,73 +124,4 @@ public class InputPointers { public int[] getTimes() { return mTimes.getPrimitiveArray(); } - - private static class ScalableIntArray { - private static final int DEFAULT_SIZE = BinaryDictionary.MAX_WORD_LENGTH; - private int[] mArray; - private int mLength; - - public ScalableIntArray() { - reset(); - } - - public void add(int index, int val) { - if (mLength < index + 1) { - mLength = index; - add(val); - } else { - mArray[index] = val; - } - } - - public void add(int val) { - final int nextLength = mLength + 1; - ensureCapacity(nextLength); - mArray[mLength] = val; - mLength = nextLength; - } - - private void ensureCapacity(int minimumCapacity) { - if (mArray.length < minimumCapacity) { - final int nextCapacity = mArray.length * 2; - // The following is the same as newLength = Math.max(minimumCapacity, nextCapacity); - final int newLength = minimumCapacity > nextCapacity - ? minimumCapacity - : nextCapacity; - mArray = Arrays.copyOf(mArray, newLength); - } - } - - public int getLength() { - return mLength; - } - - public void reset() { - mArray = new int[DEFAULT_SIZE]; - mLength = 0; - } - - public int[] getPrimitiveArray() { - return mArray; - } - - public void set(ScalableIntArray ip) { - mArray = ip.mArray; - mLength = ip.mLength; - } - - public void copy(ScalableIntArray ip) { - ensureCapacity(ip.mLength); - System.arraycopy(ip.mArray, 0, mArray, 0, ip.mLength); - mLength = ip.mLength; - } - - public void append(ScalableIntArray src, int startPos, int length) { - final int currentLength = mLength; - final int newLength = currentLength + length; - ensureCapacity(newLength); - System.arraycopy(src.mArray, startPos, mArray, currentLength, length); - mLength = newLength; - } - } } diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java index 0dcb811b5..c15f45345 100644 --- a/java/src/com/android/inputmethod/latin/InputView.java +++ b/java/src/com/android/inputmethod/latin/InputView.java @@ -24,7 +24,7 @@ import android.view.View; import android.widget.LinearLayout; public class InputView extends LinearLayout { - private View mSuggestionsContainer; + private View mSuggestionStripContainer; private View mKeyboardView; private int mKeyboardTopPadding; @@ -43,13 +43,13 @@ public class InputView extends LinearLayout { @Override protected void onFinishInflate() { - mSuggestionsContainer = findViewById(R.id.suggestions_container); + mSuggestionStripContainer = findViewById(R.id.suggestions_container); mKeyboardView = findViewById(R.id.keyboard_view); } @Override public boolean dispatchTouchEvent(MotionEvent me) { - if (mSuggestionsContainer.getVisibility() == VISIBLE + if (mSuggestionStripContainer.getVisibility() == VISIBLE && mKeyboardView.getVisibility() == VISIBLE && forwardTouchEvent(me)) { return true; @@ -57,7 +57,8 @@ public class InputView extends LinearLayout { return super.dispatchTouchEvent(me); } - // The touch events that hit the top padding of keyboard should be forwarded to SuggestionsView. + // The touch events that hit the top padding of keyboard should be forwarded to + // {@link SuggestionStripView}. private boolean forwardTouchEvent(MotionEvent me) { final Rect rect = mInputViewRect; this.getGlobalVisibleRect(rect); @@ -96,7 +97,7 @@ public class InputView extends LinearLayout { } final Rect receivingRect = mEventReceivingRect; - mSuggestionsContainer.getGlobalVisibleRect(receivingRect); + mSuggestionStripContainer.getGlobalVisibleRect(receivingRect); final int translatedX = x - receivingRect.left; final int translatedY; if (y < forwardingLimitY) { @@ -106,7 +107,7 @@ public class InputView extends LinearLayout { translatedY = y - receivingRect.top; } me.setLocation(translatedX, translatedY); - mSuggestionsContainer.dispatchTouchEvent(me); + mSuggestionStripContainer.dispatchTouchEvent(me); return true; } } diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 974af2584..bb39ce4f7 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -45,7 +45,7 @@ public class LastComposedWord { public final String mCommittedWord; public final int mSeparatorCode; public final CharSequence mPrevWord; - public final InputPointers mInputPointers = new InputPointers(); + public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH); private boolean mActive; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 518bcd5ce..d4b59c4cd 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -20,6 +20,7 @@ import static com.android.inputmethod.latin.Constants.ImeOption.FORCE_ASCII; import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE; import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT; +import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; @@ -38,7 +39,6 @@ import android.os.Debug; import android.os.IBinder; import android.os.Message; import android.os.SystemClock; -import android.preference.PreferenceActivity; import android.preference.PreferenceManager; import android.text.InputType; import android.text.TextUtils; @@ -67,10 +67,11 @@ import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.LatinKeyboardView; +import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.LocaleUtils.RunInLocale; import com.android.inputmethod.latin.define.ProductionFlag; -import com.android.inputmethod.latin.suggestions.SuggestionsView; +import com.android.inputmethod.latin.suggestions.SuggestionStripView; +import com.android.inputmethod.research.ResearchLogger; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -81,7 +82,7 @@ import java.util.Locale; * Input method implementation for Qwerty'ish keyboard. */ public class LatinIME extends InputMethodService implements KeyboardActionListener, - SuggestionsView.Listener, TargetApplicationGetter.OnTargetApplicationKnownListener { + SuggestionStripView.Listener, TargetApplicationGetter.OnTargetApplicationKnownListener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean TRACE = false; private static boolean DEBUG; @@ -125,7 +126,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private View mExtractArea; private View mKeyPreviewBackingView; private View mSuggestionsContainer; - private SuggestionsView mSuggestionsView; + private SuggestionStripView mSuggestionStripView; /* package for tests */ Suggest mSuggest; private CompletionInfo[] mApplicationSpecifiedCompletions; private ApplicationInfo mTargetApplicationInfo; @@ -205,7 +206,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final KeyboardSwitcher switcher = latinIme.mKeyboardSwitcher; switch (msg.what) { case MSG_UPDATE_SUGGESTION_STRIP: - latinIme.updateSuggestionsOrPredictions(); + latinIme.updateSuggestionStrip(); break; case MSG_UPDATE_SHIFT_STATE: switcher.updateShiftState(); @@ -548,9 +549,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen .findViewById(android.R.id.extractArea); mKeyPreviewBackingView = view.findViewById(R.id.key_preview_backing); mSuggestionsContainer = view.findViewById(R.id.suggestions_container); - mSuggestionsView = (SuggestionsView) view.findViewById(R.id.suggestions_view); - if (mSuggestionsView != null) - mSuggestionsView.setListener(this, view); + mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view); + if (mSuggestionStripView != null) + mSuggestionStripView.setListener(this, view); if (LatinImeLogger.sVISUALDEBUG) { mKeyPreviewBackingView.setBackgroundColor(0x10FF0000); } @@ -597,7 +598,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void onStartInputViewInternal(EditorInfo editorInfo, boolean restarting) { super.onStartInputView(editorInfo, restarting); final KeyboardSwitcher switcher = mKeyboardSwitcher; - LatinKeyboardView inputView = switcher.getKeyboardView(); + MainKeyboardView inputView = switcher.getKeyboardView(); if (editorInfo == null) { Log.e(TAG, "Null EditorInfo in onStartInputView()"); @@ -618,7 +619,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen + ((editorInfo.inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) != 0)); } if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.getInstance().start(); ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, mPrefs); } if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) { @@ -674,8 +674,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen switcher.loadKeyboard(editorInfo, mCurrentSettings); - if (mSuggestionsView != null) - mSuggestionsView.clear(); + if (mSuggestionStripView != null) + mSuggestionStripView.clear(); setSuggestionStripShownInternal( isSuggestionsStripVisible(), /* needsInputViewShown */ false); @@ -711,7 +711,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen LatinImeLogger.commit(); if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.getInstance().stop(); + ResearchLogger.getInstance().latinIME_onFinishInputInternal(); } KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); @@ -862,7 +862,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!mCurrentSettings.isApplicationSpecifiedCompletionsOn()) return; mApplicationSpecifiedCompletions = applicationSpecifiedCompletions; if (applicationSpecifiedCompletions == null) { - clearSuggestions(); + clearSuggestionStrip(); return; } @@ -878,7 +878,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen false /* isPrediction */); // When in fullscreen mode, show completions generated by the application final boolean isAutoCorrection = false; - setSuggestions(suggestedWords, isAutoCorrection); + setSuggestionStrip(suggestedWords, isAutoCorrection); setAutoCorrectionIndicator(isAutoCorrection); // TODO: is this the right thing to do? What should we auto-correct to in // this case? This says to keep whatever the user typed. @@ -889,7 +889,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) { // TODO: Modify this if we support suggestions with hard keyboard if (onEvaluateInputViewShown() && mSuggestionsContainer != null) { - final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); + final MainKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); final boolean inputViewShown = (keyboardView != null) ? keyboardView.isShown() : false; final boolean shouldShowSuggestions = shown && (needsInputViewShown ? inputViewShown : true); @@ -927,7 +927,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen - keyboardHeight; final LayoutParams params = mKeyPreviewBackingView.getLayoutParams(); - params.height = mSuggestionsView.setMoreSuggestionsHeight(remainingHeight); + params.height = mSuggestionStripView.setMoreSuggestionsHeight(remainingHeight); mKeyPreviewBackingView.setLayoutParams(params); return params.height; } @@ -950,7 +950,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int extraHeight = extractHeight + backingHeight + suggestionsHeight; int touchY = extraHeight; // Need to set touchable region only if input view is being shown - final LatinKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); + final MainKeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); if (keyboardView != null && keyboardView.isShown()) { if (mSuggestionsContainer.getVisibility() == View.VISIBLE) { touchY -= suggestionsHeight; @@ -988,7 +988,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // the composing word, reset the last composed word, tell the inputconnection about it. private void resetEntireInputState() { resetComposingState(true /* alsoResetLastComposedWord */); - clearSuggestions(); + clearSuggestionStrip(); mConnection.finishComposingText(); } @@ -1011,7 +1011,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, typedWord.toString(), separatorCode, prevWord); } - updateSuggestionsOrPredictions(); + updateSuggestionStrip(); } // Called from the KeyboardSwitcher which needs to know auto caps state to display @@ -1091,7 +1091,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET; } - // Callback for the SuggestionsView, to call when the "add to dictionary" hint is pressed. + // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is + // pressed. @Override public boolean addWordToUserDictionary(String word) { mUserDictionary.addWordToUserDictionary(word, 128); @@ -1329,13 +1330,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } @Override - public SuggestedWords onUpdateBatchInput(InputPointers batchPointers) { + public void onUpdateBatchInput(InputPointers batchPointers) { mWordComposer.setBatchInputPointers(batchPointers); - return updateSuggestionsOrPredictions(); + final SuggestedWords suggestedWords = getSuggestedWords(); + showSuggestionStrip(suggestedWords, null); } @Override - public void onEndBatchInput(CharSequence text) { + public void onEndBatchInput(InputPointers batchPointers) { + mWordComposer.setBatchInputPointers(batchPointers); + final SuggestedWords suggestedWords = getSuggestedWords(); + showSuggestionStrip(suggestedWords, null); + if (suggestedWords == null || suggestedWords.size() == 0) { + return; + } + final CharSequence text = suggestedWords.getWord(0); + if (TextUtils.isEmpty(text)) { + return; + } mWordComposer.setBatchInputWord(text); mConnection.beginBatchEdit(); if (SPACE_STATE_PHANTOM == mSpaceState) { @@ -1555,7 +1567,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSpaceState = SPACE_STATE_WEAK; } // In case the "add to dictionary" hint was still displayed. - if (null != mSuggestionsView) mSuggestionsView.dismissAddToDictionaryHint(); + if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint(); } mHandler.postUpdateSuggestionStrip(); Utils.Stats.onNonSeparator((char)primaryCode, x, y); @@ -1634,7 +1646,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void handleClose() { commitTyped(LastComposedWord.NOT_A_SEPARATOR); requestHideSelf(0); - LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + MainKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) inputView.closing(); } @@ -1642,14 +1654,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // TODO: make this private // Outside LatinIME, only used by the test suite. /* package for tests */ boolean isShowingPunctuationList() { - if (mSuggestionsView == null) return false; - return mCurrentSettings.mSuggestPuncList == mSuggestionsView.getSuggestions(); + if (mSuggestionStripView == null) return false; + return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions(); } private boolean isSuggestionsStripVisible() { - if (mSuggestionsView == null) + if (mSuggestionStripView == null) return false; - if (mSuggestionsView.isShowingAddToDictionaryHint()) + if (mSuggestionStripView.isShowingAddToDictionaryHint()) return true; if (!mCurrentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation)) return false; @@ -1658,14 +1670,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return mCurrentSettings.isSuggestionsRequested(mDisplayOrientation); } - private void clearSuggestions() { - setSuggestions(SuggestedWords.EMPTY, false); + private void clearSuggestionStrip() { + setSuggestionStrip(SuggestedWords.EMPTY, false); setAutoCorrectionIndicator(false); } - private void setSuggestions(final SuggestedWords words, final boolean isAutoCorrection) { - if (mSuggestionsView != null) { - mSuggestionsView.setSuggestions(words); + private void setSuggestionStrip(final SuggestedWords words, final boolean isAutoCorrection) { + if (mSuggestionStripView != null) { + mSuggestionStripView.setSuggestions(words); mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection); } } @@ -1681,8 +1693,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - // TODO: rename this method to updateSuggestionStrip or simply updateSuggestions - private SuggestedWords updateSuggestionsOrPredictions() { + private void updateSuggestionStrip() { mHandler.cancelUpdateSuggestionStrip(); // Check if we have a suggestion engine attached. @@ -1692,15 +1703,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen + "requested!"); mWordComposer.setAutoCorrection(mWordComposer.getTypedWord()); } - return null; + return; } - final String typedWord = mWordComposer.getTypedWord(); if (!mWordComposer.isComposingWord() && !mCurrentSettings.mBigramPredictionEnabled) { setPunctuationSuggestions(); - return null; + return; } + final SuggestedWords suggestedWords = getSuggestedWords(); + final String typedWord = mWordComposer.getTypedWord(); + showSuggestionStrip(suggestedWords, typedWord); + } + + private SuggestedWords getSuggestedWords() { + final String typedWord = mWordComposer.getTypedWord(); // Get the word on which we should search the bigrams. If we are composing a word, it's // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we // should just skip whitespace if any, so 1. @@ -1708,13 +1725,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final CharSequence prevWord = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, mWordComposer.isComposingWord() ? 2 : 1); - SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer, + final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer, prevWord, mKeyboardSwitcher.getKeyboard().getProximityInfo(), mCurrentSettings.mCorrectionEnabled); - suggestedWords = maybeRetrieveOlderSuggestions(typedWord, suggestedWords); - - showSuggestions(suggestedWords, typedWord); - return suggestedWords; + return maybeRetrieveOlderSuggestions(typedWord, suggestedWords); } private SuggestedWords maybeRetrieveOlderSuggestions(final CharSequence typedWord, @@ -1728,10 +1742,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // revert to suggestions - although it is unclear how we can come here if it's displayed. if (suggestedWords.size() > 1 || typedWord.length() <= 1 || !suggestedWords.mTypedWordValid - || mSuggestionsView.isShowingAddToDictionaryHint()) { + || mSuggestionStripView.isShowingAddToDictionaryHint()) { return suggestedWords; } else { - SuggestedWords previousSuggestions = mSuggestionsView.getSuggestions(); + SuggestedWords previousSuggestions = mSuggestionStripView.getSuggestions(); if (previousSuggestions == mCurrentSettings.mSuggestPuncList) { previousSuggestions = SuggestedWords.EMPTY; } @@ -1747,10 +1761,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void showSuggestions(final SuggestedWords suggestedWords, + private void showSuggestionStrip(final SuggestedWords suggestedWords, final CharSequence typedWord) { if (null == suggestedWords || suggestedWords.size() <= 0) { - clearSuggestions(); + clearSuggestionStrip(); return; } final CharSequence autoCorrection; @@ -1765,7 +1779,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } mWordComposer.setAutoCorrection(autoCorrection); final boolean isAutoCorrection = suggestedWords.willAutoCorrect(); - setSuggestions(suggestedWords, isAutoCorrection); + setSuggestionStrip(suggestedWords, isAutoCorrection); setAutoCorrectionIndicator(isAutoCorrection); setSuggestionStripShown(isSuggestionsStripVisible()); } @@ -1773,7 +1787,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void commitCurrentAutoCorrection(final int separatorCodePoint) { // Complete any pending suggestions query first if (mHandler.hasPendingUpdateSuggestions()) { - updateSuggestionsOrPredictions(); + updateSuggestionStrip(); } final CharSequence typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull(); final String typedWord = mWordComposer.getTypedWord(); @@ -1802,11 +1816,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - // Called from SuggestionsView through the SuggestionsView.Listener interface + // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} + // interface @Override public void pickSuggestionManually(final int index, final CharSequence suggestion, final int x, final int y) { - final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions(); + final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput if (suggestion.length() == 1 && isShowingPunctuationList()) { // Word separators are suggested before the user inputs something. @@ -1838,8 +1853,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mCurrentSettings.isApplicationSpecifiedCompletionsOn() && mApplicationSpecifiedCompletions != null && index >= 0 && index < mApplicationSpecifiedCompletions.length) { - if (mSuggestionsView != null) { - mSuggestionsView.clear(); + if (mSuggestionStripView != null) { + mSuggestionStripView.clear(); } mKeyboardSwitcher.updateShiftState(); resetComposingState(true /* alsoResetLastComposedWord */); @@ -1872,23 +1887,18 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mKeyboardSwitcher.updateShiftState(); // We should show the "Touch again to save" hint if the user pressed the first entry - // AND either: - // - There is no dictionary (we know that because we tried to load it => null != mSuggest - // AND mSuggest.hasMainDictionary() is false) - // - There is a dictionary and the word is not in it + // AND it's in none of our current dictionaries (main, user or otherwise). // Please note that if mSuggest is null, it means that everything is off: suggestion // and correction, so we shouldn't try to show the hint final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null - // If there is no dictionary the hint should be shown. - && (!mSuggest.hasMainDictionary() - // If "suggestion" is not in the dictionary, the hint should be shown. - || !AutoCorrection.isValidWord( - mSuggest.getUnigramDictionaries(), suggestion, true)); + // If the suggestion is not in the dictionary, the hint should be shown. + && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true); Utils.Stats.onSeparator((char)Keyboard.CODE_SPACE, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) { - mSuggestionsView.showAddToDictionaryHint(suggestion, mCurrentSettings.mHintToSaveText); + mSuggestionStripView.showAddToDictionaryHint( + suggestion, mCurrentSettings.mHintToSaveText); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. mHandler.postUpdateSuggestionStrip(); @@ -1900,7 +1910,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen */ private void commitChosenWord(final CharSequence chosenWord, final int commitType, final int separatorCode) { - final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions(); + final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1); if (ProductionFlag.IS_EXPERIMENTAL) { @@ -1918,9 +1928,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void setPunctuationSuggestions() { if (mCurrentSettings.mBigramPredictionEnabled) { - clearSuggestions(); + clearSuggestionStrip(); } else { - setSuggestions(mCurrentSettings.mSuggestPuncList, false); + setSuggestionStrip(mCurrentSettings.mSuggestPuncList, false); } setAutoCorrectionIndicator(false); setSuggestionStripShown(isSuggestionsStripVisible()); @@ -2102,18 +2112,26 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen }; private void launchSettings() { - launchSettingsClass(SettingsActivity.class); + handleClose(); + launchSubActivity(SettingsActivity.class); } // Called from debug code only public void launchDebugSettings() { - launchSettingsClass(DebugSettingsActivity.class); + handleClose(); + launchSubActivity(DebugSettingsActivity.class); } - private void launchSettingsClass(Class<? extends PreferenceActivity> settingsClass) { - handleClose(); + public void launchKeyboardedDialogActivity(Class<? extends Activity> activityClass) { + // Put the text in the attached EditText into a safe, saved state before switching to a + // new activity that will also use the soft keyboard. + commitTyped(LastComposedWord.NOT_A_SEPARATOR); + launchSubActivity(activityClass); + } + + private void launchSubActivity(Class<? extends Activity> activityClass) { Intent intent = new Intent(); - intent.setClass(LatinIME.this, settingsClass); + intent.setClass(LatinIME.this, activityClass); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); } @@ -2151,7 +2169,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen showOptionDialog(builder.create()); } - /* package */ void showOptionDialog(AlertDialog dialog) { + public void showOptionDialog(AlertDialog dialog) { final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken(); if (windowToken == null) return; diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java new file mode 100644 index 000000000..387d45a53 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/ResizableIntArray.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import java.util.Arrays; + +// TODO: This class is not thread-safe. +public class ResizableIntArray { + private int[] mArray; + private int mLength; + + public ResizableIntArray(final int capacity) { + reset(capacity); + } + + public int get(final int index) { + if (index < mLength) { + return mArray[index]; + } + throw new ArrayIndexOutOfBoundsException("length=" + mLength + "; index=" + index); + } + + public void add(final int index, final int val) { + if (index < mLength) { + mArray[index] = val; + } else { + mLength = index; + add(val); + } + } + + public void add(final int val) { + final int currentLength = mLength; + ensureCapacity(currentLength + 1); + mArray[currentLength] = val; + mLength = currentLength + 1; + } + + /** + * Calculate the new capacity of {@code mArray}. + * @param minimumCapacity the minimum capacity that the {@code mArray} should have. + * @return the new capacity that the {@code mArray} should have. Returns zero when there is no + * need to expand {@code mArray}. + */ + private int calculateCapacity(final int minimumCapacity) { + final int currentCapcity = mArray.length; + if (currentCapcity < minimumCapacity) { + final int nextCapacity = currentCapcity * 2; + // The following is the same as return Math.max(minimumCapacity, nextCapacity); + return minimumCapacity > nextCapacity ? minimumCapacity : nextCapacity; + } + return 0; + } + + private void ensureCapacity(final int minimumCapacity) { + final int newCapacity = calculateCapacity(minimumCapacity); + if (newCapacity > 0) { + // TODO: Implement primitive array pool. + mArray = Arrays.copyOf(mArray, newCapacity); + } + } + + public int getLength() { + return mLength; + } + + public void setLength(final int newLength) { + ensureCapacity(newLength); + mLength = newLength; + } + + public void reset(final int capacity) { + // TODO: Implement primitive array pool. + mArray = new int[capacity]; + mLength = 0; + } + + public int[] getPrimitiveArray() { + return mArray; + } + + public void set(final ResizableIntArray ip) { + // TODO: Implement primitive array pool. + mArray = ip.mArray; + mLength = ip.mLength; + } + + public void copy(final ResizableIntArray ip) { + final int newCapacity = calculateCapacity(ip.mLength); + if (newCapacity > 0) { + // TODO: Implement primitive array pool. + mArray = new int[newCapacity]; + } + System.arraycopy(ip.mArray, 0, mArray, 0, ip.mLength); + mLength = ip.mLength; + } + + public void append(final ResizableIntArray src, final int startPos, final int length) { + if (length == 0) { + return; + } + final int currentLength = mLength; + final int newLength = currentLength + length; + ensureCapacity(newLength); + System.arraycopy(src.mArray, startPos, mArray, currentLength, length); + mLength = newLength; + } + + public void fill(final int value, final int startPos, final int length) { + if (startPos < 0 || length < 0) { + throw new IllegalArgumentException("startPos=" + startPos + "; length=" + length); + } + final int endPos = startPos + length; + ensureCapacity(endPos); + Arrays.fill(mArray, startPos, endPos, value); + if (mLength < endPos) { + mLength = endPos; + } + } +} diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 5786978a8..8b4c17322 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -28,6 +28,7 @@ import android.view.inputmethod.InputConnection; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; import java.util.regex.Pattern; diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 70acdc771..45608f439 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -39,6 +39,7 @@ import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; import com.android.inputmethodcommon.InputMethodSettingsFragment; public class Settings extends InputMethodSettingsFragment @@ -70,6 +71,7 @@ public class Settings extends InputMethodSettingsFragment "pref_key_preview_popup_dismiss_delay"; public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict"; public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction"; + public static final String PREF_GESTURE_INPUT = "gesture_input"; public static final String PREF_VIBRATION_DURATION_SETTINGS = "pref_vibration_duration_settings"; public static final String PREF_KEYPRESS_SOUND_VOLUME = @@ -196,6 +198,12 @@ public class Settings extends InputMethodSettingsFragment textCorrectionGroup.removePreference(dictionaryLink); } + final boolean gestureInputEnabledByBuildConfig = res.getBoolean( + R.bool.config_gesture_input_enabled_by_build_config); + if (!gestureInputEnabledByBuildConfig) { + final Preference gestureInputPref = findPreference(PREF_GESTURE_INPUT); + miscSettings.removePreference(gestureInputPref); + } final boolean showUsabilityStudyModeOption = res.getBoolean(R.bool.config_enable_usability_study_mode_option) || ProductionFlag.IS_EXPERIMENTAL || ENABLE_EXPERIMENTAL_SETTINGS; @@ -208,7 +216,8 @@ public class Settings extends InputMethodSettingsFragment if (ProductionFlag.IS_EXPERIMENTAL) { if (usabilityStudyPref instanceof CheckBoxPreference) { CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref; - checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE, true)); + checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE, + ResearchLogger.DEFAULT_USABILITY_STUDY_MODE)); checkbox.setSummary(R.string.settings_warning_researcher_mode); } } diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 10025daf8..3ed981375 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -83,6 +83,7 @@ public class SettingsValues { @SuppressWarnings("unused") // TODO: Use this private final float mKeypressSoundVolumeRawValue; private final InputMethodSubtype[] mAdditionalSubtypes; + public final boolean mGestureInputEnabled; // From the input box private final InputAttributes mInputAttributes; @@ -169,6 +170,10 @@ public class SettingsValues { mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain); mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray( getPrefAdditionalSubtypes(prefs, res)); + final boolean gestureInputEnabledByBuildConfig = res.getBoolean( + R.bool.config_gesture_input_enabled_by_build_config); + mGestureInputEnabled = gestureInputEnabledByBuildConfig + && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true); mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect; mSuggestionVisibility = createSuggestionVisibility(res); } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 664de6774..a7a5fcb5f 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -98,9 +98,9 @@ public class SubtypeSwitcher { mConnectivityManager = (ConnectivityManager) service.getSystemService( Context.CONNECTIVITY_SERVICE); mCurrentSystemLocale = mResources.getConfiguration().locale; - mCurrentSubtype = mImm.getCurrentInputMethodSubtype(); mNoLanguageSubtype = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( service, SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); + mCurrentSubtype = ImfUtils.getCurrentInputMethodSubtype(service, mNoLanguageSubtype); if (mNoLanguageSubtype == null) { throw new RuntimeException("Can't find no lanugage with QWERTY subtype"); } @@ -113,7 +113,7 @@ public class SubtypeSwitcher { // Only configuration changed event is allowed to call this because this is heavy. private void updateAllParameters() { mCurrentSystemLocale = mResources.getConfiguration().locale; - updateSubtype(mImm.getCurrentInputMethodSubtype()); + updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype)); updateParametersOnStartInputView(); } @@ -142,7 +142,7 @@ public class SubtypeSwitcher { + currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue()); Log.w(TAG, "Last subtype was disabled. Update to the current one."); } - updateSubtype(mImm.getCurrentInputMethodSubtype()); + updateSubtype(ImfUtils.getCurrentInputMethodSubtype(mService, mNoLanguageSubtype)); } } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index ca9dbaf05..6d346d179 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -33,7 +33,7 @@ public class WordComposer { private static final int N = BinaryDictionary.MAX_WORD_LENGTH; private int[] mPrimaryKeyCodes; - private final InputPointers mInputPointers = new InputPointers(); + private final InputPointers mInputPointers = new InputPointers(N); private final StringBuilder mTypedWord; private CharSequence mAutoCorrection; private boolean mIsResumed; diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java index c6fe43b69..58b01aa55 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -42,10 +42,10 @@ public class MoreSuggestions extends Keyboard { private int mToPos; public static class MoreSuggestionsParam extends Keyboard.Params { - private final int[] mWidths = new int[SuggestionsView.MAX_SUGGESTIONS]; - private final int[] mRowNumbers = new int[SuggestionsView.MAX_SUGGESTIONS]; - private final int[] mColumnOrders = new int[SuggestionsView.MAX_SUGGESTIONS]; - private final int[] mNumColumnsInRow = new int[SuggestionsView.MAX_SUGGESTIONS]; + private final int[] mWidths = new int[SuggestionStripView.MAX_SUGGESTIONS]; + private final int[] mRowNumbers = new int[SuggestionStripView.MAX_SUGGESTIONS]; + private final int[] mColumnOrders = new int[SuggestionStripView.MAX_SUGGESTIONS]; + private final int[] mNumColumnsInRow = new int[SuggestionStripView.MAX_SUGGESTIONS]; private static final int MAX_COLUMNS_IN_ROW = 3; private int mNumRows; public Drawable mDivider; @@ -63,7 +63,7 @@ public class MoreSuggestions extends Keyboard { int row = 0; int pos = fromPos, rowStartPos = fromPos; - final int size = Math.min(suggestions.size(), SuggestionsView.MAX_SUGGESTIONS); + final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS); while (pos < size) { final String word = suggestions.getWord(pos).toString(); // TODO: Should take care of text x-scaling. diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java index 19287e3f3..5b23d7f3c 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java @@ -68,7 +68,7 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel { @Override public void onCodeInput(int primaryCode, int x, int y) { final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE; - if (index >= 0 && index < SuggestionsView.MAX_SUGGESTIONS) { + if (index >= 0 && index < SuggestionStripView.MAX_SUGGESTIONS) { mListener.onCustomRequest(index); } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 4d33f4ba5..b57ffd2de 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -60,15 +60,15 @@ import com.android.inputmethod.keyboard.ViewLayoutUtils; import com.android.inputmethod.latin.AutoCorrection; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResearchLogger; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; -public class SuggestionsView extends RelativeLayout implements OnClickListener, +public class SuggestionStripView extends RelativeLayout implements OnClickListener, OnLongClickListener { public interface Listener { public boolean addWordToUserDictionary(String word); @@ -98,24 +98,24 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, private Listener mListener; private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - private final SuggestionsViewParams mParams; + private final SuggestionStripViewParams mParams; private static final float MIN_TEXT_XSCALE = 0.70f; private final UiHandler mHandler = new UiHandler(this); - private static class UiHandler extends StaticInnerHandlerWrapper<SuggestionsView> { + private static class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> { private static final int MSG_HIDE_PREVIEW = 0; - public UiHandler(SuggestionsView outerInstance) { + public UiHandler(SuggestionStripView outerInstance) { super(outerInstance); } @Override public void dispatchMessage(Message msg) { - final SuggestionsView suggestionsView = getOuterInstance(); + final SuggestionStripView suggestionStripView = getOuterInstance(); switch (msg.what) { case MSG_HIDE_PREVIEW: - suggestionsView.hidePreview(); + suggestionStripView.hidePreview(); break; } } @@ -129,7 +129,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } } - private static class SuggestionsViewParams { + private static class SuggestionStripViewParams { private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3; private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40; private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2; @@ -175,7 +175,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, private final TextView mLeftwardsArrowView; private final TextView mHintToSaveView; - public SuggestionsViewParams(Context context, AttributeSet attrs, int defStyle, + public SuggestionStripViewParams(Context context, AttributeSet attrs, int defStyle, ArrayList<TextView> words, ArrayList<View> dividers, ArrayList<TextView> infos) { mWords = words; mDividers = dividers; @@ -191,38 +191,39 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, final Resources res = word.getResources(); mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height); - final TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.SuggestionsView, defStyle, R.style.SuggestionsViewStyle); - mSuggestionStripOption = a.getInt(R.styleable.SuggestionsView_suggestionStripOption, 0); + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle); + mSuggestionStripOption = a.getInt( + R.styleable.SuggestionStripView_suggestionStripOption, 0); final float alphaValidTypedWord = getPercent(a, - R.styleable.SuggestionsView_alphaValidTypedWord, 100); + R.styleable.SuggestionStripView_alphaValidTypedWord, 100); final float alphaTypedWord = getPercent(a, - R.styleable.SuggestionsView_alphaTypedWord, 100); + R.styleable.SuggestionStripView_alphaTypedWord, 100); final float alphaAutoCorrect = getPercent(a, - R.styleable.SuggestionsView_alphaAutoCorrect, 100); + R.styleable.SuggestionStripView_alphaAutoCorrect, 100); final float alphaSuggested = getPercent(a, - R.styleable.SuggestionsView_alphaSuggested, 100); - mAlphaObsoleted = getPercent(a, R.styleable.SuggestionsView_alphaSuggested, 100); - mColorValidTypedWord = applyAlpha( - a.getColor(R.styleable.SuggestionsView_colorValidTypedWord, 0), - alphaValidTypedWord); - mColorTypedWord = applyAlpha( - a.getColor(R.styleable.SuggestionsView_colorTypedWord, 0), alphaTypedWord); - mColorAutoCorrect = applyAlpha( - a.getColor(R.styleable.SuggestionsView_colorAutoCorrect, 0), alphaAutoCorrect); - mColorSuggested = applyAlpha( - a.getColor(R.styleable.SuggestionsView_colorSuggested, 0), alphaSuggested); + R.styleable.SuggestionStripView_alphaSuggested, 100); + mAlphaObsoleted = getPercent(a, + R.styleable.SuggestionStripView_alphaSuggested, 100); + mColorValidTypedWord = applyAlpha(a.getColor( + R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord); + mColorTypedWord = applyAlpha(a.getColor( + R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord); + mColorAutoCorrect = applyAlpha(a.getColor( + R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect); + mColorSuggested = applyAlpha(a.getColor( + R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested); mSuggestionsCountInStrip = a.getInt( - R.styleable.SuggestionsView_suggestionsCountInStrip, + R.styleable.SuggestionStripView_suggestionsCountInStrip, DEFAULT_SUGGESTIONS_COUNT_IN_STRIP); mCenterSuggestionWeight = getPercent(a, - R.styleable.SuggestionsView_centerSuggestionPercentile, + R.styleable.SuggestionStripView_centerSuggestionPercentile, DEFAULT_CENTER_SUGGESTION_PERCENTILE); mMaxMoreSuggestionsRow = a.getInt( - R.styleable.SuggestionsView_maxMoreSuggestionsRow, + R.styleable.SuggestionStripView_maxMoreSuggestionsRow, DEFAULT_MAX_MORE_SUGGESTIONS_ROW); mMinMoreSuggestionsWidth = getRatio(a, - R.styleable.SuggestionsView_minMoreSuggestionsWidth); + R.styleable.SuggestionStripView_minMoreSuggestionsWidth); a.recycle(); mMoreSuggestionsHint = getMoreSuggestionsHint(res, @@ -596,15 +597,15 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } /** - * Construct a {@link SuggestionsView} for showing suggestions to be picked by the user. + * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user. * @param context * @param attrs */ - public SuggestionsView(Context context, AttributeSet attrs) { - this(context, attrs, R.attr.suggestionsViewStyle); + public SuggestionStripView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.suggestionStripViewStyle); } - public SuggestionsView(Context context, AttributeSet attrs, int defStyle) { + public SuggestionStripView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); final LayoutInflater inflater = LayoutInflater.from(context); @@ -631,7 +632,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null)); } - mParams = new SuggestionsViewParams(context, attrs, defStyle, mWords, mDividers, mInfos); + mParams = new SuggestionStripViewParams( + context, attrs, defStyle, mWords, mDividers, mInfos); mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null); mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer @@ -677,7 +679,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, mSuggestedWords = suggestedWords; mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth()); if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.suggestionsView_setSuggestions(mSuggestedWords); + ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords); } } @@ -759,7 +761,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } private boolean showMoreSuggestions() { - final SuggestionsViewParams params = mParams; + final SuggestionStripViewParams params = mParams; if (params.mMoreSuggestionsAvailable) { final int stripWidth = getWidth(); final View container = mMoreSuggestionsContainer; diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java new file mode 100644 index 000000000..c9f3b476a --- /dev/null +++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2012 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.research; + +import android.app.Activity; +import android.os.Bundle; +import android.text.Editable; +import android.view.View; +import android.widget.CheckBox; +import android.widget.EditText; + +import com.android.inputmethod.latin.R; + +public class FeedbackActivity extends Activity { + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.research_feedback_activity); + final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout); + layout.setActivity(this); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + public void onBackPressed() { + ResearchLogger.getInstance().onLeavingSendFeedbackDialog(); + super.onBackPressed(); + } +} diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java new file mode 100644 index 000000000..a2e08e2b7 --- /dev/null +++ b/java/src/com/android/inputmethod/research/FeedbackFragment.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2012 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.research; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Bundle; +import android.text.Editable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; + +import com.android.inputmethod.latin.R; + +public class FeedbackFragment extends Fragment { + private EditText mEditText; + private CheckBox mCheckBox; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + final View view = inflater.inflate(R.layout.research_feedback_fragment_layout, container, + false); + mEditText = (EditText) view.findViewById(R.id.research_feedback_contents); + mCheckBox = (CheckBox) view.findViewById(R.id.research_feedback_include_history); + + final Button sendButton = (Button) view.findViewById( + R.id.research_feedback_send_button); + sendButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final Editable editable = mEditText.getText(); + final String feedbackContents = editable.toString(); + final boolean includeHistory = mCheckBox.isChecked(); + ResearchLogger.getInstance().sendFeedback(feedbackContents, includeHistory); + final Activity activity = FeedbackFragment.this.getActivity(); + activity.finish(); + ResearchLogger.getInstance().onLeavingSendFeedbackDialog(); + } + }); + + final Button cancelButton = (Button) view.findViewById( + R.id.research_feedback_cancel_button); + cancelButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + final Activity activity = FeedbackFragment.this.getActivity(); + activity.finish(); + ResearchLogger.getInstance().onLeavingSendFeedbackDialog(); + } + }); + + return view; + } +} diff --git a/java/src/com/android/inputmethod/research/FeedbackLayout.java b/java/src/com/android/inputmethod/research/FeedbackLayout.java new file mode 100644 index 000000000..f2cbfe308 --- /dev/null +++ b/java/src/com/android/inputmethod/research/FeedbackLayout.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2012 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.research; + +import android.app.Activity; +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.LinearLayout; + +public class FeedbackLayout extends LinearLayout { + private Activity mActivity; + + public FeedbackLayout(Context context) { + super(context); + } + + public FeedbackLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FeedbackLayout(Context context, AttributeSet attrs, int defstyle) { + super(context, attrs, defstyle); + } + + public void setActivity(Activity activity) { + mActivity = activity; + } + + @Override + public boolean dispatchKeyEventPreIme(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + state.startTracking(event, this); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP + && !event.isCanceled() && state.isTracking(event)) { + mActivity.onBackPressed(); + return true; + } + } + } + return super.dispatchKeyEventPreIme(event); + } +} diff --git a/java/src/com/android/inputmethod/latin/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java index 1de5cb36a..18bf3c07f 100644 --- a/java/src/com/android/inputmethod/latin/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -14,7 +14,7 @@ * the License. */ -package com.android.inputmethod.latin; +package com.android.inputmethod.research; import android.content.SharedPreferences; import android.os.SystemClock; @@ -23,9 +23,10 @@ import android.util.Log; import android.view.inputmethod.CompletionInfo; import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.ResearchLogger.LogUnit; +import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.research.ResearchLogger.LogUnit; import java.io.BufferedWriter; import java.io.File; @@ -55,13 +56,14 @@ public class ResearchLog { final ScheduledExecutorService mExecutor; /* package */ final File mFile; - private JsonWriter mJsonWriter = NULL_JSON_WRITER; // should never be null + private JsonWriter mJsonWriter = NULL_JSON_WRITER; private int mLoggingState; private static final int LOGGING_STATE_UNSTARTED = 0; - private static final int LOGGING_STATE_RUNNING = 1; - private static final int LOGGING_STATE_STOPPING = 2; - private static final int LOGGING_STATE_STOPPED = 3; + private static final int LOGGING_STATE_READY = 1; // don't create file until necessary + private static final int LOGGING_STATE_RUNNING = 2; + private static final int LOGGING_STATE_STOPPING = 3; + private static final int LOGGING_STATE_STOPPED = 4; private static final long FLUSH_DELAY_IN_MS = 1000 * 5; private static class NullOutputStream extends OutputStream { @@ -94,11 +96,9 @@ public class ResearchLog { public synchronized void start() throws IOException { switch (mLoggingState) { case LOGGING_STATE_UNSTARTED: - mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile))); - mJsonWriter.setLenient(true); - mJsonWriter.beginArray(); - mLoggingState = LOGGING_STATE_RUNNING; + mLoggingState = LOGGING_STATE_READY; break; + case LOGGING_STATE_READY: case LOGGING_STATE_RUNNING: case LOGGING_STATE_STOPPING: case LOGGING_STATE_STOPPED: @@ -111,6 +111,7 @@ public class ResearchLog { case LOGGING_STATE_UNSTARTED: mLoggingState = LOGGING_STATE_STOPPED; break; + case LOGGING_STATE_READY: case LOGGING_STATE_RUNNING: mExecutor.submit(new Callable<Object>() { @Override @@ -120,14 +121,13 @@ public class ResearchLog { mJsonWriter.flush(); mJsonWriter.close(); } finally { - // the contentprovider only exports data if the writable - // bit is cleared. boolean success = mFile.setWritable(false, false); mLoggingState = LOGGING_STATE_STOPPED; } return null; } }); + removeAnyScheduledFlush(); mExecutor.shutdown(); mLoggingState = LOGGING_STATE_STOPPING; break; @@ -139,27 +139,26 @@ public class ResearchLog { public boolean isAlive() { switch (mLoggingState) { case LOGGING_STATE_UNSTARTED: + case LOGGING_STATE_READY: case LOGGING_STATE_RUNNING: return true; } return false; } - public void waitUntilStopped(int timeoutInMs) throws InterruptedException { + public void waitUntilStopped(final int timeoutInMs) throws InterruptedException { + removeAnyScheduledFlush(); + mExecutor.shutdown(); mExecutor.awaitTermination(timeoutInMs, TimeUnit.MILLISECONDS); } - private boolean isAbortSuccessful; - public boolean isAbortSuccessful() { - return isAbortSuccessful; - } - public synchronized void abort() { switch (mLoggingState) { case LOGGING_STATE_UNSTARTED: mLoggingState = LOGGING_STATE_STOPPED; isAbortSuccessful = true; break; + case LOGGING_STATE_READY: case LOGGING_STATE_RUNNING: mExecutor.submit(new Callable<Object>() { @Override @@ -173,6 +172,7 @@ public class ResearchLog { return null; } }); + removeAnyScheduledFlush(); mExecutor.shutdown(); mLoggingState = LOGGING_STATE_STOPPING; break; @@ -181,10 +181,16 @@ public class ResearchLog { } } + private boolean isAbortSuccessful; + public boolean isAbortSuccessful() { + return isAbortSuccessful; + } + /* package */ synchronized void flush() { switch (mLoggingState) { case LOGGING_STATE_UNSTARTED: break; + case LOGGING_STATE_READY: case LOGGING_STATE_RUNNING: removeAnyScheduledFlush(); mExecutor.submit(mFlushCallable); @@ -197,7 +203,9 @@ public class ResearchLog { private Callable<Object> mFlushCallable = new Callable<Object>() { @Override public Object call() throws Exception { - mJsonWriter.flush(); + if (mLoggingState == LOGGING_STATE_RUNNING) { + mJsonWriter.flush(); + } return null; } }; @@ -220,6 +228,7 @@ public class ResearchLog { switch (mLoggingState) { case LOGGING_STATE_UNSTARTED: break; + case LOGGING_STATE_READY: case LOGGING_STATE_RUNNING: mExecutor.submit(new Callable<Object>() { @Override @@ -239,6 +248,7 @@ public class ResearchLog { switch (mLoggingState) { case LOGGING_STATE_UNSTARTED: break; + case LOGGING_STATE_READY: case LOGGING_STATE_RUNNING: mExecutor.submit(new Callable<Object>() { @Override @@ -260,6 +270,11 @@ public class ResearchLog { void outputEvent(final String[] keys, final Object[] values) { // not thread safe. try { + if (mJsonWriter == NULL_JSON_WRITER) { + mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile))); + mJsonWriter.setLenient(true); + mJsonWriter.beginArray(); + } mJsonWriter.beginObject(); mJsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); mJsonWriter.name(UPTIME_KEY).value(SystemClock.uptimeMillis()); diff --git a/java/src/com/android/inputmethod/research/ResearchLogUploader.java b/java/src/com/android/inputmethod/research/ResearchLogUploader.java new file mode 100644 index 000000000..3b1213009 --- /dev/null +++ b/java/src/com/android/inputmethod/research/ResearchLogUploader.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2012 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.research; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.BatteryManager; +import android.util.Log; + +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.R.string; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public final class ResearchLogUploader { + private static final String TAG = ResearchLogUploader.class.getSimpleName(); + private static final int UPLOAD_INTERVAL_IN_MS = 1000 * 60 * 15; // every 15 min + private static final int BUF_SIZE = 1024 * 8; + + private final boolean mCanUpload; + private final Context mContext; + private final File mFilesDir; + private final URL mUrl; + private final ScheduledExecutorService mExecutor; + + private Runnable doUploadRunnable = new UploadRunnable(null, false); + + public ResearchLogUploader(final Context context, final File filesDir) { + mContext = context; + mFilesDir = filesDir; + final PackageManager packageManager = context.getPackageManager(); + final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET, + context.getPackageName()) == PackageManager.PERMISSION_GRANTED; + if (!hasPermission) { + mCanUpload = false; + mUrl = null; + mExecutor = null; + return; + } + URL tempUrl = null; + boolean canUpload = false; + ScheduledExecutorService executor = null; + try { + final String urlString = context.getString(R.string.research_logger_upload_url); + if (urlString == null || urlString.equals("")) { + return; + } + tempUrl = new URL(urlString); + canUpload = true; + executor = Executors.newSingleThreadScheduledExecutor(); + } catch (MalformedURLException e) { + tempUrl = null; + e.printStackTrace(); + return; + } finally { + mCanUpload = canUpload; + mUrl = tempUrl; + mExecutor = executor; + } + } + + public void start() { + if (mCanUpload) { + Log.d(TAG, "scheduling regular uploading"); + mExecutor.scheduleWithFixedDelay(doUploadRunnable, UPLOAD_INTERVAL_IN_MS, + UPLOAD_INTERVAL_IN_MS, TimeUnit.MILLISECONDS); + } else { + Log.d(TAG, "no permission to upload"); + } + } + + public void uploadNow(final Callback callback) { + // Perform an immediate upload. Note that this should happen even if there is + // another upload happening right now, as it may have missed the latest changes. + // TODO: Reschedule regular upload tests starting from now. + if (mCanUpload) { + mExecutor.submit(new UploadRunnable(callback, true)); + } + } + + public interface Callback { + public void onUploadCompleted(final boolean success); + } + + private boolean isExternallyPowered() { + final Intent intent = mContext.registerReceiver(null, new IntentFilter( + Intent.ACTION_BATTERY_CHANGED)); + final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + return pluggedState == BatteryManager.BATTERY_PLUGGED_AC + || pluggedState == BatteryManager.BATTERY_PLUGGED_USB; + } + + private boolean hasWifiConnection() { + final ConnectivityManager manager = + (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); + final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + return wifiInfo.isConnected(); + } + + class UploadRunnable implements Runnable { + private final Callback mCallback; + private final boolean mForceUpload; + + public UploadRunnable(final Callback callback, final boolean forceUpload) { + mCallback = callback; + mForceUpload = forceUpload; + } + + @Override + public void run() { + doUpload(); + } + + private void doUpload() { + if (!mForceUpload && (!isExternallyPowered() || !hasWifiConnection())) { + return; + } + if (mFilesDir == null) { + return; + } + final File[] files = mFilesDir.listFiles(new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.getName().startsWith(ResearchLogger.FILENAME_PREFIX) + && !pathname.canWrite(); + } + }); + boolean success = true; + if (files.length == 0) { + success = false; + } + for (final File file : files) { + if (!uploadFile(file)) { + success = false; + } + } + if (mCallback != null) { + mCallback.onUploadCompleted(success); + } + } + + private boolean uploadFile(File file) { + Log.d(TAG, "attempting upload of " + file.getAbsolutePath()); + boolean success = false; + final int contentLength = (int) file.length(); + HttpURLConnection connection = null; + InputStream fileIs = null; + try { + fileIs = new FileInputStream(file); + connection = (HttpURLConnection) mUrl.openConnection(); + connection.setRequestMethod("PUT"); + connection.setDoOutput(true); + connection.setFixedLengthStreamingMode(contentLength); + final OutputStream os = connection.getOutputStream(); + final byte[] buf = new byte[BUF_SIZE]; + int numBytesRead; + while ((numBytesRead = fileIs.read(buf)) != -1) { + os.write(buf, 0, numBytesRead); + } + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + Log.d(TAG, "upload failed: " + connection.getResponseCode()); + InputStream netIs = connection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(netIs)); + String line; + while ((line = reader.readLine()) != null) { + Log.d(TAG, "| " + reader.readLine()); + } + reader.close(); + return success; + } + file.delete(); + success = true; + Log.d(TAG, "upload successful"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (fileIs != null) { + try { + fileIs.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (connection != null) { + connection.disconnect(); + } + } + return success; + } + } +} diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 1abfbad13..68bd98a23 100644 --- a/java/src/com/android/inputmethod/latin/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -14,37 +14,56 @@ * the License. */ -package com.android.inputmethod.latin; +package com.android.inputmethod.research; import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; import android.app.AlertDialog; +import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Style; import android.inputmethodservice.InputMethodService; import android.os.Build; +import android.os.IBinder; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.Window; +import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; +import android.widget.Button; import android.widget.Toast; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.MainKeyboardView; +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.RichInputConnection; import com.android.inputmethod.latin.RichInputConnection.Range; +import com.android.inputmethod.latin.Suggest; +import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.define.ProductionFlag; import java.io.File; -import java.io.FileFilter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -64,19 +83,23 @@ import java.util.UUID; public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = ResearchLogger.class.getSimpleName(); private static final boolean OUTPUT_ENTIRE_BUFFER = false; // true may disclose private info + public static final boolean DEFAULT_USABILITY_STUDY_MODE = false; /* package */ static boolean sIsLogging = false; private static final int OUTPUT_FORMAT_VERSION = 1; private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; - private static final String FILENAME_PREFIX = "researchLog"; + private static final String PREF_RESEARCH_HAS_SEEN_SPLASH = "pref_research_has_seen_splash"; + /* package */ static final String FILENAME_PREFIX = "researchLog"; private static final String FILENAME_SUFFIX = ".txt"; private static final SimpleDateFormat TIMESTAMP_DATEFORMAT = new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US); + private static final boolean IS_SHOWING_INDICATOR = true; + private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false; // constants related to specific log points private static final String WHITESPACE_SEPARATORS = " \t\n\r"; private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1 private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid"; - private static final int ABORT_TIMEOUT_IN_MS = 10 * 1000; + private static final int ABORT_TIMEOUT_IN_MS = 10 * 1000; // timeout to notify user private static final ResearchLogger sInstance = new ResearchLogger(); // to write to a different filename, e.g., for testing, set mFile before calling start() @@ -88,10 +111,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // the system to do so. /* package */ ResearchLog mIntentionalResearchLog; // LogUnits are queued here and released only when the user requests the intentional log. - private final List<LogUnit> mIntentionalResearchLogQueue = new ArrayList<LogUnit>(); + private List<LogUnit> mIntentionalResearchLogQueue = new ArrayList<LogUnit>(); private boolean mIsPasswordView = false; private boolean mIsLoggingSuspended = false; + private SharedPreferences mPrefs; // digits entered by the user are replaced with this codepoint. /* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT = @@ -101,6 +125,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private static final String PREF_LAST_CLEANUP_TIME = "pref_last_cleanup_time"; private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS; private static final long MAX_LOGFILE_AGE_IN_MS = DateUtils.DAY_IN_MILLIS; + protected static final int SUSPEND_DURATION_IN_MINUTES = 1; // set when LatinIME should ignore an onUpdateSelection() callback that // arises from operations in this class private static boolean sLatinIMEExpectingUpdateSelection = false; @@ -109,7 +134,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private Suggest mSuggest; private Dictionary mDictionary; private KeyboardSwitcher mKeyboardSwitcher; - private Context mContext; + private InputMethodService mInputMethodService; + + private ResearchLogUploader mResearchLogUploader; private ResearchLogger() { } @@ -124,7 +151,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (ims == null) { Log.w(TAG, "IMS is null; logging is off"); } else { - mContext = ims; mFilesDir = ims.getFilesDir(); if (mFilesDir == null || !mFilesDir.exists()) { Log.w(TAG, "IME storage directory does not exist."); @@ -132,6 +158,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } if (prefs != null) { mUUIDString = getUUID(prefs); + if (!prefs.contains(PREF_USABILITY_STUDY_MODE)) { + Editor e = prefs.edit(); + e.putBoolean(PREF_USABILITY_STUDY_MODE, DEFAULT_USABILITY_STUDY_MODE); + e.apply(); + } sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); prefs.registerOnSharedPreferenceChangeListener(this); @@ -145,7 +176,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang e.apply(); } } + mResearchLogUploader = new ResearchLogUploader(ims, mFilesDir); + mResearchLogUploader.start(); mKeyboardSwitcher = keyboardSwitcher; + mInputMethodService = ims; + mPrefs = prefs; } private void cleanupLoggingDir(final File dir, final long time) { @@ -157,6 +192,73 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } + public void mainKeyboardView_onAttachedToWindow() { + maybeShowSplashScreen(); + } + + private boolean hasSeenSplash() { + return mPrefs.getBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, false); + } + + private Dialog mSplashDialog = null; + + private void maybeShowSplashScreen() { + if (hasSeenSplash()) { + return; + } + if (mSplashDialog != null && mSplashDialog.isShowing()) { + return; + } + final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken(); + if (windowToken == null) { + return; + } + mSplashDialog = new Dialog(mInputMethodService, android.R.style.Theme_Holo_Dialog); + mSplashDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + mSplashDialog.setContentView(R.layout.research_splash); + mSplashDialog.setCancelable(true); + final Window w = mSplashDialog.getWindow(); + final WindowManager.LayoutParams lp = w.getAttributes(); + lp.token = windowToken; + lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; + w.setAttributes(lp); + w.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + mSplashDialog.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + mInputMethodService.requestHideSelf(0); + } + }); + final Button doNotLogButton = (Button) mSplashDialog.findViewById( + R.id.research_do_not_log_button); + doNotLogButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onUserLoggingElection(false); + mSplashDialog.dismiss(); + } + }); + final Button doLogButton = (Button) mSplashDialog.findViewById(R.id.research_do_log_button); + doLogButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onUserLoggingElection(true); + mSplashDialog.dismiss(); + } + }); + mSplashDialog.show(); + } + + public void onUserLoggingElection(final boolean enableLogging) { + setLoggingAllowed(enableLogging); + if (mPrefs == null) { + return; + } + final Editor e = mPrefs.edit(); + e.putBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, true); + e.apply(); + } + private File createLogFile(File filesDir) { final StringBuilder sb = new StringBuilder(); sb.append(FILENAME_PREFIX).append('-'); @@ -166,8 +268,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return new File(filesDir, sb.toString()); } - public void start() { - if (!sIsLogging) { + private void start() { + maybeShowSplashScreen(); + updateSuspendedState(); + requestIndicatorRedraw(); + if (!isAllowedToLog()) { // Log.w(TAG, "not in usability mode; not logging"); return; } @@ -175,10 +280,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang Log.w(TAG, "IME storage directory does not exist. Cannot start logging."); return; } - if (mMainResearchLog == null || !mMainResearchLog.isAlive()) { - mMainResearchLog = new ResearchLog(createLogFile(mFilesDir)); - } try { + if (mMainResearchLog == null || !mMainResearchLog.isAlive()) { + mMainResearchLog = new ResearchLog(createLogFile(mFilesDir)); + } mMainResearchLog.start(); if (mIntentionalResearchLog == null || !mIntentionalResearchLog.isAlive()) { mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir)); @@ -189,15 +294,26 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - public void stop() { + /* package */ void stop() { if (mMainResearchLog != null) { mMainResearchLog.stop(); } + if (mIntentionalResearchLog != null) { + mIntentionalResearchLog.stop(); + } + } + + private void setLoggingAllowed(boolean enableLogging) { + if (mPrefs == null) { + return; + } + Editor e = mPrefs.edit(); + e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging); + e.apply(); + sIsLogging = enableLogging; } public boolean abort() { - mIsLoggingSuspended = true; - requestIndicatorRedraw(); boolean didAbortMainLog = false; if (mMainResearchLog != null) { mMainResearchLog.abort(); @@ -209,6 +325,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (mMainResearchLog.isAbortSuccessful()) { didAbortMainLog = true; } + mMainResearchLog = null; } boolean didAbortIntentionalLog = false; if (mIntentionalResearchLog != null) { @@ -221,6 +338,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (mIntentionalResearchLog.isAbortSuccessful()) { didAbortIntentionalLog = true; } + mIntentionalResearchLog = null; } return didAbortMainLog && didAbortIntentionalLog; } @@ -231,19 +349,31 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - private void logWholeSessionHistory() throws IOException { - try { - LogUnit headerLogUnit = new LogUnit(); - headerLogUnit.addLogAtom(EVENTKEYS_INTENTIONAL_LOG, EVENTKEYS_NULLVALUES, false); - mIntentionalResearchLog.publishAllEvents(headerLogUnit); - for (LogUnit logUnit : mIntentionalResearchLogQueue) { - mIntentionalResearchLog.publishAllEvents(logUnit); - } - mIntentionalResearchLog.stop(); - mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir)); - mIntentionalResearchLog.start(); - } finally { - mIntentionalResearchLogQueue.clear(); + private void restart() { + stop(); + start(); + } + + private long mResumeTime = 0L; + private void suspendLoggingUntil(long time) { + mIsLoggingSuspended = true; + mResumeTime = time; + requestIndicatorRedraw(); + } + + private void resumeLogging() { + mResumeTime = 0L; + updateSuspendedState(); + requestIndicatorRedraw(); + if (isAllowedToLog()) { + restart(); + } + } + + private void updateSuspendedState() { + final long time = System.currentTimeMillis(); + if (time > mResumeTime) { + mIsLoggingSuspended = false; } } @@ -256,15 +386,21 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (sIsLogging == false) { abort(); } + requestIndicatorRedraw(); } - /* package */ void presentResearchDialog(final LatinIME latinIME) { + public void presentResearchDialog(final LatinIME latinIME) { + if (mInFeedbackDialog) { + Toast.makeText(latinIME, R.string.research_please_exit_feedback_form, + Toast.LENGTH_LONG).show(); + return; + } final CharSequence title = latinIME.getString(R.string.english_ime_research_log); + final boolean showEnable = mIsLoggingSuspended || !sIsLogging; final CharSequence[] items = new CharSequence[] { - latinIME.getString(R.string.note_timestamp_for_researchlog), - mIsLoggingSuspended ? latinIME.getString(R.string.enable_session_logging) : - latinIME.getString(R.string.do_not_log_this_session), - latinIME.getString(R.string.log_whole_session_history), + latinIME.getString(R.string.research_feedback_menu_option), + showEnable ? latinIME.getString(R.string.research_enable_session_logging) : + latinIME.getString(R.string.research_do_not_log_this_session) }; final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override @@ -272,41 +408,30 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang di.dismiss(); switch (position) { case 0: - userTimestamp(); - Toast.makeText(latinIME, R.string.notify_recorded_timestamp, - Toast.LENGTH_LONG).show(); + presentFeedbackDialog(latinIME); break; case 1: - if (mIsLoggingSuspended) { - mIsLoggingSuspended = false; - requestIndicatorRedraw(); - Toast toast = Toast.makeText(latinIME, - R.string.notify_session_logging_enabled, Toast.LENGTH_LONG); + if (showEnable) { + if (!sIsLogging) { + setLoggingAllowed(true); + } + resumeLogging(); + Toast.makeText(latinIME, + R.string.research_notify_session_logging_enabled, + Toast.LENGTH_LONG).show(); } else { Toast toast = Toast.makeText(latinIME, - R.string.notify_session_log_deleting, Toast.LENGTH_LONG); + R.string.research_notify_session_log_deleting, + Toast.LENGTH_LONG); toast.show(); boolean isLogDeleted = abort(); + final long currentTime = System.currentTimeMillis(); + final long resumeTime = currentTime + 1000 * 60 * + SUSPEND_DURATION_IN_MINUTES; + suspendLoggingUntil(resumeTime); toast.cancel(); - if (isLogDeleted) { - Toast.makeText(latinIME, R.string.notify_session_log_deleted, - Toast.LENGTH_LONG).show(); - } else { - Toast.makeText(latinIME, - R.string.notify_session_log_not_deleted, Toast.LENGTH_LONG) - .show(); - } - } - break; - case 2: - try { - logWholeSessionHistory(); - Toast.makeText(latinIME, R.string.notify_session_history_logged, + Toast.makeText(latinIME, R.string.research_notify_logging_suspended, Toast.LENGTH_LONG).show(); - } catch (IOException e) { - Toast.makeText(latinIME, R.string.notify_session_history_not_logged, - Toast.LENGTH_LONG).show(); - e.printStackTrace(); } break; } @@ -319,6 +444,83 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang latinIME.showOptionDialog(builder.create()); } + private boolean mInFeedbackDialog = false; + public void presentFeedbackDialog(LatinIME latinIME) { + mInFeedbackDialog = true; + latinIME.launchKeyboardedDialogActivity(FeedbackActivity.class); + } + + private ResearchLog mFeedbackLog; + private List<LogUnit> mFeedbackQueue; + private ResearchLog mSavedMainResearchLog; + private ResearchLog mSavedIntentionalResearchLog; + private List<LogUnit> mSavedIntentionalResearchLogQueue; + + private void saveLogsForFeedback() { + mFeedbackLog = mIntentionalResearchLog; + if (mIntentionalResearchLogQueue != null) { + mFeedbackQueue = new ArrayList<LogUnit>(mIntentionalResearchLogQueue); + } else { + mFeedbackQueue = null; + } + mSavedMainResearchLog = mMainResearchLog; + mSavedIntentionalResearchLog = mIntentionalResearchLog; + mSavedIntentionalResearchLogQueue = mIntentionalResearchLogQueue; + + mMainResearchLog = null; + mIntentionalResearchLog = null; + mIntentionalResearchLogQueue = new ArrayList<LogUnit>(); + } + + private static final int LOG_DRAIN_TIMEOUT_IN_MS = 1000 * 5; + public void sendFeedback(final String feedbackContents, final boolean includeHistory) { + if (includeHistory && mFeedbackLog != null) { + try { + LogUnit headerLogUnit = new LogUnit(); + headerLogUnit.addLogAtom(EVENTKEYS_INTENTIONAL_LOG, EVENTKEYS_NULLVALUES, false); + mFeedbackLog.publishAllEvents(headerLogUnit); + for (LogUnit logUnit : mFeedbackQueue) { + mFeedbackLog.publishAllEvents(logUnit); + } + userFeedback(mFeedbackLog, feedbackContents); + mFeedbackLog.stop(); + try { + mFeedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + mIntentionalResearchLog = new ResearchLog(createLogFile(mFilesDir)); + mIntentionalResearchLog.start(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + mIntentionalResearchLogQueue.clear(); + } + mResearchLogUploader.uploadNow(null); + } else { + // create a separate ResearchLog just for feedback + final ResearchLog feedbackLog = new ResearchLog(createLogFile(mFilesDir)); + try { + feedbackLog.start(); + userFeedback(feedbackLog, feedbackContents); + feedbackLog.stop(); + feedbackLog.waitUntilStopped(LOG_DRAIN_TIMEOUT_IN_MS); + mResearchLogUploader.uploadNow(null); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public void onLeavingSendFeedbackDialog() { + mInFeedbackDialog = false; + mMainResearchLog = mSavedMainResearchLog; + mIntentionalResearchLog = mSavedIntentionalResearchLog; + mIntentionalResearchLogQueue = mSavedIntentionalResearchLogQueue; + } + public void initSuggest(Suggest suggest) { mSuggest = suggest; } @@ -328,13 +530,50 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } private boolean isAllowedToLog() { - return !mIsPasswordView && !mIsLoggingSuspended; + return !mIsPasswordView && !mIsLoggingSuspended && sIsLogging; } public void requestIndicatorRedraw() { - // invalidate any existing graphics - if (mKeyboardSwitcher != null) { - mKeyboardSwitcher.getKeyboardView().invalidateAllKeys(); + if (!IS_SHOWING_INDICATOR) { + return; + } + if (mKeyboardSwitcher == null) { + return; + } + final KeyboardView keyboardView = mKeyboardSwitcher.getKeyboardView(); + if (keyboardView == null) { + return; + } + keyboardView.invalidateAllKeys(); + } + + + public void paintIndicator(KeyboardView view, Paint paint, Canvas canvas, int width, + int height) { + // TODO: Reimplement using a keyboard background image specific to the ResearchLogger + // and remove this method. + // The check for MainKeyboardView ensures that a red border is only placed around + // the main keyboard, not every keyboard. + if (IS_SHOWING_INDICATOR && isAllowedToLog() && view instanceof MainKeyboardView) { + final int savedColor = paint.getColor(); + paint.setColor(Color.RED); + final Style savedStyle = paint.getStyle(); + paint.setStyle(Style.STROKE); + final float savedStrokeWidth = paint.getStrokeWidth(); + if (IS_SHOWING_INDICATOR_CLEARLY) { + paint.setStrokeWidth(5); + canvas.drawRect(0, 0, width, height, paint); + } else { + // Put a tiny red dot on the screen so a knowledgeable user can check whether + // it is enabled. The dot is actually a zero-width, zero-height rectangle, + // placed at the lower-right corner of the canvas, painted with a non-zero border + // width. + paint.setStrokeWidth(3); + canvas.drawRect(width, height, width, height, paint); + } + paint.setColor(savedColor); + paint.setStyle(savedStyle); + paint.setStrokeWidth(savedStrokeWidth); } } @@ -467,6 +706,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } private void publishLogUnit(LogUnit logUnit, boolean isPrivacySensitive) { + if (!isAllowedToLog()) { + return; + } + if (mMainResearchLog == null) { + return; + } if (isPrivacySensitive) { mMainResearchLog.publishPublicEvents(logUnit); } else { @@ -536,6 +781,18 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } + private static String getUUID(final SharedPreferences prefs) { + String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null); + if (null == uuidString) { + UUID uuid = UUID.randomUUID(); + uuidString = uuid.toString(); + Editor editor = prefs.edit(); + editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString); + editor.apply(); + } + return uuidString; + } + private String scrubWord(String word) { if (mDictionary == null) { return WORD_REPLACEMENT_STRING; @@ -546,14 +803,84 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang return WORD_REPLACEMENT_STRING; } + // Special methods related to startup, shutdown, logging itself + private static final String[] EVENTKEYS_INTENTIONAL_LOG = { "IntentionalLog" }; - private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT = { - "LatinKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size", + + private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = { + "LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions", + "fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion" + }; + public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, + final SharedPreferences prefs) { + final ResearchLogger researchLogger = getInstance(); + if (researchLogger.mInFeedbackDialog) { + researchLogger.saveLogsForFeedback(); + } + researchLogger.start(); + if (editorInfo != null) { + final Context context = researchLogger.mInputMethodService; + try { + final PackageInfo packageInfo; + packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), + 0); + final Integer versionCode = packageInfo.versionCode; + final String versionName = packageInfo.versionName; + final Object[] values = { + researchLogger.mUUIDString, editorInfo.packageName, + Integer.toHexString(editorInfo.inputType), + Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId, + Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName, + OUTPUT_FORMAT_VERSION + }; + researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values); + } catch (NameNotFoundException e) { + e.printStackTrace(); + } + } + } + + public void latinIME_onFinishInputInternal() { + stop(); + } + + private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = { + "LatinIMECommitText", "typedWord" + }; + + public static void latinIME_commitText(final CharSequence typedWord) { + final String scrubbedWord = scrubDigitsFromString(typedWord.toString()); + final Object[] values = { + scrubbedWord + }; + final ResearchLogger researchLogger = getInstance(); + researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values); + researchLogger.onWordComplete(scrubbedWord); + } + + private static final String[] EVENTKEYS_USER_FEEDBACK = { + "UserFeedback", "FeedbackContents" + }; + + private void userFeedback(ResearchLog researchLog, String feedbackContents) { + // this method is special; it directs the feedbackContents to a particular researchLog + final LogUnit logUnit = new LogUnit(); + final Object[] values = { + feedbackContents + }; + logUnit.addLogAtom(EVENTKEYS_USER_FEEDBACK, values, false); + researchLog.publishAllEvents(logUnit); + } + + // Regular logging methods + + private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT = { + "MainKeyboardViewProcessMotionEvent", "action", "eventTime", "id", "x", "y", "size", "pressure" }; - public static void latinKeyboardView_processMotionEvent(final MotionEvent me, final int action, + public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action, final long eventTime, final int index, final int id, final int x, final int y) { if (me != null) { final String actionString; @@ -573,7 +900,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang actionString, eventTime, id, x, y, size, pressure }; getInstance().enqueuePotentiallyPrivateEvent( - EVENTKEYS_LATINKEYBOARDVIEW_PROCESSMOTIONEVENT, values); + EVENTKEYS_MAINKEYBOARDVIEW_PROCESSMOTIONEVENT, values); } } @@ -611,19 +938,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang EVENTKEYS_LATINIME_COMMITCURRENTAUTOCORRECTION, values); } - private static final String[] EVENTKEYS_LATINIME_COMMITTEXT = { - "LatinIMECommitText", "typedWord" - }; - public static void latinIME_commitText(final CharSequence typedWord) { - final String scrubbedWord = scrubDigitsFromString(typedWord.toString()); - final Object[] values = { - scrubbedWord - }; - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_COMMITTEXT, values); - researchLogger.onWordComplete(scrubbedWord); - } - private static final String[] EVENTKEYS_LATINIME_DELETESURROUNDINGTEXT = { "LatinIMEDeleteSurroundingText", "length" }; @@ -653,7 +967,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang values); } - /* package */ static boolean getAndClearLatinIMEExpectingUpdateSelection() { + public static boolean getAndClearLatinIMEExpectingUpdateSelection() { boolean returnValue = sLatinIMEExpectingUpdateSelection; sLatinIMEExpectingUpdateSelection = false; return returnValue; @@ -702,51 +1016,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // Play it safe. Remove privacy-sensitive events. researchLogger.publishLogUnit(researchLogger.mCurrentLogUnit, true); researchLogger.mCurrentLogUnit = new LogUnit(); + getInstance().stop(); } } - private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = { - "LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions", - "fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion" - }; - public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, - final SharedPreferences prefs) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.start(); - if (editorInfo != null) { - final Context context = researchLogger.mContext; - try { - final PackageInfo packageInfo; - packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), - 0); - final Integer versionCode = packageInfo.versionCode; - final String versionName = packageInfo.versionName; - final Object[] values = { - researchLogger.mUUIDString, editorInfo.packageName, - Integer.toHexString(editorInfo.inputType), - Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId, - Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName, - OUTPUT_FORMAT_VERSION - }; - researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values); - } catch (NameNotFoundException e) { - e.printStackTrace(); - } - } - } - - private static String getUUID(final SharedPreferences prefs) { - String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null); - if (null == uuidString) { - UUID uuid = UUID.randomUUID(); - uuidString = uuid.toString(); - Editor editor = prefs.edit(); - editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString); - editor.apply(); - } - return uuidString; - } - private static final String[] EVENTKEYS_LATINIME_ONUPDATESELECTION = { "LatinIMEOnUpdateSelection", "lastSelectionStart", "lastSelectionEnd", "oldSelStart", "oldSelEnd", "newSelStart", "newSelEnd", "composingSpanStart", "composingSpanEnd", @@ -856,23 +1129,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang EVENTKEYS_NULLVALUES); } - private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS = { - "LatinKeyboardViewOnLongPress" + private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS = { + "MainKeyboardViewOnLongPress" }; - public static void latinKeyboardView_onLongPress() { - getInstance().enqueueEvent(EVENTKEYS_LATINKEYBOARDVIEW_ONLONGPRESS, EVENTKEYS_NULLVALUES); + public static void mainKeyboardView_onLongPress() { + getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_ONLONGPRESS, EVENTKEYS_NULLVALUES); } - private static final String[] EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD = { - "LatinKeyboardViewSetKeyboard", "elementId", "locale", "orientation", "width", + private static final String[] EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD = { + "MainKeyboardViewSetKeyboard", "elementId", "locale", "orientation", "width", "modeName", "action", "navigateNext", "navigatePrevious", "clobberSettingsKey", "passwordInput", "shortcutKeyEnabled", "hasShortcutKey", "languageSwitchKeyEnabled", "isMultiLine", "tw", "th", "keys" }; - public static void latinKeyboardView_setKeyboard(final Keyboard keyboard) { + public static void mainKeyboardView_setKeyboard(final Keyboard keyboard) { if (keyboard != null) { final KeyboardId kid = keyboard.mId; final boolean isPasswordView = kid.passwordInput(); + getInstance().setIsPasswordView(isPasswordView); final Object[] values = { KeyboardId.elementIdToName(kid.mElementId), kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), @@ -892,7 +1166,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang keyboard.mOccupiedHeight, keyboard.mKeys }; - getInstance().enqueueEvent(EVENTKEYS_LATINKEYBOARDVIEW_SETKEYBOARD, values); + getInstance().enqueueEvent(EVENTKEYS_MAINKEYBOARDVIEW_SETKEYBOARD, values); getInstance().setIsPasswordView(isPasswordView); } } @@ -984,16 +1258,16 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - private static final String[] EVENTKEYS_SUGGESTIONSVIEW_SETSUGGESTIONS = { - "SuggestionsViewSetSuggestions", "suggestedWords" + private static final String[] EVENTKEYS_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS = { + "SuggestionStripViewSetSuggestions", "suggestedWords" }; - public static void suggestionsView_setSuggestions(final SuggestedWords suggestedWords) { + public static void suggestionStripView_setSuggestions(final SuggestedWords suggestedWords) { if (suggestedWords != null) { final Object[] values = { suggestedWords }; - getInstance().enqueuePotentiallyPrivateEvent(EVENTKEYS_SUGGESTIONSVIEW_SETSUGGESTIONS, - values); + getInstance().enqueuePotentiallyPrivateEvent( + EVENTKEYS_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS, values); } } |