OSDN Git Service

Split up the event synthesis code by source.
authorJeff Brown <jeffbrown@google.com>
Wed, 10 Apr 2013 00:46:25 +0000 (17:46 -0700)
committerJeff Brown <jeffbrown@google.com>
Wed, 10 Apr 2013 10:01:29 +0000 (03:01 -0700)
The goal is to better encapsulate this code to make it easier
to maintain and to facilitate some upcoming changes.

Some of the variables have been renamed but the logic is unchanged.

Bug: 8583760
Change-Id: I45501f7dabebcb938e42c386291d615d088a4c8c

core/java/android/view/SimulatedDpad.java [deleted file]
core/java/android/view/ViewRootImpl.java

diff --git a/core/java/android/view/SimulatedDpad.java b/core/java/android/view/SimulatedDpad.java
deleted file mode 100644 (file)
index c889328..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.app.SearchManager;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.util.Log;
-
-/**
- * This class creates DPAD events from TouchNavigation events.
- * 
- * @see ViewRootImpl
- */
-
-//TODO: Make this class an internal class of ViewRootImpl.java
-class SimulatedDpad {
-
-    private static final String TAG = "SimulatedDpad";
-
-    // 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;
-    private static final int MSG_FLICK = 313;
-    // 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;
-
-    private final Context mContext;
-
-    // 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 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;
-
-    public SimulatedDpad(Context context) {
-        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;
-
-        mContext = context;
-    }
-
-    private final Handler mHandler = new Handler(true /*async*/) {
-            @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_FLICK: {
-                    final long time = SystemClock.uptimeMillis();
-                    ViewRootImpl viewroot = (ViewRootImpl) msg.obj;
-                    // Send the key
-                    viewroot.enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, msg.arg2, 0, mLastMetaState,
-                            mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));
-                    viewroot.enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, msg.arg2, 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 msgCopy = Message.obtain(msg);
-                        msgCopy.arg1 = delay;
-                        msgCopy.setAsynchronous(true);
-                        mHandler.sendMessageDelayed(msgCopy, delay);
-                    }
-                    break;
-                }
-            }
-        }
-    };
-
-    public void updateTouchNavigation(ViewRootImpl viewroot, MotionEvent event,
-            boolean synthesizeNewKeys) {
-        if (!synthesizeNewKeys) {
-            mHandler.removeMessages(MSG_FLICK);
-        }
-        InputDevice device = event.getDevice();
-        if (device == null) {
-            return;
-        }
-        // 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) {
-                    mHandler.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 mAccumulatedXSquared = mAccumulatedX * mAccumulatedX;
-                float mAccumulatedYSquared = mAccumulatedY * mAccumulatedY;
-                // Determine if we've moved far enough to send a key press
-                if (mAccumulatedXSquared > mDistancePerTickSquared ||
-                        mAccumulatedYSquared > mDistancePerTickSquared) {
-                    float dominantAxis;
-                    float sign;
-                    boolean isXAxis;
-                    int key;
-                    int repeatCount = 0;
-                    // Determine dominant axis
-                    if (mAccumulatedXSquared > mAccumulatedYSquared) {
-                        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) {
-                            viewroot.enqueueInputEvent(new KeyEvent(time, time,
-                                    KeyEvent.ACTION_DOWN, key, 0, event.getMetaState(),
-                                    event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
-                                    event.getSource()));
-                            viewroot.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;
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-                if (time - mLastTouchNavigationStartTimeMs < MAX_TAP_TIME && mAlwaysInTapRegion) {
-                    if (synthesizeNewKeys) {
-                        viewroot.enqueueInputEvent(new KeyEvent(mLastTouchNavigationStartTimeMs,
-                                    time, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0,
-                                    event.getMetaState(), event.getDeviceId(), 0,
-                                    KeyEvent.FLAG_FALLBACK, event.getSource()));
-                        viewroot.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 = Message.obtain(mHandler, MSG_FLICK,
-                                    mKeySendRateMs, mLastKeySent, viewroot);
-                            message.setAsynchronous(true);
-                            mHandler.sendMessageDelayed(message, mKeySendRateMs);
-                        }
-                    }
-                }
-                mEdgeSwipePossible = false;
-                break;
-        }
-
-        // Store touch event position and time
-        mLastTouchNavigationEventTimeMs = time;
-        mLastTouchNavigationXPosition = event.getX();
-        mLastTouchNavigationYPosition = event.getY();
-    }
-}
index 22586f6..38d7713 100644 (file)
@@ -19,10 +19,13 @@ 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;
@@ -51,6 +54,7 @@ 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;
@@ -2923,8 +2927,6 @@ public final class ViewRootImpl implements ViewParent,
     private final static int MSG_DISPATCH_DONE_ANIMATING = 22;
     private final static int MSG_INVALIDATE_WORLD = 23;
     private final static int MSG_WINDOW_MOVED = 24;
-    private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 25;
-    private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 26;
 
     final class ViewRootHandler extends Handler {
         @Override
@@ -2974,10 +2976,6 @@ public final class ViewRootImpl implements ViewParent,
                     return "MSG_DISPATCH_DONE_ANIMATING";
                 case MSG_WINDOW_MOVED:
                     return "MSG_WINDOW_MOVED";
-                case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
-                    return "MSG_ENQUEUE_X_AXIS_KEY_REPEAT";
-                case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT:
-                    return "MSG_ENQUEUE_Y_AXIS_KEY_REPEAT";
             }
             return super.getMessageName(message);
         }
@@ -3200,18 +3198,6 @@ public final class ViewRootImpl implements ViewParent,
                     invalidateWorld(mView);
                 }
             } break;
-            case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
-            case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
-                KeyEvent oldEvent = (KeyEvent)msg.obj;
-                KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent, SystemClock.uptimeMillis(),
-                        oldEvent.getRepeatCount() + 1);
-                if (mAttachInfo.mHasWindowFocus) {
-                    enqueueInputEvent(e);
-                    Message m = obtainMessage(msg.what, e);
-                    m.setAsynchronous(true);
-                    sendMessageDelayed(m, mViewConfiguration.getKeyRepeatDelay());
-                }
-            } break;
             }
         }
     }
@@ -3877,37 +3863,34 @@ public final class ViewRootImpl implements ViewParent,
     }
 
     /**
-     * Performs default processing of unhandled input events.
+     * Performs synthesis of new input events from unhandled input events.
      */
     final class SyntheticInputStage extends InputStage {
-        private final TrackballAxis mTrackballAxisX = new TrackballAxis();
-        private final TrackballAxis mTrackballAxisY = new TrackballAxis();
-        private long mLastTrackballTime;
-
-        private int mLastJoystickXDirection;
-        private int mLastJoystickYDirection;
-        private int mLastJoystickXKeyCode;
-        private int mLastJoystickYKeyCode;
-
-        private SimulatedDpad mSimulatedDpad;
+        private final SyntheticTrackballHandler mTrackball = new SyntheticTrackballHandler();
+        private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler();
+        private final SyntheticTouchNavigationHandler mTouchNavigation =
+                new SyntheticTouchNavigationHandler();
 
         public SyntheticInputStage() {
             super(null);
-            mSimulatedDpad = new SimulatedDpad(mContext);
         }
 
         @Override
         protected int onProcess(QueuedInputEvent q) {
             q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED;
             if (q.mEvent instanceof MotionEvent) {
-                final int source = q.mEvent.getSource();
+                final MotionEvent event = (MotionEvent)q.mEvent;
+                final int source = event.getSource();
                 if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-                    return processTrackballEvent(q);
+                    mTrackball.process(event);
+                    return FINISH_HANDLED;
                 } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                    return processJoystickEvent(q);
+                    mJoystick.process(event);
+                    return FINISH_HANDLED;
                 } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
                         == InputDevice.SOURCE_TOUCH_NAVIGATION) {
-                    return processTouchNavigationEvent(q);
+                    mTouchNavigation.process(event);
+                    return FINISH_HANDLED;
                 }
             }
             return FORWARD;
@@ -3918,49 +3901,55 @@ public final class ViewRootImpl implements ViewParent,
             if ((q.mFlags & QueuedInputEvent.FLAG_RESYNTHESIZED) == 0) {
                 // Cancel related synthetic events if any prior stage has handled the event.
                 if (q.mEvent instanceof MotionEvent) {
-                    final int source = q.mEvent.getSource();
+                    final MotionEvent event = (MotionEvent)q.mEvent;
+                    final int source = event.getSource();
                     if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
-                        cancelTrackballEvent(q);
+                        mTrackball.cancel(event);
                     } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
-                        cancelJoystickEvent(q);
+                        mJoystick.cancel(event);
                     } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION)
                             == InputDevice.SOURCE_TOUCH_NAVIGATION) {
-                        cancelTouchNavigationEvent(q);
+                        mTouchNavigation.cancel(event);
                     }
                 }
             }
             super.onDeliverToNext(q);
         }
+    }
 
-        private int processTrackballEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
+    /**
+     * Creates dpad events from unhandled trackball movements.
+     */
+    final class SyntheticTrackballHandler {
+        private final TrackballAxis mX = new TrackballAxis();
+        private final TrackballAxis mY = new TrackballAxis();
+        private long mLastTime;
 
+        public void process(MotionEvent event) {
             // Translate the trackball event into DPAD keys and try to deliver those.
-            final TrackballAxis x = mTrackballAxisX;
-            final TrackballAxis y = mTrackballAxisY;
             long curTime = SystemClock.uptimeMillis();
-            if ((mLastTrackballTime + MAX_TRACKBALL_DELAY) < curTime) {
+            if ((mLastTime + MAX_TRACKBALL_DELAY) < curTime) {
                 // It has been too long since the last movement,
                 // so restart at the beginning.
-                x.reset(0);
-                y.reset(0);
-                mLastTrackballTime = curTime;
+                mX.reset(0);
+                mY.reset(0);
+                mLastTime = curTime;
             }
 
             final int action = event.getAction();
             final int metaState = event.getMetaState();
             switch (action) {
                 case MotionEvent.ACTION_DOWN:
-                    x.reset(2);
-                    y.reset(2);
+                    mX.reset(2);
+                    mY.reset(2);
                     enqueueInputEvent(new KeyEvent(curTime, curTime,
                             KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
                             KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
                             InputDevice.SOURCE_KEYBOARD));
                     break;
                 case MotionEvent.ACTION_UP:
-                    x.reset(2);
-                    y.reset(2);
+                    mX.reset(2);
+                    mY.reset(2);
                     enqueueInputEvent(new KeyEvent(curTime, curTime,
                             KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0, metaState,
                             KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
@@ -3968,14 +3957,14 @@ public final class ViewRootImpl implements ViewParent,
                     break;
             }
 
-            if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step="
-                    + x.step + " dir=" + x.dir + " acc=" + x.acceleration
+            if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + mX.position + " step="
+                    + mX.step + " dir=" + mX.dir + " acc=" + mX.acceleration
                     + " move=" + event.getX()
-                    + " / Y=" + y.position + " step="
-                    + y.step + " dir=" + y.dir + " acc=" + y.acceleration
+                    + " / Y=" + mY.position + " step="
+                    + mY.step + " dir=" + mY.dir + " acc=" + mY.acceleration
                     + " move=" + event.getY());
-            final float xOff = x.collect(event.getX(), event.getEventTime(), "X");
-            final float yOff = y.collect(event.getY(), event.getEventTime(), "Y");
+            final float xOff = mX.collect(event.getX(), event.getEventTime(), "X");
+            final float yOff = mY.collect(event.getY(), event.getEventTime(), "Y");
 
             // Generate DPAD events based on the trackball movement.
             // We pick the axis that has moved the most as the direction of
@@ -3987,20 +3976,20 @@ public final class ViewRootImpl implements ViewParent,
             int movement = 0;
             float accel = 1;
             if (xOff > yOff) {
-                movement = x.generate();
+                movement = mX.generate();
                 if (movement != 0) {
                     keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT
                             : KeyEvent.KEYCODE_DPAD_LEFT;
-                    accel = x.acceleration;
-                    y.reset(2);
+                    accel = mX.acceleration;
+                    mY.reset(2);
                 }
             } else if (yOff > 0) {
-                movement = y.generate();
+                movement = mY.generate();
                 if (movement != 0) {
                     keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN
                             : KeyEvent.KEYCODE_DPAD_UP;
-                    accel = y.acceleration;
-                    x.reset(2);
+                    accel = mY.acceleration;
+                    mX.reset(2);
                 }
             }
 
@@ -4034,16 +4023,12 @@ public final class ViewRootImpl implements ViewParent,
                             KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FALLBACK,
                             InputDevice.SOURCE_KEYBOARD));
                 }
-                mLastTrackballTime = curTime;
+                mLastTime = curTime;
             }
-
-            // Unfortunately we can't tell whether the application consumed the keys, so
-            // we always consider the trackball event handled.
-            return FINISH_HANDLED;
         }
 
-        private void cancelTrackballEvent(QueuedInputEvent q) {
-            mLastTrackballTime = Integer.MIN_VALUE;
+        public void cancel(MotionEvent event) {
+            mLastTime = Integer.MIN_VALUE;
 
             // If we reach this, we consumed a trackball event.
             // Because we will not translate the trackball event into a key event,
@@ -4052,19 +4037,220 @@ public final class ViewRootImpl implements ViewParent,
                 ensureTouchMode(false);
             }
         }
+    }
 
-        private int processJoystickEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
-            updateJoystickDirection(event, true);
-            return FINISH_HANDLED;
+    /**
+     * Maintains state information for a single trackball axis, generating
+     * discrete (DPAD) movements based on raw trackball motion.
+     */
+    static final class TrackballAxis {
+        /**
+         * The maximum amount of acceleration we will apply.
+         */
+        static final float MAX_ACCELERATION = 20;
+
+        /**
+         * The maximum amount of time (in milliseconds) between events in order
+         * for us to consider the user to be doing fast trackball movements,
+         * and thus apply an acceleration.
+         */
+        static final long FAST_MOVE_TIME = 150;
+
+        /**
+         * Scaling factor to the time (in milliseconds) between events to how
+         * much to multiple/divide the current acceleration.  When movement
+         * is < FAST_MOVE_TIME this multiplies the acceleration; when >
+         * FAST_MOVE_TIME it divides it.
+         */
+        static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
+
+        static final float FIRST_MOVEMENT_THRESHOLD = 0.5f;
+        static final float SECOND_CUMULATIVE_MOVEMENT_THRESHOLD = 2.0f;
+        static final float SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD = 1.0f;
+
+        float position;
+        float acceleration = 1;
+        long lastMoveTime = 0;
+        int step;
+        int dir;
+        int nonAccelMovement;
+
+        void reset(int _step) {
+            position = 0;
+            acceleration = 1;
+            lastMoveTime = 0;
+            step = _step;
+            dir = 0;
         }
 
-        private void cancelJoystickEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
-            updateJoystickDirection(event, false);
+        /**
+         * Add trackball movement into the state.  If the direction of movement
+         * has been reversed, the state is reset before adding the
+         * movement (so that you don't have to compensate for any previously
+         * collected movement before see the result of the movement in the
+         * new direction).
+         *
+         * @return Returns the absolute value of the amount of movement
+         * collected so far.
+         */
+        float collect(float off, long time, String axis) {
+            long normTime;
+            if (off > 0) {
+                normTime = (long)(off * FAST_MOVE_TIME);
+                if (dir < 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                    lastMoveTime = 0;
+                }
+                dir = 1;
+            } else if (off < 0) {
+                normTime = (long)((-off) * FAST_MOVE_TIME);
+                if (dir > 0) {
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
+                    position = 0;
+                    step = 0;
+                    acceleration = 1;
+                    lastMoveTime = 0;
+                }
+                dir = -1;
+            } else {
+                normTime = 0;
+            }
+
+            // The number of milliseconds between each movement that is
+            // considered "normal" and will not result in any acceleration
+            // or deceleration, scaled by the offset we have here.
+            if (normTime > 0) {
+                long delta = time - lastMoveTime;
+                lastMoveTime = time;
+                float acc = acceleration;
+                if (delta < normTime) {
+                    // The user is scrolling rapidly, so increase acceleration.
+                    float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
+                    if (scale > 1) acc *= scale;
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
+                            + off + " normTime=" + normTime + " delta=" + delta
+                            + " scale=" + scale + " acc=" + acc);
+                    acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
+                } else {
+                    // The user is scrolling slowly, so decrease acceleration.
+                    float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
+                    if (scale > 1) acc /= scale;
+                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
+                            + off + " normTime=" + normTime + " delta=" + delta
+                            + " scale=" + scale + " acc=" + acc);
+                    acceleration = acc > 1 ? acc : 1;
+                }
+            }
+            position += off;
+            return Math.abs(position);
         }
 
-        private void updateJoystickDirection(MotionEvent event, boolean synthesizeNewKeys) {
+        /**
+         * Generate the number of discrete movement events appropriate for
+         * the currently collected trackball movement.
+         *
+         * @return Returns the number of discrete movements, either positive
+         * or negative, or 0 if there is not enough trackball movement yet
+         * for a discrete movement.
+         */
+        int generate() {
+            int movement = 0;
+            nonAccelMovement = 0;
+            do {
+                final int dir = position >= 0 ? 1 : -1;
+                switch (step) {
+                    // If we are going to execute the first step, then we want
+                    // to do this as soon as possible instead of waiting for
+                    // a full movement, in order to make things look responsive.
+                    case 0:
+                        if (Math.abs(position) < FIRST_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        nonAccelMovement += dir;
+                        step = 1;
+                        break;
+                    // If we have generated the first movement, then we need
+                    // to wait for the second complete trackball motion before
+                    // generating the second discrete movement.
+                    case 1:
+                        if (Math.abs(position) < SECOND_CUMULATIVE_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        nonAccelMovement += dir;
+                        position -= SECOND_CUMULATIVE_MOVEMENT_THRESHOLD * dir;
+                        step = 2;
+                        break;
+                    // After the first two, we generate discrete movements
+                    // consistently with the trackball, applying an acceleration
+                    // if the trackball is moving quickly.  This is a simple
+                    // acceleration on top of what we already compute based
+                    // on how quickly the wheel is being turned, to apply
+                    // a longer increasing acceleration to continuous movement
+                    // in one direction.
+                    default:
+                        if (Math.abs(position) < SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD) {
+                            return movement;
+                        }
+                        movement += dir;
+                        position -= dir * SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD;
+                        float acc = acceleration;
+                        acc *= 1.1f;
+                        acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
+                        break;
+                }
+            } while (true);
+        }
+    }
+
+    /**
+     * Creates dpad events from unhandled joystick movements.
+     */
+    final class SyntheticJoystickHandler extends Handler {
+        private final static int MSG_ENQUEUE_X_AXIS_KEY_REPEAT = 1;
+        private final static int MSG_ENQUEUE_Y_AXIS_KEY_REPEAT = 2;
+
+        private int mLastXDirection;
+        private int mLastYDirection;
+        private int mLastXKeyCode;
+        private int mLastYKeyCode;
+
+        public SyntheticJoystickHandler() {
+            super(true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ENQUEUE_X_AXIS_KEY_REPEAT:
+                case MSG_ENQUEUE_Y_AXIS_KEY_REPEAT: {
+                    KeyEvent oldEvent = (KeyEvent)msg.obj;
+                    KeyEvent e = KeyEvent.changeTimeRepeat(oldEvent,
+                            SystemClock.uptimeMillis(),
+                            oldEvent.getRepeatCount() + 1);
+                    if (mAttachInfo.mHasWindowFocus) {
+                        enqueueInputEvent(e);
+                        Message m = obtainMessage(msg.what, e);
+                        m.setAsynchronous(true);
+                        sendMessageDelayed(m, ViewConfiguration.getKeyRepeatDelay());
+                    }
+                } break;
+            }
+        }
+
+        public void process(MotionEvent event) {
+            update(event, true);
+        }
+
+        public void cancel(MotionEvent event) {
+            update(event, false);
+        }
+
+        private void update(MotionEvent event, boolean synthesizeNewKeys) {
             final long time = event.getEventTime();
             final int metaState = event.getMetaState();
             final int deviceId = event.getDeviceId();
@@ -4082,51 +4268,51 @@ public final class ViewRootImpl implements ViewParent,
                 yDirection = joystickAxisValueToDirection(event.getY());
             }
 
-            if (xDirection != mLastJoystickXDirection) {
-                if (mLastJoystickXKeyCode != 0) {
-                    mHandler.removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
+            if (xDirection != mLastXDirection) {
+                if (mLastXKeyCode != 0) {
+                    removeMessages(MSG_ENQUEUE_X_AXIS_KEY_REPEAT);
                     enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, mLastJoystickXKeyCode, 0, metaState,
+                            KeyEvent.ACTION_UP, mLastXKeyCode, 0, metaState,
                             deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
-                    mLastJoystickXKeyCode = 0;
+                    mLastXKeyCode = 0;
                 }
 
-                mLastJoystickXDirection = xDirection;
+                mLastXDirection = xDirection;
 
                 if (xDirection != 0 && synthesizeNewKeys) {
-                    mLastJoystickXKeyCode = xDirection > 0
+                    mLastXKeyCode = xDirection > 0
                             ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT;
                     final KeyEvent e = new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, mLastJoystickXKeyCode, 0, metaState,
+                            KeyEvent.ACTION_DOWN, mLastXKeyCode, 0, metaState,
                             deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
                     enqueueInputEvent(e);
-                    Message m = mHandler.obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
+                    Message m = obtainMessage(MSG_ENQUEUE_X_AXIS_KEY_REPEAT, e);
                     m.setAsynchronous(true);
                     mHandler.sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
                 }
             }
 
-            if (yDirection != mLastJoystickYDirection) {
-                if (mLastJoystickYKeyCode != 0) {
-                    mHandler.removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
+            if (yDirection != mLastYDirection) {
+                if (mLastYKeyCode != 0) {
+                    removeMessages(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT);
                     enqueueInputEvent(new KeyEvent(time, time,
-                            KeyEvent.ACTION_UP, mLastJoystickYKeyCode, 0, metaState,
+                            KeyEvent.ACTION_UP, mLastYKeyCode, 0, metaState,
                             deviceId, 0, KeyEvent.FLAG_FALLBACK, source));
-                    mLastJoystickYKeyCode = 0;
+                    mLastYKeyCode = 0;
                 }
 
-                mLastJoystickYDirection = yDirection;
+                mLastYDirection = yDirection;
 
                 if (yDirection != 0 && synthesizeNewKeys) {
-                    mLastJoystickYKeyCode = yDirection > 0
+                    mLastYKeyCode = yDirection > 0
                             ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
                     final KeyEvent e = new KeyEvent(time, time,
-                            KeyEvent.ACTION_DOWN, mLastJoystickYKeyCode, 0, metaState,
+                            KeyEvent.ACTION_DOWN, mLastYKeyCode, 0, metaState,
                             deviceId, 0, KeyEvent.FLAG_FALLBACK, source);
                     enqueueInputEvent(e);
-                    Message m = mHandler.obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
+                    Message m = obtainMessage(MSG_ENQUEUE_Y_AXIS_KEY_REPEAT, e);
                     m.setAsynchronous(true);
-                    mHandler.sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
+                    sendMessageDelayed(m, ViewConfiguration.getKeyRepeatTimeout());
                 }
             }
         }
@@ -4140,16 +4326,288 @@ public final class ViewRootImpl implements ViewParent,
                 return 0;
             }
         }
+    }
 
-        private int processTouchNavigationEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
-            mSimulatedDpad.updateTouchNavigation(ViewRootImpl.this, event, true);
-            return FINISH_HANDLED;
+    /**
+     * 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 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;
+
+        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;
         }
 
-        private void cancelTouchNavigationEvent(QueuedInputEvent q) {
-            final MotionEvent event = (MotionEvent)q.mEvent;
-            mSimulatedDpad.updateTouchNavigation(ViewRootImpl.this, event, false);
+        @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);
+        }
+
+        public void cancel(MotionEvent event) {
+            update(event, false);
+        }
+
+        private void update(MotionEvent event, boolean synthesizeNewKeys) {
+            if (!synthesizeNewKeys) {
+                removeMessages(MSG_FLICK);
+            }
+
+            InputDevice device = event.getDevice();
+            if (device == null) {
+                return;
+            }
+
+            // 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;
+                }
+
+                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;
+                    }
+                    break;
+                }
+
+                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);
+                            }
+                        }
+                    }
+                    mEdgeSwipePossible = false;
+                    break;
+                }
+            }
+
+            // Store touch event position and time
+            mLastTouchNavigationEventTimeMs = time;
+            mLastTouchNavigationXPosition = event.getX();
+            mLastTouchNavigationYPosition = event.getY();
         }
     }
 
@@ -5487,174 +5945,6 @@ public final class ViewRootImpl implements ViewParent,
         }
     }
 
-    /**
-     * Maintains state information for a single trackball axis, generating
-     * discrete (DPAD) movements based on raw trackball motion.
-     */
-    static final class TrackballAxis {
-        /**
-         * The maximum amount of acceleration we will apply.
-         */
-        static final float MAX_ACCELERATION = 20;
-
-        /**
-         * The maximum amount of time (in milliseconds) between events in order
-         * for us to consider the user to be doing fast trackball movements,
-         * and thus apply an acceleration.
-         */
-        static final long FAST_MOVE_TIME = 150;
-
-        /**
-         * Scaling factor to the time (in milliseconds) between events to how
-         * much to multiple/divide the current acceleration.  When movement
-         * is < FAST_MOVE_TIME this multiplies the acceleration; when >
-         * FAST_MOVE_TIME it divides it.
-         */
-        static final float ACCEL_MOVE_SCALING_FACTOR = (1.0f/40);
-
-        static final float FIRST_MOVEMENT_THRESHOLD = 0.5f;
-        static final float SECOND_CUMULATIVE_MOVEMENT_THRESHOLD = 2.0f;
-        static final float SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD = 1.0f;
-
-        float position;
-        float acceleration = 1;
-        long lastMoveTime = 0;
-        int step;
-        int dir;
-        int nonAccelMovement;
-
-        void reset(int _step) {
-            position = 0;
-            acceleration = 1;
-            lastMoveTime = 0;
-            step = _step;
-            dir = 0;
-        }
-
-        /**
-         * Add trackball movement into the state.  If the direction of movement
-         * has been reversed, the state is reset before adding the
-         * movement (so that you don't have to compensate for any previously
-         * collected movement before see the result of the movement in the
-         * new direction).
-         *
-         * @return Returns the absolute value of the amount of movement
-         * collected so far.
-         */
-        float collect(float off, long time, String axis) {
-            long normTime;
-            if (off > 0) {
-                normTime = (long)(off * FAST_MOVE_TIME);
-                if (dir < 0) {
-                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to positive!");
-                    position = 0;
-                    step = 0;
-                    acceleration = 1;
-                    lastMoveTime = 0;
-                }
-                dir = 1;
-            } else if (off < 0) {
-                normTime = (long)((-off) * FAST_MOVE_TIME);
-                if (dir > 0) {
-                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " reversed to negative!");
-                    position = 0;
-                    step = 0;
-                    acceleration = 1;
-                    lastMoveTime = 0;
-                }
-                dir = -1;
-            } else {
-                normTime = 0;
-            }
-
-            // The number of milliseconds between each movement that is
-            // considered "normal" and will not result in any acceleration
-            // or deceleration, scaled by the offset we have here.
-            if (normTime > 0) {
-                long delta = time - lastMoveTime;
-                lastMoveTime = time;
-                float acc = acceleration;
-                if (delta < normTime) {
-                    // The user is scrolling rapidly, so increase acceleration.
-                    float scale = (normTime-delta) * ACCEL_MOVE_SCALING_FACTOR;
-                    if (scale > 1) acc *= scale;
-                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " accelerate: off="
-                            + off + " normTime=" + normTime + " delta=" + delta
-                            + " scale=" + scale + " acc=" + acc);
-                    acceleration = acc < MAX_ACCELERATION ? acc : MAX_ACCELERATION;
-                } else {
-                    // The user is scrolling slowly, so decrease acceleration.
-                    float scale = (delta-normTime) * ACCEL_MOVE_SCALING_FACTOR;
-                    if (scale > 1) acc /= scale;
-                    if (DEBUG_TRACKBALL) Log.v(TAG, axis + " deccelerate: off="
-                            + off + " normTime=" + normTime + " delta=" + delta
-                            + " scale=" + scale + " acc=" + acc);
-                    acceleration = acc > 1 ? acc : 1;
-                }
-            }
-            position += off;
-            return Math.abs(position);
-        }
-
-        /**
-         * Generate the number of discrete movement events appropriate for
-         * the currently collected trackball movement.
-         *
-         * @return Returns the number of discrete movements, either positive
-         * or negative, or 0 if there is not enough trackball movement yet
-         * for a discrete movement.
-         */
-        int generate() {
-            int movement = 0;
-            nonAccelMovement = 0;
-            do {
-                final int dir = position >= 0 ? 1 : -1;
-                switch (step) {
-                    // If we are going to execute the first step, then we want
-                    // to do this as soon as possible instead of waiting for
-                    // a full movement, in order to make things look responsive.
-                    case 0:
-                        if (Math.abs(position) < FIRST_MOVEMENT_THRESHOLD) {
-                            return movement;
-                        }
-                        movement += dir;
-                        nonAccelMovement += dir;
-                        step = 1;
-                        break;
-                    // If we have generated the first movement, then we need
-                    // to wait for the second complete trackball motion before
-                    // generating the second discrete movement.
-                    case 1:
-                        if (Math.abs(position) < SECOND_CUMULATIVE_MOVEMENT_THRESHOLD) {
-                            return movement;
-                        }
-                        movement += dir;
-                        nonAccelMovement += dir;
-                        position -= SECOND_CUMULATIVE_MOVEMENT_THRESHOLD * dir;
-                        step = 2;
-                        break;
-                    // After the first two, we generate discrete movements
-                    // consistently with the trackball, applying an acceleration
-                    // if the trackball is moving quickly.  This is a simple
-                    // acceleration on top of what we already compute based
-                    // on how quickly the wheel is being turned, to apply
-                    // a longer increasing acceleration to continuous movement
-                    // in one direction.
-                    default:
-                        if (Math.abs(position) < SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD) {
-                            return movement;
-                        }
-                        movement += dir;
-                        position -= dir * SUBSEQUENT_INCREMENTAL_MOVEMENT_THRESHOLD;
-                        float acc = acceleration;
-                        acc *= 1.1f;
-                        acceleration = acc < MAX_ACCELERATION ? acc : acceleration;
-                        break;
-                }
-            } while (true);
-        }
-    }
-
     public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
         public CalledFromWrongThreadException(String msg) {
             super(msg);