summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Philip Quinn <pquinn@google.com> 2019-01-29 12:47:09 -0800
committer Philip Quinn <pquinn@google.com> 2019-03-14 09:54:11 -0700
commitf195c98a424c97ecfeb02d0a976d23fd453fe2dc (patch)
treeb73f9b8000890d7ee53dc7229a6d8bb18217c51c
parentf5ba1c62681fcd5364b9ff9e95d98ff11d531963 (diff)
Write touch classification metrics.
Bug: 111480215 Bug: 123663806 Test: statsd_testdrive 177 Change-Id: I33e449769a78624769e8f87a694be4c69ba19cb8
-rw-r--r--core/java/android/view/GestureDetector.java73
-rw-r--r--core/java/android/view/View.java62
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.