am 4dac901f: Rewrite touch navigation dpad synthesis.

* commit '4dac901f011e7c15882e260441225633a6435e49':
  Rewrite touch navigation dpad synthesis.
This commit is contained in:
Jeff Brown
2013-04-10 03:56:56 -07:00
committed by Android Git Automerger
5 changed files with 494 additions and 254 deletions

View File

@@ -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;
private static final String LOCAL_TAG = "SyntheticTouchNavigationHandler";
private static final boolean LOCAL_DEBUG = false;
// 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;
// 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;
// Where the cutoff is for determining an edge swipe
private static final float EDGE_SWIPE_THRESHOLD = 0.9f;
/* TODO: These constants should eventually be moved to ViewConfiguration. */
// TODO: Pass touch slop from the input device
private static final int TOUCH_SLOP = 30;
// Tap timeout in milliseconds.
private static final int TAP_TIMEOUT = 250;
// 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;
// The maximum distance traveled for a gesture to be considered a tap in millimeters.
private static final int TAP_SLOP_MILLIMETERS = 5;
// 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.
// 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;
// Set to true if any movement was delivered to the app.
// Implies that tap slop was exceeded.
private boolean mConsumedMovement;
// 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;
// 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;
// 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;
// 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;
// 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);
}
break;
}
}
}
public void process(MotionEvent event) {
update(event, true);
}
// 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;
public void cancel(MotionEvent event) {
update(event, false);
}
// 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;
private void update(MotionEvent event, boolean synthesizeNewKeys) {
if (!synthesizeNewKeys) {
removeMessages(MSG_FLICK);
// 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);
}
}
}
}
InputDevice device = event.getDevice();
if (device == null) {
if (!mCurrentDeviceSupported) {
return;
}
// Store what time the TouchNavigation event occurred
final long time = SystemClock.uptimeMillis();
switch (event.getAction()) {
// Handle the event.
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
mLastTouchNavigationStartTimeMs = time;
mAlwaysInTapRegion = true;
mTouchNavigationEnterXPosition = event.getX();
mTouchNavigationEnterYPosition = event.getY();
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;
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;
}
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;
}
// 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");
}
}
// 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;
}
// 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 (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 (mActivePointerId < 0) {
break;
}
final int index = event.findPointerIndex(mActivePointerId);
if (index < 0) {
finishKeys(time);
finishTracking(time);
break;
}
if (synthesizeNewKeys) {
Message message = obtainMessage(
MSG_FLICK, mKeySendRateMs, mLastKeySent);
message.setAsynchronous(true);
sendMessageDelayed(message, mKeySendRateMs);
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);
}
mEdgeSwipePossible = false;
break;
}
case MotionEvent.ACTION_CANCEL: {
finishKeys(time);
finishTracking(time);
break;
}
}
// Store touch event position and time
mLastTouchNavigationEventTimeMs = time;
mLastTouchNavigationXPosition = event.getX();
mLastTouchNavigationYPosition = event.getY();
}
public void cancel(MotionEvent event) {
if (mCurrentDeviceId == event.getDeviceId()
&& mCurrentSource == event.getSource()) {
final long time = event.getEventTime();
finishKeys(time);
finishTracking(time);
}
}
private void finishKeys(long time) {
cancelFling();
sendKeyUp(time);
}
private void finishTracking(long time) {
if (mActivePointerId >= 0) {
mActivePointerId = -1;
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
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;
}
}
}
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);
}
// 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;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (vx >= mConfigMinFlingVelocity
&& Math.abs(vy) < mConfigMinFlingVelocity) {
mFlingVelocity = vx;
break;
}
return false;
case KeyEvent.KEYCODE_DPAD_UP:
if (-vy >= mConfigMinFlingVelocity
&& Math.abs(vx) < mConfigMinFlingVelocity) {
mFlingVelocity = -vy;
break;
}
return false;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (vy >= mConfigMinFlingVelocity
&& Math.abs(vx) < mConfigMinFlingVelocity) {
mFlingVelocity = vy;
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;
}
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);
}
}
};
}
/**

View File

@@ -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.

View File

@@ -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();

View File

@@ -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,

View File

@@ -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