Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5be044f3 authored by Philip Quinn's avatar Philip Quinn
Browse files

Write touch classification metrics.

Bug: 111480215
Bug: 123663806
Test: statsd_testdrive 177

Change-Id: I44f0a1877acc38b3199dcc6a3b6d4bf00ac8d817
parent afebeabb
Loading
Loading
Loading
Loading
+73 −5
Original line number Diff line number Diff line
@@ -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,10 +613,16 @@ public class GestureDetector {
                mStillDown = true;
                mInLongPress = false;
                mDeferConfirmSingleTap = false;
                mHasRecordedClassification = false;

                if (mIsLongpressEnabled) {
                    mHandler.removeMessages(LONG_PRESS);
                    mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
                    mHandler.sendMessageAtTime(
                            mHandler.obtainMessage(
                                    LONG_PRESS,
                                    TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS,
                                    0 /* arg2 */),
                            mCurrentDownEvent.getDownTime()
                                    + ViewConfiguration.getLongPressTimeout());
                }
                mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
@@ -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,26 @@ 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;
        }
        if (mCurrentDownEvent == null || mCurrentMotionEvent == null) {
            // If the complete event stream wasn't seen, don't record anything.
            mHasRecordedClassification = true;
            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;
    }
}
+55 −7
Original line number Diff line number Diff line
@@ -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;
@@ -14571,7 +14576,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;
                }
            }
@@ -15312,7 +15322,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;
                    }
@@ -15336,7 +15350,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;
@@ -15373,7 +15391,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;
                    }
@@ -15395,7 +15417,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;
@@ -26143,7 +26169,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;
@@ -26153,6 +26179,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            mPendingCheckForLongPress.setAnchor(x, y);
            mPendingCheckForLongPress.rememberWindowAttachCount();
            mPendingCheckForLongPress.rememberPressedState();
            mPendingCheckForLongPress.setClassification(classification);
            postDelayed(mPendingCheckForLongPress, delay);
        }
    }
@@ -27710,11 +27737,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;
                }
@@ -27733,6 +27766,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 {
@@ -27745,17 +27782,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.