summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/java/android/view/ViewRootImpl.java616
-rw-r--r--services/input/EventHub.cpp36
-rw-r--r--services/input/EventHub.h17
-rw-r--r--services/input/InputReader.cpp61
-rw-r--r--services/input/InputReader.h20
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