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

Commit b4693e25 authored by Adam Powell's avatar Adam Powell Committed by Android (Google) Code Review
Browse files

Merge "Smooth out handling of touchMajor/touchMinor in ScaleGestureDetector." into jb-mr1-dev

parents 2fd6cb04 a4ce6ae0
Loading
Loading
Loading
Loading
+130 −1
Original line number Diff line number Diff line
@@ -17,8 +17,11 @@
package android.view;

import android.content.Context;
import android.os.SystemClock;
import android.util.FloatMath;

import java.util.Arrays;

/**
 * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
 * The {@link OnScaleGestureListener} callback will notify users when a particular
@@ -139,6 +142,12 @@ public class ScaleGestureDetector {
    private int mSpanSlop;
    private int mMinSpan;

    private float[] mTouchHistoryLastAccepted;
    private int[] mTouchHistoryDirection;
    private long[] mTouchHistoryLastAcceptedTime;

    private static final long TOUCH_STABILIZE_TIME = 64; // ms

    /**
     * Consistency verifier for debugging purposes.
     */
@@ -154,6 +163,119 @@ public class ScaleGestureDetector {
                com.android.internal.R.dimen.config_minScalingSpan);
    }

    /**
     * The touchMajor/touchMinor elements of a MotionEvent can flutter/jitter on
     * some hardware/driver combos. Smooth it out to get kinder, gentler behavior.
     * @param ev MotionEvent to add to the ongoing history
     */
    private void addTouchHistory(MotionEvent ev) {
        final long currentTime = SystemClock.uptimeMillis();
        final int count = ev.getPointerCount();
        for (int i = 0; i < count; i++) {
            final int id = ev.getPointerId(i);
            ensureTouchHistorySize(id);

            final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted[id]);
            boolean accept = true;
            final int historySize = ev.getHistorySize();
            for (int h = 0; h < historySize + 1; h++) {
                final float major;
                final float minor;
                if (h < historySize) {
                    major = ev.getHistoricalTouchMajor(i, h);
                    minor = ev.getHistoricalTouchMinor(i, h);
                } else {
                    major = ev.getTouchMajor(i);
                    minor = ev.getTouchMinor(i);
                }
                final float avg = (major + minor) / 2;

                if (hasLastAccepted) {
                    final int directionSig = (int) Math.signum(avg - mTouchHistoryLastAccepted[id]);
                    if (directionSig != mTouchHistoryDirection[id]) {
                        mTouchHistoryDirection[id] = directionSig;
                        final long time = h < historySize ? ev.getHistoricalEventTime(h)
                                : ev.getEventTime();
                        mTouchHistoryLastAcceptedTime[id] = time;
                        accept = false;
                    }
                    if (currentTime - mTouchHistoryLastAcceptedTime[id] < TOUCH_STABILIZE_TIME) {
                        accept = false;
                    }
                }
            }

            if (accept) {
                float newAccepted = (ev.getTouchMajor(i) + ev.getTouchMinor(i)) / 2;
                if (hasLastAccepted) {
                    newAccepted = (mTouchHistoryLastAccepted[id] + newAccepted) / 2;
                }
                mTouchHistoryLastAccepted[id] = newAccepted;
                mTouchHistoryDirection[id] = 0;
                mTouchHistoryLastAcceptedTime[id] = ev.getEventTime();
            }
        }
    }

    /**
     * Clear out the touch history for a given pointer id.
     * @param id pointer id to clear
     * @see #addTouchHistory(MotionEvent)
     */
    private void removeTouchHistoryForId(int id) {
        mTouchHistoryLastAccepted[id] = Float.NaN;
        mTouchHistoryDirection[id] = 0;
        mTouchHistoryLastAcceptedTime[id] = 0;
    }

    /**
     * Get the adjusted combined touchMajor/touchMinor value for a given pointer id
     * @param id the pointer id of the data to obtain
     * @return the adjusted major/minor value for the point at id
     * @see #addTouchHistory(MotionEvent)
     */
    private float getAdjustedTouchHistory(int id) {
        return mTouchHistoryLastAccepted[id];
    }

    /**
     * Clear all touch history tracking. Useful in ACTION_CANCEL or ACTION_UP.
     * @see #addTouchHistory(MotionEvent)
     */
    private void clearTouchHistory() {
        Arrays.fill(mTouchHistoryLastAccepted, Float.NaN);
        Arrays.fill(mTouchHistoryDirection, 0);
        Arrays.fill(mTouchHistoryLastAcceptedTime, 0);
    }

    private void ensureTouchHistorySize(int id) {
        final int requiredSize = id + 1;
        if (mTouchHistoryLastAccepted == null || mTouchHistoryLastAccepted.length < requiredSize) {
            final float[] newLastAccepted = new float[requiredSize];
            final int[] newDirection = new int[requiredSize];
            final long[] newLastAcceptedTime = new long[requiredSize];

            int oldLength = 0;
            if (mTouchHistoryLastAccepted != null) {
                System.arraycopy(mTouchHistoryLastAccepted, 0, newLastAccepted, 0,
                        mTouchHistoryLastAccepted.length);
                System.arraycopy(mTouchHistoryDirection, 0, newDirection, 0,
                        mTouchHistoryDirection.length);
                System.arraycopy(mTouchHistoryLastAcceptedTime, 0, newLastAcceptedTime, 0,
                        mTouchHistoryLastAcceptedTime.length);
                oldLength = mTouchHistoryLastAccepted.length;
            }

            Arrays.fill(newLastAccepted, oldLength, newLastAccepted.length, Float.NaN);
            Arrays.fill(newDirection, oldLength, newDirection.length, 0);
            Arrays.fill(newLastAcceptedTime, oldLength, newLastAcceptedTime.length, 0);

            mTouchHistoryLastAccepted = newLastAccepted;
            mTouchHistoryDirection = newDirection;
            mTouchHistoryLastAcceptedTime = newLastAcceptedTime;
        }
    }

    /**
     * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
     * when appropriate.
@@ -186,6 +308,7 @@ public class ScaleGestureDetector {
            }

            if (streamComplete) {
                clearTouchHistory();
                return true;
            }
        }
@@ -208,13 +331,19 @@ public class ScaleGestureDetector {
        final float focusX = sumX / div;
        final float focusY = sumY / div;

        if (pointerUp) {
            removeTouchHistoryForId(event.getPointerId(event.getActionIndex()));
        } else {
            addTouchHistory(event);
        }

        // Determine average deviation from focal point
        float devSumX = 0, devSumY = 0;
        for (int i = 0; i < count; i++) {
            if (skipIndex == i) continue;

            // Average touch major and touch minor and convert the resulting diameter into a radius.
            final float touchSize = (event.getTouchMajor(i) + event.getTouchMinor(i)) / 4;
            final float touchSize = getAdjustedTouchHistory(event.getPointerId(i));
            devSumX += Math.abs(event.getX(i) - focusX) + touchSize;
            devSumY += Math.abs(event.getY(i) - focusY) + touchSize;
        }