diff options
-rw-r--r-- | core/java/android/view/ViewRootImpl.java | 616 | ||||
-rw-r--r-- | services/input/EventHub.cpp | 36 | ||||
-rw-r--r-- | services/input/EventHub.h | 17 | ||||
-rw-r--r-- | services/input/InputReader.cpp | 61 | ||||
-rw-r--r-- | services/input/InputReader.h | 20 |
5 files changed, 495 insertions, 255 deletions
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 38d7713..98b7877 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -19,13 +19,10 @@ package android.view; import android.Manifest; import android.animation.LayoutTransition; import android.app.ActivityManagerNative; -import android.app.SearchManager; -import android.content.ActivityNotFoundException; import android.content.ClipDescription; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; @@ -54,7 +51,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; -import android.os.UserHandle; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.Log; @@ -4332,283 +4328,413 @@ public final class ViewRootImpl implements ViewParent, * Creates dpad events from unhandled touch navigation movements. */ final class SyntheticTouchNavigationHandler extends Handler { - private static final int MSG_FLICK = 1; - - // Maximum difference in milliseconds between the down and up of a touch - // event for it to be considered a tap - // TODO:Read this value from a configuration file - private static final int MAX_TAP_TIME = 250; - - // Where the cutoff is for determining an edge swipe - private static final float EDGE_SWIPE_THRESHOLD = 0.9f; - - // TODO: Pass touch slop from the input device - private static final int TOUCH_SLOP = 30; - - // The position of the previous TouchNavigation event - private float mLastTouchNavigationXPosition; - private float mLastTouchNavigationYPosition; - // Where the Touch Navigation was initially pressed - private float mTouchNavigationEnterXPosition; - private float mTouchNavigationEnterYPosition; - // When the most recent ACTION_HOVER_ENTER occurred - private long mLastTouchNavigationStartTimeMs = 0; - // When the most recent direction key was sent - private long mLastTouchNavigationKeySendTimeMs = 0; - // When the most recent touch event of any type occurred - private long mLastTouchNavigationEventTimeMs = 0; - // Did the swipe begin in a valid region - private boolean mEdgeSwipePossible; - - // How quickly keys were sent - private int mKeySendRateMs = 0; - private int mLastKeySent; - // Last movement in device screen pixels - private float mLastMoveX = 0; - private float mLastMoveY = 0; - // Offset from the initial touch. Gets reset as direction keys are sent. + private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler"; + private static final boolean LOCAL_DEBUG = false; + + // Assumed nominal width and height in millimeters of a touch navigation pad, + // if no resolution information is available from the input system. + private static final float DEFAULT_WIDTH_MILLIMETERS = 48; + private static final float DEFAULT_HEIGHT_MILLIMETERS = 48; + + /* TODO: These constants should eventually be moved to ViewConfiguration. */ + + // Tap timeout in milliseconds. + private static final int TAP_TIMEOUT = 250; + + // The maximum distance traveled for a gesture to be considered a tap in millimeters. + private static final int TAP_SLOP_MILLIMETERS = 5; + + // The nominal distance traveled to move by one unit. + private static final int TICK_DISTANCE_MILLIMETERS = 12; + + // Minimum and maximum fling velocity in ticks per second. + // The minimum velocity should be set such that we perform enough ticks per + // second that the fling appears to be fluid. For example, if we set the minimum + // to 2 ticks per second, then there may be up to half a second delay between the next + // to last and last ticks which is noticeably discrete and jerky. This value should + // probably not be set to anything less than about 4. + // If fling accuracy is a problem then consider tuning the tick distance instead. + private static final float MIN_FLING_VELOCITY_TICKS_PER_SECOND = 6f; + private static final float MAX_FLING_VELOCITY_TICKS_PER_SECOND = 20f; + + // Fling velocity decay factor applied after each new key is emitted. + // This parameter controls the deceleration and overall duration of the fling. + // The fling stops automatically when its velocity drops below the minimum + // fling velocity defined above. + private static final float FLING_TICK_DECAY = 0.8f; + + /* The input device that we are tracking. */ + + private int mCurrentDeviceId = -1; + private int mCurrentSource; + private boolean mCurrentDeviceSupported; + + /* Configuration for the current input device. */ + + // The tap timeout and scaled slop. + private int mConfigTapTimeout; + private float mConfigTapSlop; + + // The scaled tick distance. A movement of this amount should generally translate + // into a single dpad event in a given direction. + private float mConfigTickDistance; + + // The minimum and maximum scaled fling velocity. + private float mConfigMinFlingVelocity; + private float mConfigMaxFlingVelocity; + + /* Tracking state. */ + + // The velocity tracker for detecting flings. + private VelocityTracker mVelocityTracker; + + // The active pointer id, or -1 if none. + private int mActivePointerId = -1; + + // Time and location where tracking started. + private long mStartTime; + private float mStartX; + private float mStartY; + + // Most recently observed position. + private float mLastX; + private float mLastY; + + // Accumulated movement delta since the last direction key was sent. private float mAccumulatedX; private float mAccumulatedY; - // Change in position allowed during tap events - private float mTouchSlop; - private float mTouchSlopSquared; - // Has the TouchSlop constraint been invalidated - private boolean mAlwaysInTapRegion = true; - - // Information from the most recent event. - // Used to determine what device sent the event during a fling. - private int mLastSource; - private int mLastMetaState; - private int mLastDeviceId; - - // TODO: Currently using screen dimensions tuned to a Galaxy Nexus, need to - // read this from a config file instead - private int mDistancePerTick; - private int mDistancePerTickSquared; - // Highest rate that the flinged events can occur at before dying out - private int mMaxRepeatDelay; - // The square of the minimum distance needed for a flick to register - private int mMinFlickDistanceSquared; - // How quickly the repeated events die off - private float mFlickDecay; + // Set to true if any movement was delivered to the app. + // Implies that tap slop was exceeded. + private boolean mConsumedMovement; + + // The most recently sent key down event. + // The keycode remains set until the direction changes or a fling ends + // so that repeated key events may be generated as required. + private long mPendingKeyDownTime; + private int mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN; + private int mPendingKeyRepeatCount; + private int mPendingKeyMetaState; + + // The current fling velocity while a fling is in progress. + private boolean mFlinging; + private float mFlingVelocity; public SyntheticTouchNavigationHandler() { super(true); - mDistancePerTick = SystemProperties.getInt("persist.vr_dist_tick", 64); - mDistancePerTickSquared = mDistancePerTick * mDistancePerTick; - mMaxRepeatDelay = SystemProperties.getInt("persist.vr_repeat_delay", 300); - mMinFlickDistanceSquared = SystemProperties.getInt("persist.vr_min_flick", 20); - mMinFlickDistanceSquared *= mMinFlickDistanceSquared; - mFlickDecay = Float.parseFloat(SystemProperties.get( - "persist.sys.vr_flick_decay", "1.3")); - mTouchSlop = TOUCH_SLOP; - mTouchSlopSquared = mTouchSlop * mTouchSlop; } - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_FLICK: { - final long time = SystemClock.uptimeMillis(); - final int keyCode = msg.arg2; + public void process(MotionEvent event) { + // Update the current device information. + final long time = event.getEventTime(); + final int deviceId = event.getDeviceId(); + final int source = event.getSource(); + if (mCurrentDeviceId != deviceId || mCurrentSource != source) { + finishKeys(time); + finishTracking(time); + mCurrentDeviceId = deviceId; + mCurrentSource = source; + mCurrentDeviceSupported = false; + InputDevice device = event.getDevice(); + if (device != null) { + // In order to support an input device, we must know certain + // characteristics about it, such as its size and resolution. + InputDevice.MotionRange xRange = device.getMotionRange(MotionEvent.AXIS_X); + InputDevice.MotionRange yRange = device.getMotionRange(MotionEvent.AXIS_Y); + if (xRange != null && yRange != null) { + mCurrentDeviceSupported = true; + + // Infer the resolution if it not actually known. + float xRes = xRange.getResolution(); + if (xRes <= 0) { + xRes = xRange.getRange() / DEFAULT_WIDTH_MILLIMETERS; + } + float yRes = yRange.getResolution(); + if (yRes <= 0) { + yRes = yRange.getRange() / DEFAULT_HEIGHT_MILLIMETERS; + } + float nominalRes = (xRes + yRes) * 0.5f; + + // Precompute all of the configuration thresholds we will need. + mConfigTapTimeout = TAP_TIMEOUT; + mConfigTapSlop = TAP_SLOP_MILLIMETERS * nominalRes; + mConfigTickDistance = TICK_DISTANCE_MILLIMETERS * nominalRes; + mConfigMinFlingVelocity = + MIN_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance; + mConfigMaxFlingVelocity = + MAX_FLING_VELOCITY_TICKS_PER_SECOND * mConfigTickDistance; + + if (LOCAL_DEBUG) { + Log.d(LOCAL_TAG, "Configured device " + mCurrentDeviceId + + " (" + Integer.toHexString(mCurrentSource) + "): " + + "mConfigTapTimeout=" + mConfigTapTimeout + + ", mConfigTapSlop=" + mConfigTapSlop + + ", mConfigTickDistance=" + mConfigTickDistance + + ", mConfigMinFlingVelocity=" + mConfigMinFlingVelocity + + ", mConfigMaxFlingVelocity=" + mConfigMaxFlingVelocity); + } + } + } + } + if (!mCurrentDeviceSupported) { + return; + } - // Send the key - enqueueInputEvent(new KeyEvent(time, time, - KeyEvent.ACTION_DOWN, keyCode, 0, mLastMetaState, - mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource)); - enqueueInputEvent(new KeyEvent(time, time, - KeyEvent.ACTION_UP, keyCode, 0, mLastMetaState, - mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource)); - - // Increase the delay by the decay factor and resend - final int delay = (int) Math.ceil(mFlickDecay * msg.arg1); - if (delay <= mMaxRepeatDelay) { - Message next = obtainMessage(MSG_FLICK, delay, keyCode); - next.setAsynchronous(true); - sendMessageDelayed(next, delay); + // Handle the event. + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { + boolean caughtFling = mFlinging; + finishKeys(time); + finishTracking(time); + mActivePointerId = event.getPointerId(0); + mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(event); + mStartTime = time; + mStartX = event.getX(); + mStartY = event.getY(); + mLastX = mStartX; + mLastY = mStartY; + mAccumulatedX = 0; + mAccumulatedY = 0; + + // If we caught a fling, then pretend that the tap slop has already + // been exceeded to suppress taps whose only purpose is to stop the fling. + mConsumedMovement = caughtFling; + break; + } + + case MotionEvent.ACTION_MOVE: + case MotionEvent.ACTION_UP: { + if (mActivePointerId < 0) { + break; } + final int index = event.findPointerIndex(mActivePointerId); + if (index < 0) { + finishKeys(time); + finishTracking(time); + break; + } + + mVelocityTracker.addMovement(event); + final float x = event.getX(index); + final float y = event.getY(index); + mAccumulatedX += x - mLastX; + mAccumulatedY += y - mLastY; + mLastX = x; + mLastY = y; + + // Consume any accumulated movement so far. + final int metaState = event.getMetaState(); + consumeAccumulatedMovement(time, metaState); + + // Detect taps and flings. + if (action == MotionEvent.ACTION_UP) { + if (!mConsumedMovement + && Math.hypot(mLastX - mStartX, mLastY - mStartY) < mConfigTapSlop + && time <= mStartTime + mConfigTapTimeout) { + // It's a tap! + finishKeys(time); + sendKeyDownOrRepeat(time, KeyEvent.KEYCODE_DPAD_CENTER, metaState); + sendKeyUp(time); + } else if (mConsumedMovement + && mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) { + // It might be a fling. + mVelocityTracker.computeCurrentVelocity(1000, mConfigMaxFlingVelocity); + final float vx = mVelocityTracker.getXVelocity(mActivePointerId); + final float vy = mVelocityTracker.getYVelocity(mActivePointerId); + if (!startFling(time, vx, vy)) { + finishKeys(time); + } + } + finishTracking(time); + } + break; + } + + case MotionEvent.ACTION_CANCEL: { + finishKeys(time); + finishTracking(time); break; } } } - public void process(MotionEvent event) { - update(event, true); + public void cancel(MotionEvent event) { + if (mCurrentDeviceId == event.getDeviceId() + && mCurrentSource == event.getSource()) { + final long time = event.getEventTime(); + finishKeys(time); + finishTracking(time); + } } - public void cancel(MotionEvent event) { - update(event, false); + private void finishKeys(long time) { + cancelFling(); + sendKeyUp(time); } - private void update(MotionEvent event, boolean synthesizeNewKeys) { - if (!synthesizeNewKeys) { - removeMessages(MSG_FLICK); + private void finishTracking(long time) { + if (mActivePointerId >= 0) { + mActivePointerId = -1; + mVelocityTracker.recycle(); + mVelocityTracker = null; } + } - InputDevice device = event.getDevice(); - if (device == null) { - return; + private void consumeAccumulatedMovement(long time, int metaState) { + final float absX = Math.abs(mAccumulatedX); + final float absY = Math.abs(mAccumulatedY); + if (absX >= absY) { + if (absX >= mConfigTickDistance) { + mAccumulatedX = consumeAccumulatedMovement(time, metaState, mAccumulatedX, + KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT); + mAccumulatedY = 0; + mConsumedMovement = true; + } + } else { + if (absY >= mConfigTickDistance) { + mAccumulatedY = consumeAccumulatedMovement(time, metaState, mAccumulatedY, + KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN); + mAccumulatedX = 0; + mConsumedMovement = true; + } } + } - // Store what time the TouchNavigation event occurred - final long time = SystemClock.uptimeMillis(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: { - mLastTouchNavigationStartTimeMs = time; - mAlwaysInTapRegion = true; - mTouchNavigationEnterXPosition = event.getX(); - mTouchNavigationEnterYPosition = event.getY(); - mAccumulatedX = 0; - mAccumulatedY = 0; - mLastMoveX = 0; - mLastMoveY = 0; - if (device.getMotionRange(MotionEvent.AXIS_Y).getMax() - * EDGE_SWIPE_THRESHOLD < event.getY()) { - // Did the swipe begin in a valid region - mEdgeSwipePossible = true; - } - // Clear any flings - if (synthesizeNewKeys) { - removeMessages(MSG_FLICK); - } - break; + private float consumeAccumulatedMovement(long time, int metaState, + float accumulator, int negativeKeyCode, int positiveKeyCode) { + while (accumulator <= -mConfigTickDistance) { + sendKeyDownOrRepeat(time, negativeKeyCode, metaState); + accumulator += mConfigTickDistance; + } + while (accumulator >= mConfigTickDistance) { + sendKeyDownOrRepeat(time, positiveKeyCode, metaState); + accumulator -= mConfigTickDistance; + } + return accumulator; + } + + private void sendKeyDownOrRepeat(long time, int keyCode, int metaState) { + if (mPendingKeyCode != keyCode) { + sendKeyUp(time); + mPendingKeyDownTime = time; + mPendingKeyCode = keyCode; + mPendingKeyRepeatCount = 0; + } else { + mPendingKeyRepeatCount += 1; + } + mPendingKeyMetaState = metaState; + + // Note: Normally we would pass FLAG_LONG_PRESS when the repeat count is 1 + // but it doesn't quite make sense when simulating the events in this way. + if (LOCAL_DEBUG) { + Log.d(LOCAL_TAG, "Sending key down: keyCode=" + mPendingKeyCode + + ", repeatCount=" + mPendingKeyRepeatCount + + ", metaState=" + Integer.toHexString(mPendingKeyMetaState)); + } + enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time, + KeyEvent.ACTION_DOWN, mPendingKeyCode, mPendingKeyRepeatCount, + mPendingKeyMetaState, mCurrentDeviceId, + KeyEvent.FLAG_FALLBACK, mCurrentSource)); + } + + private void sendKeyUp(long time) { + if (mPendingKeyCode != KeyEvent.KEYCODE_UNKNOWN) { + if (LOCAL_DEBUG) { + Log.d(LOCAL_TAG, "Sending key up: keyCode=" + mPendingKeyCode + + ", metaState=" + Integer.toHexString(mPendingKeyMetaState)); } + enqueueInputEvent(new KeyEvent(mPendingKeyDownTime, time, + KeyEvent.ACTION_UP, mPendingKeyCode, 0, mPendingKeyMetaState, + mCurrentDeviceId, 0, KeyEvent.FLAG_FALLBACK, + mCurrentSource)); + mPendingKeyCode = KeyEvent.KEYCODE_UNKNOWN; + } + } + + private boolean startFling(long time, float vx, float vy) { + if (LOCAL_DEBUG) { + Log.d(LOCAL_TAG, "Considering fling: vx=" + vx + ", vy=" + vy + + ", min=" + mConfigMinFlingVelocity); + } - case MotionEvent.ACTION_MOVE: { - // Determine whether the move is slop or an intentional move - float deltaX = event.getX() - mTouchNavigationEnterXPosition; - float deltaY = event.getY() - mTouchNavigationEnterYPosition; - if (mTouchSlopSquared < deltaX * deltaX + deltaY * deltaY) { - mAlwaysInTapRegion = false; + // Flings must be oriented in the same direction as the preceding movements. + switch (mPendingKeyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (-vx >= mConfigMinFlingVelocity + && Math.abs(vy) < mConfigMinFlingVelocity) { + mFlingVelocity = -vx; + break; } + return false; - // Checks if the swipe has crossed the midpoint - // and if our swipe gesture is complete - if (event.getY() < (device.getMotionRange(MotionEvent.AXIS_Y).getMax() - * .5) && mEdgeSwipePossible) { - mEdgeSwipePossible = false; - - Intent intent = - ((SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(mContext, false, UserHandle.USER_CURRENT_OR_SELF); - if (intent != null) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - mContext.startActivity(intent); - } catch (ActivityNotFoundException e){ - Log.e(TAG, "Could not start search activity"); - } - } else { - Log.e(TAG, "Could not find a search activity"); - } + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (vx >= mConfigMinFlingVelocity + && Math.abs(vy) < mConfigMinFlingVelocity) { + mFlingVelocity = vx; + break; } + return false; - // Find the difference in position between the two most recent - // TouchNavigation events - mLastMoveX = event.getX() - mLastTouchNavigationXPosition; - mLastMoveY = event.getY() - mLastTouchNavigationYPosition; - mAccumulatedX += mLastMoveX; - mAccumulatedY += mLastMoveY; - float accumulatedXSquared = mAccumulatedX * mAccumulatedX; - float accumulatedYSquared = mAccumulatedY * mAccumulatedY; - - // Determine if we've moved far enough to send a key press - if (accumulatedXSquared > mDistancePerTickSquared - || accumulatedYSquared > mDistancePerTickSquared) { - float dominantAxis; - float sign; - boolean isXAxis; - int key; - int repeatCount = 0; - // Determine dominant axis - if (accumulatedXSquared > accumulatedYSquared) { - dominantAxis = mAccumulatedX; - isXAxis = true; - } else { - dominantAxis = mAccumulatedY; - isXAxis = false; - } - // Determine sign of axis - sign = (dominantAxis > 0) ? 1 : -1; - // Determine key to send - if (isXAxis) { - key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_RIGHT : - KeyEvent.KEYCODE_DPAD_LEFT; - } else { - key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_DOWN : - KeyEvent.KEYCODE_DPAD_UP; - } - // Send key until maximum distance constraint is satisfied - while (dominantAxis * dominantAxis > mDistancePerTickSquared) { - repeatCount++; - dominantAxis -= sign * mDistancePerTick; - if (synthesizeNewKeys) { - enqueueInputEvent(new KeyEvent(time, time, - KeyEvent.ACTION_DOWN, key, 0, event.getMetaState(), - event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK, - event.getSource())); - enqueueInputEvent(new KeyEvent(time, time, - KeyEvent.ACTION_UP, key, 0, event.getMetaState(), - event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK, - event.getSource())); - } - } - // Save new axis values - mAccumulatedX = isXAxis ? dominantAxis : 0; - mAccumulatedY = isXAxis ? 0 : dominantAxis; - - mLastKeySent = key; - mKeySendRateMs = (int) (time - mLastTouchNavigationKeySendTimeMs) / - repeatCount; - mLastTouchNavigationKeySendTimeMs = time; + case KeyEvent.KEYCODE_DPAD_UP: + if (-vy >= mConfigMinFlingVelocity + && Math.abs(vx) < mConfigMinFlingVelocity) { + mFlingVelocity = -vy; + break; } - break; - } + return false; - case MotionEvent.ACTION_UP: { - if (time - mLastTouchNavigationStartTimeMs < MAX_TAP_TIME - && mAlwaysInTapRegion) { - if (synthesizeNewKeys) { - enqueueInputEvent(new KeyEvent(mLastTouchNavigationStartTimeMs, - time, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, - event.getMetaState(), event.getDeviceId(), 0, - KeyEvent.FLAG_FALLBACK, event.getSource())); - enqueueInputEvent(new KeyEvent(mLastTouchNavigationStartTimeMs, - time, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, - event.getMetaState(), event.getDeviceId(), 0, - KeyEvent.FLAG_FALLBACK, event.getSource())); - } - } else { - float xMoveSquared = mLastMoveX * mLastMoveX; - float yMoveSquared = mLastMoveY * mLastMoveY; - // Determine whether the last gesture was a fling. - if (mMinFlickDistanceSquared <= xMoveSquared + yMoveSquared - && time - mLastTouchNavigationEventTimeMs <= MAX_TAP_TIME - && mKeySendRateMs <= mMaxRepeatDelay - && mKeySendRateMs > 0) { - mLastDeviceId = event.getDeviceId(); - mLastSource = event.getSource(); - mLastMetaState = event.getMetaState(); - - if (synthesizeNewKeys) { - Message message = obtainMessage( - MSG_FLICK, mKeySendRateMs, mLastKeySent); - message.setAsynchronous(true); - sendMessageDelayed(message, mKeySendRateMs); - } - } + case KeyEvent.KEYCODE_DPAD_DOWN: + if (vy >= mConfigMinFlingVelocity + && Math.abs(vx) < mConfigMinFlingVelocity) { + mFlingVelocity = vy; + break; } - mEdgeSwipePossible = false; - break; + return false; + } + + // Post the first fling event. + mFlinging = postFling(time); + return mFlinging; + } + + private boolean postFling(long time) { + // The idea here is to estimate the time when the pointer would have + // traveled one tick distance unit given the current fling velocity. + // This effect creates continuity of motion. + if (mFlingVelocity >= mConfigMinFlingVelocity) { + long delay = (long)(mConfigTickDistance / mFlingVelocity * 1000); + postAtTime(mFlingRunnable, time + delay); + if (LOCAL_DEBUG) { + Log.d(LOCAL_TAG, "Posted fling: velocity=" + + mFlingVelocity + ", delay=" + delay + + ", keyCode=" + mPendingKeyCode); } + return true; } + return false; + } - // Store touch event position and time - mLastTouchNavigationEventTimeMs = time; - mLastTouchNavigationXPosition = event.getX(); - mLastTouchNavigationYPosition = event.getY(); + private void cancelFling() { + if (mFlinging) { + removeCallbacks(mFlingRunnable); + mFlinging = false; + } } + + private final Runnable mFlingRunnable = new Runnable() { + @Override + public void run() { + final long time = SystemClock.uptimeMillis(); + sendKeyDownOrRepeat(time, mPendingKeyCode, mPendingKeyMetaState); + mFlingVelocity *= FLING_TICK_DECAY; + if (!postFling(time)) { + mFlinging = false; + finishKeys(time); + } + } + }; } /** diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp index 0773afb..f4e1cec 100644 --- a/services/input/EventHub.cpp +++ b/services/input/EventHub.cpp @@ -40,7 +40,6 @@ #include <androidfw/KeyCharacterMap.h> #include <androidfw/VirtualKeyMap.h> -#include <sha1.h> #include <string.h> #include <stdint.h> #include <dirent.h> @@ -49,6 +48,7 @@ #include <sys/epoll.h> #include <sys/ioctl.h> #include <sys/limits.h> +#include <sys/sha1.h> /* this macro is used to tell if "bit" is set in "array" * it selects a byte from the array, and does a boolean AND @@ -162,7 +162,8 @@ EventHub::Device::Device(int fd, int32_t id, const String8& path, next(NULL), fd(fd), id(id), path(path), identifier(identifier), classes(0), configuration(NULL), virtualKeyMap(NULL), - ffEffectPlaying(false), ffEffectId(-1) { + ffEffectPlaying(false), ffEffectId(-1), + timestampOverrideSec(0), timestampOverrideUsec(0) { memset(keyBitmask, 0, sizeof(keyBitmask)); memset(absBitmask, 0, sizeof(absBitmask)); memset(relBitmask, 0, sizeof(relBitmask)); @@ -766,12 +767,37 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz size_t count = size_t(readSize) / sizeof(struct input_event); for (size_t i = 0; i < count; i++) { - const struct input_event& iev = readBuffer[i]; - ALOGV("%s got: t0=%d, t1=%d, type=%d, code=%d, value=%d", + struct input_event& iev = readBuffer[i]; + ALOGV("%s got: time=%d.%06d, type=%d, code=%d, value=%d", device->path.string(), (int) iev.time.tv_sec, (int) iev.time.tv_usec, iev.type, iev.code, iev.value); + // Some input devices may have a better concept of the time + // when an input event was actually generated than the kernel + // which simply timestamps all events on entry to evdev. + // This is a custom Android extension of the input protocol + // mainly intended for use with uinput based device drivers. + if (iev.type == EV_MSC) { + if (iev.code == MSC_ANDROID_TIME_SEC) { + device->timestampOverrideSec = iev.value; + continue; + } else if (iev.code == MSC_ANDROID_TIME_USEC) { + device->timestampOverrideUsec = iev.value; + continue; + } + } + if (device->timestampOverrideSec || device->timestampOverrideUsec) { + iev.time.tv_sec = device->timestampOverrideSec; + iev.time.tv_usec = device->timestampOverrideUsec; + if (iev.type == EV_SYN && iev.code == SYN_REPORT) { + device->timestampOverrideSec = 0; + device->timestampOverrideUsec = 0; + } + ALOGV("applied override time %d.%06d", + int(iev.time.tv_sec), int(iev.time.tv_usec)); + } + #ifdef HAVE_POSIX_CLOCKS // Use the time specified in the event instead of the current time // so that downstream code can get more accurate estimates of @@ -829,8 +855,8 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz event->code = iev.code; event->value = iev.value; event += 1; + capacity -= 1; } - capacity -= count; if (capacity == 0) { // The result buffer is full. Reset the pending event index // so we will try to read the device again on the next iteration. diff --git a/services/input/EventHub.h b/services/input/EventHub.h index afc12ef..c93fc7a 100644 --- a/services/input/EventHub.h +++ b/services/input/EventHub.h @@ -42,6 +42,20 @@ #define BTN_FIRST 0x100 // first button code #define BTN_LAST 0x15f // last button code +/* + * These constants are used privately in Android to pass raw timestamps + * through evdev from uinput device drivers because there is currently no + * other way to transfer this information. The evdev driver automatically + * timestamps all input events with the time they were posted and clobbers + * whatever information was passed in. + * + * For the purposes of this hack, the timestamp is specified in the + * CLOCK_MONOTONIC timebase and is split into two EV_MSC events specifying + * seconds and microseconds. + */ +#define MSC_ANDROID_TIME_SEC 0x6 +#define MSC_ANDROID_TIME_USEC 0x7 + namespace android { enum { @@ -329,6 +343,9 @@ private: bool ffEffectPlaying; int16_t ffEffectId; // initially -1 + int32_t timestampOverrideSec; + int32_t timestampOverrideUsec; + Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier); ~Device(); diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp index 602afd4..ab38ed2 100644 --- a/services/input/InputReader.cpp +++ b/services/input/InputReader.cpp @@ -2701,6 +2701,12 @@ void TouchInputMapper::dump(String8& dump) { mPointerYZoomScale); dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n", mPointerGestureMaxSwipeWidth); + } else if (mDeviceMode == DEVICE_MODE_NAVIGATION) { + dump.appendFormat(INDENT3 "Navigation Gesture Detector:\n"); + dump.appendFormat(INDENT4 "AssistStartY: %0.3f\n", + mNavigationAssistStartY); + dump.appendFormat(INDENT4 "AssistEndY: %0.3f\n", + mNavigationAssistEndY); } } @@ -2895,7 +2901,7 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) { } } else if (mParameters.deviceType == Parameters::DEVICE_TYPE_TOUCH_NAVIGATION) { mSource = AINPUT_SOURCE_TOUCH_NAVIGATION; - mDeviceMode = DEVICE_MODE_UNSCALED; + mDeviceMode = DEVICE_MODE_NAVIGATION; } else { mSource = AINPUT_SOURCE_TOUCHPAD; mDeviceMode = DEVICE_MODE_UNSCALED; @@ -3243,8 +3249,8 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) { break; } - // Compute pointer gesture detection parameters. if (mDeviceMode == DEVICE_MODE_POINTER) { + // Compute pointer gesture detection parameters. float rawDiagonal = hypotf(rawWidth, rawHeight); float displayDiagonal = hypotf(mSurfaceWidth, mSurfaceHeight); @@ -3269,10 +3275,14 @@ void TouchInputMapper::configureSurface(nsecs_t when, bool* outResetNeeded) { // translated into freeform gestures. mPointerGestureMaxSwipeWidth = mConfig.pointerGestureSwipeMaxWidthRatio * rawDiagonal; - } - // Abort current pointer usages because the state has changed. - abortPointerUsage(when, 0 /*policyFlags*/); + // Abort current pointer usages because the state has changed. + abortPointerUsage(when, 0 /*policyFlags*/); + } else if (mDeviceMode == DEVICE_MODE_NAVIGATION) { + // Compute navigation parameters. + mNavigationAssistStartY = mSurfaceHeight * 0.9f; + mNavigationAssistEndY = mSurfaceHeight * 0.5f; + } // Inform the dispatcher about the changes. *outResetNeeded = true; @@ -3611,6 +3621,7 @@ void TouchInputMapper::reset(nsecs_t when) { mPointerGesture.reset(); mPointerSimple.reset(); + mNavigation.reset(); if (mPointerController != NULL) { mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL); @@ -3761,6 +3772,8 @@ void TouchInputMapper::sync(nsecs_t when) { mPointerController->setSpots(mCurrentCookedPointerData.pointerCoords, mCurrentCookedPointerData.idToIndex, mCurrentCookedPointerData.touchingIdBits); + } else if (mDeviceMode == DEVICE_MODE_NAVIGATION) { + dispatchNavigationAssist(when, policyFlags); } dispatchHoverExit(when, policyFlags); @@ -5482,6 +5495,44 @@ void TouchInputMapper::abortPointerSimple(nsecs_t when, uint32_t policyFlags) { dispatchPointerSimple(when, policyFlags, false, false); } +void TouchInputMapper::dispatchNavigationAssist(nsecs_t when, uint32_t policyFlags) { + if (mCurrentCookedPointerData.touchingIdBits.count() == 1) { + if (mLastCookedPointerData.touchingIdBits.isEmpty()) { + // First pointer down. + uint32_t id = mCurrentCookedPointerData.touchingIdBits.firstMarkedBit(); + const PointerCoords& coords = mCurrentCookedPointerData.pointerCoordsForId(id); + if (coords.getY() >= mNavigationAssistStartY) { + // Start tracking the possible assist swipe. + mNavigation.activeAssistId = id; + return; + } + } else if (mNavigation.activeAssistId >= 0 + && mCurrentCookedPointerData.touchingIdBits.hasBit(mNavigation.activeAssistId)) { + const PointerCoords& coords = mCurrentCookedPointerData.pointerCoordsForId( + mNavigation.activeAssistId); + if (coords.getY() > mNavigationAssistEndY) { + // Swipe is still in progress. + return; + } + + // Detected assist swipe. + int32_t metaState = mContext->getGlobalMetaState(); + NotifyKeyArgs downArgs(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, + policyFlags | POLICY_FLAG_VIRTUAL, + AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_ASSIST, 0, metaState, when); + getListener()->notifyKey(&downArgs); + + NotifyKeyArgs upArgs(when, getDeviceId(), AINPUT_SOURCE_KEYBOARD, + policyFlags | POLICY_FLAG_VIRTUAL, + AKEY_EVENT_ACTION_UP, 0, AKEYCODE_ASSIST, 0, metaState, when); + getListener()->notifyKey(&upArgs); + } + } + + // Cancel the assist swipe. + mNavigation.activeAssistId = -1; +} + void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source, int32_t action, int32_t flags, int32_t metaState, int32_t buttonState, int32_t edgeFlags, const PointerProperties* properties, const PointerCoords* coords, diff --git a/services/input/InputReader.h b/services/input/InputReader.h index 8a52c06..312f19b 100644 --- a/services/input/InputReader.h +++ b/services/input/InputReader.h @@ -791,6 +791,10 @@ struct CookedPointerData { void clear(); void copyFrom(const CookedPointerData& other); + inline const PointerCoords& pointerCoordsForId(uint32_t id) const { + return pointerCoords[idToIndex[id]]; + } + inline bool isHovering(uint32_t pointerIndex) { return hoveringIdBits.hasBit(pointerProperties[pointerIndex].id); } @@ -1180,6 +1184,7 @@ protected: DEVICE_MODE_DISABLED, // input is disabled DEVICE_MODE_DIRECT, // direct mapping (touchscreen) DEVICE_MODE_UNSCALED, // unscaled mapping (touchpad) + DEVICE_MODE_NAVIGATION, // unscaled mapping with assist gesture (touch navigation) DEVICE_MODE_POINTER, // pointer mapping (pointer) }; DeviceMode mDeviceMode; @@ -1432,6 +1437,10 @@ private: // The maximum swipe width. float mPointerGestureMaxSwipeWidth; + // The start and end Y thresholds for invoking the assist navigation swipe. + float mNavigationAssistStartY; + float mNavigationAssistEndY; + struct PointerDistanceHeapElement { uint32_t currentPointerIndex : 8; uint32_t lastPointerIndex : 8; @@ -1606,6 +1615,15 @@ private: } } mPointerSimple; + struct Navigation { + // The id of a pointer that is tracking a possible assist swipe. + int32_t activeAssistId; // -1 if none + + void reset() { + activeAssistId = -1; + } + } mNavigation; + // The pointer and scroll velocity controls. VelocityControl mPointerVelocityControl; VelocityControl mWheelXVelocityControl; @@ -1641,6 +1659,8 @@ private: bool down, bool hovering); void abortPointerSimple(nsecs_t when, uint32_t policyFlags); + void dispatchNavigationAssist(nsecs_t when, uint32_t policyFlags); + // Dispatches a motion event. // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the // method will take care of setting the index and transmuting the action to DOWN or UP |