diff options
| author | 2019-01-29 12:47:09 -0800 | |
|---|---|---|
| committer | 2019-03-14 09:54:11 -0700 | |
| commit | f195c98a424c97ecfeb02d0a976d23fd453fe2dc (patch) | |
| tree | b73f9b8000890d7ee53dc7229a6d8bb18217c51c | |
| parent | f5ba1c62681fcd5364b9ff9e95d98ff11d531963 (diff) | |
Write touch classification metrics.
Bug: 111480215
Bug: 123663806
Test: statsd_testdrive 177
Change-Id: I33e449769a78624769e8f87a694be4c69ba19cb8
| -rw-r--r-- | core/java/android/view/GestureDetector.java | 73 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 62 |
2 files changed, 123 insertions, 12 deletions
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index c794a69d3680..c1d122a1f9b1 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -16,11 +16,20 @@ package android.view; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; + import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; import android.os.Handler; import android.os.Message; +import android.os.SystemClock; +import android.util.StatsLog; /** * Detects various gestures and events using the supplied {@link MotionEvent}s. @@ -251,8 +260,12 @@ public class GestureDetector { private boolean mAlwaysInTapRegion; private boolean mAlwaysInBiggerTapRegion; private boolean mIgnoreNextUpEvent; + // Whether a classification has been recorded by statsd for the current event stream. Reset on + // ACTION_DOWN. + private boolean mHasRecordedClassification; private MotionEvent mCurrentDownEvent; + private MotionEvent mCurrentMotionEvent; private MotionEvent mPreviousUpEvent; /** @@ -297,6 +310,7 @@ public class GestureDetector { break; case LONG_PRESS: + recordGestureClassification(msg.arg1); dispatchLongPress(); break; @@ -304,6 +318,8 @@ public class GestureDetector { // If the user's finger is still down, do not count it as a tap if (mDoubleTapListener != null) { if (!mStillDown) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); mDoubleTapListener.onSingleTapConfirmed(mCurrentDownEvent); } else { mDeferConfirmSingleTap = true; @@ -501,6 +517,11 @@ public class GestureDetector { final int action = ev.getAction(); + if (mCurrentMotionEvent != null) { + mCurrentMotionEvent.recycle(); + } + mCurrentMotionEvent = MotionEvent.obtain(ev); + if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } @@ -569,6 +590,8 @@ public class GestureDetector { && isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) { // This is a second tap mIsDoubleTapping = true; + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); // Give a callback with the first tap of the double-tap handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent); // Give a callback with down event of the double-tap @@ -590,11 +613,17 @@ public class GestureDetector { mStillDown = true; mInLongPress = false; mDeferConfirmSingleTap = false; + mHasRecordedClassification = false; if (mIsLongpressEnabled) { mHandler.removeMessages(LONG_PRESS); - mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime() - + ViewConfiguration.getLongPressTimeout()); + mHandler.sendMessageAtTime( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, + 0 /* arg2 */), + mCurrentDownEvent.getDownTime() + + ViewConfiguration.getLongPressTimeout()); } mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT); @@ -613,6 +642,8 @@ public class GestureDetector { final float scrollY = mLastFocusY - focusY; if (mIsDoubleTapping) { // Give the move events of the double-tap + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mAlwaysInTapRegion) { final int deltaX = (int) (focusX - mDownFocusX); @@ -635,8 +666,12 @@ public class GestureDetector { // reschedule long press with a modified timeout. mHandler.removeMessages(LONG_PRESS); final long longPressTimeout = ViewConfiguration.getLongPressTimeout(); - mHandler.sendEmptyMessageAtTime(LONG_PRESS, ev.getDownTime() - + (long) (longPressTimeout * multiplier)); + mHandler.sendMessageAtTime( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS, + 0 /* arg2 */), + ev.getDownTime() + (long) (longPressTimeout * multiplier)); } // Inhibit default scroll. If a gesture is ambiguous, we prevent scroll // until the gesture is resolved. @@ -646,6 +681,8 @@ public class GestureDetector { } if (distance > slopSquare) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL); handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; @@ -659,6 +696,7 @@ public class GestureDetector { mAlwaysInBiggerTapRegion = false; } } else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) { + recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SCROLL); handled = mListener.onScroll(mCurrentDownEvent, ev, scrollX, scrollY); mLastFocusX = focusX; mLastFocusY = focusY; @@ -667,7 +705,11 @@ public class GestureDetector { motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS; if (deepPress && hasPendingLongPress) { mHandler.removeMessages(LONG_PRESS); - mHandler.sendEmptyMessage(LONG_PRESS); + mHandler.sendMessage( + mHandler.obtainMessage( + LONG_PRESS, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS, + 0 /* arg2 */)); } break; @@ -676,11 +718,15 @@ public class GestureDetector { MotionEvent currentUpEvent = MotionEvent.obtain(ev); if (mIsDoubleTapping) { // Finally, give the up event of the double-tap + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP); handled |= mDoubleTapListener.onDoubleTapEvent(ev); } else if (mInLongPress) { mHandler.removeMessages(TAP); mInLongPress = false; } else if (mAlwaysInTapRegion && !mIgnoreNextUpEvent) { + recordGestureClassification( + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); handled = mListener.onSingleTapUp(ev); if (mDeferConfirmSingleTap && mDoubleTapListener != null) { mDoubleTapListener.onSingleTapConfirmed(ev); @@ -821,4 +867,21 @@ public class GestureDetector { mInLongPress = true; mListener.onLongPress(mCurrentDownEvent); } + + private void recordGestureClassification(int classification) { + if (mHasRecordedClassification + || classification + == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) { + // Only record the first classification for an event stream. + return; + } + StatsLog.write( + StatsLog.TOUCH_GESTURE_CLASSIFIED, + getClass().getName(), + classification, + (int) (SystemClock.uptimeMillis() - mCurrentMotionEvent.getDownTime()), + (float) Math.hypot(mCurrentMotionEvent.getRawX() - mCurrentDownEvent.getRawX(), + mCurrentMotionEvent.getRawY() - mCurrentDownEvent.getRawY())); + mHasRecordedClassification = true; + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index b857f1ed9996..49eb78d63479 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -17,6 +17,10 @@ package android.view; import static android.content.res.Resources.ID_NULL; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP; +import static android.util.StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED; @@ -96,6 +100,7 @@ import android.util.Property; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.StateSet; +import android.util.StatsLog; import android.util.SuperNotCalledException; import android.util.TypedValue; import android.view.AccessibilityIterators.CharacterTextSegmentIterator; @@ -14542,7 +14547,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (clickable) { setPressed(true, x, y); } - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + // This is not a touch gesture -- do not classify it as one. + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION); return true; } } @@ -15283,7 +15293,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mHasPerformedLongPress = false; if (!clickable) { - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); break; } @@ -15307,7 +15321,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } else { // Not inside a scrolling container, so show the feedback right away setPressed(true, x, y); - checkForLongClick(ViewConfiguration.getLongPressTimeout(), x, y); + checkForLongClick( + ViewConfiguration.getLongPressTimeout(), + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } break; @@ -15344,7 +15362,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * ambiguousMultiplier); // Subtract the time already spent delay -= event.getEventTime() - event.getDownTime(); - checkForLongClick(delay, x, y); + checkForLongClick( + delay, + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } touchSlop *= ambiguousMultiplier; } @@ -15366,7 +15388,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (deepPress && hasPendingLongPressCallback()) { // process the long click action immediately removeLongPressCallback(); - checkForLongClick(0 /* send immediately */, x, y); + checkForLongClick( + 0 /* send immediately */, + x, + y, + TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS); } break; @@ -26031,7 +26057,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } - private void checkForLongClick(long delay, float x, float y) { + private void checkForLongClick(long delay, float x, float y, int classification) { if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) { mHasPerformedLongPress = false; @@ -26041,6 +26067,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mPendingCheckForLongPress.setAnchor(x, y); mPendingCheckForLongPress.rememberWindowAttachCount(); mPendingCheckForLongPress.rememberPressedState(); + mPendingCheckForLongPress.setClassification(classification); postDelayed(mPendingCheckForLongPress, delay); } } @@ -27598,11 +27625,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private float mX; private float mY; private boolean mOriginalPressedState; + /** + * The classification of the long click being checked: one of the + * StatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__* constants. + */ + private int mClassification; @Override public void run() { if ((mOriginalPressedState == isPressed()) && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { + recordGestureClassification(mClassification); if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } @@ -27621,6 +27654,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void rememberPressedState() { mOriginalPressedState = isPressed(); } + + public void setClassification(int classification) { + mClassification = classification; + } } private final class CheckForTap implements Runnable { @@ -27633,17 +27670,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPressed(true, x, y); final long delay = ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout(); - checkForLongClick(delay, x, y); + checkForLongClick(delay, x, y, TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS); } } private final class PerformClick implements Runnable { @Override public void run() { + recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP); performClickInternal(); } } + /** Records a classification for the current event stream. */ + private void recordGestureClassification(int classification) { + if (classification == TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__UNKNOWN_CLASSIFICATION) { + return; + } + // To avoid negatively impacting View performance, the latency and displacement metrics + // are omitted. + StatsLog.write(StatsLog.TOUCH_GESTURE_CLASSIFIED, getClass().getName(), classification); + } + /** * This method returns a ViewPropertyAnimator object, which can be used to animate * specific properties on this View. |