Joystick tweaks. (DO NOT MERGE)

Ensure that the joystick can always reach -1.0, 0.0 and 1.0 positions
even when noise filtering is applied.  (Bug: 3514510)

Add support for a few more standard axes.

Add additional mapping modes for axes.
Some axes are inverted from standard interpretation
or are actually intended to be split into two distict axes
such as left/right trigger controls or accelerator/brake.

Add key layout file for a G25 racing wheel and XBox 360 controller
to tweak behavior.  They work fine without them but the axis mappings
are not ideal.

Change-Id: I0fddd90309af4dc14d35f34fe99ed6e521c0b7c7
diff --git a/api/current.xml b/api/current.xml
index 422522e..142ff2b 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -218115,6 +218115,28 @@
  visibility="public"
 >
 </field>
+<field name="AXIS_BRAKE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="23"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="AXIS_GAS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="22"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="AXIS_GENERIC_1"
  type="int"
  transient="false"
@@ -218368,6 +218390,17 @@
  visibility="public"
 >
 </field>
+<field name="AXIS_RUDDER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="20"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="AXIS_RX"
  type="int"
  transient="false"
@@ -218412,6 +218445,17 @@
  visibility="public"
 >
 </field>
+<field name="AXIS_THROTTLE"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="19"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="AXIS_TOOL_MAJOR"
  type="int"
  transient="false"
@@ -218467,6 +218511,17 @@
  visibility="public"
 >
 </field>
+<field name="AXIS_WHEEL"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="21"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="AXIS_X"
  type="int"
  transient="false"
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 076f712..3c39149 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -675,6 +675,87 @@
     public static final int AXIS_RTRIGGER = 18;
 
     /**
+     * Constant used to identify the Throttle axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the throttle control.
+     * The value is normalized to a range from 0.0 (fully open) to 1.0 (fully closed).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_THROTTLE = 19;
+
+    /**
+     * Constant used to identify the Rudder axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the rudder control.
+     * The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_RUDDER = 20;
+
+    /**
+     * Constant used to identify the Wheel axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the steering wheel control.
+     * The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_WHEEL = 21;
+
+    /**
+     * Constant used to identify the Gas axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the gas (accelerator) control.
+     * The value is normalized to a range from 0.0 (no acceleration)
+     * to 1.0 (maximum acceleration).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_GAS = 22;
+
+    /**
+     * Constant used to identify the Brake axis of a motion event.
+     * <p>
+     * <ul>
+     * <li>For a joystick, reports the absolute position of the brake control.
+     * The value is normalized to a range from 0.0 (no braking) to 1.0 (maximum braking).
+     * </ul>
+     * </p>
+     *
+     * @see #getAxisValue(int, int)
+     * @see #getHistoricalAxisValue(int, int, int)
+     * @see MotionEvent.PointerCoords#getAxisValue(int)
+     * @see InputDevice#getMotionRange
+     */
+    public static final int AXIS_BRAKE = 23;
+
+    /**
      * Constant used to identify the Generic 1 axis of a motion event.
      * The interpretation of a generic axis is device-specific.
      *
@@ -877,6 +958,11 @@
         names.append(AXIS_HAT_Y, "AXIS_HAT_Y");
         names.append(AXIS_LTRIGGER, "AXIS_LTRIGGER");
         names.append(AXIS_RTRIGGER, "AXIS_RTRIGGER");
+        names.append(AXIS_THROTTLE, "AXIS_THROTTLE");
+        names.append(AXIS_RUDDER, "AXIS_RUDDER");
+        names.append(AXIS_WHEEL, "AXIS_WHEEL");
+        names.append(AXIS_GAS, "AXIS_GAS");
+        names.append(AXIS_BRAKE, "AXIS_BRAKE");
         names.append(AXIS_GENERIC_1, "AXIS_GENERIC_1");
         names.append(AXIS_GENERIC_2, "AXIS_GENERIC_2");
         names.append(AXIS_GENERIC_3, "AXIS_GENERIC_3");
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 6d925d6..1428b63 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -409,6 +409,10 @@
 axis 0x03 RX
 axis 0x04 RY
 axis 0x05 RZ
+axis 0x06 THROTTLE
+axis 0x07 RUDDER
+axis 0x08 WHEEL
+axis 0x09 GAS
+axis 0x0a BRAKE
 axis 0x10 HAT_X
 axis 0x11 HAT_Y
- 
\ No newline at end of file
diff --git a/data/keyboards/Vendor_045e_Product_028e.kl b/data/keyboards/Vendor_045e_Product_028e.kl
new file mode 100644
index 0000000..99f046a
--- /dev/null
+++ b/data/keyboards/Vendor_045e_Product_028e.kl
@@ -0,0 +1,46 @@
+# Copyright (C) 2011 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.
+
+#
+# XBox 360 USB Controller
+#
+
+key 304   BUTTON_A
+key 305   BUTTON_B
+key 307   BUTTON_X
+key 308   BUTTON_Y
+key 310   BUTTON_L1
+key 311   BUTTON_R1
+key 314   BUTTON_SELECT
+key 315   BUTTON_START
+key 316   BUTTON_MODE
+key 317   BUTTON_THUMBL
+key 318   BUTTON_THUMBR
+
+# Left and right stick.
+# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
+# This confuses applications that rely on the flat value because the joystick actually
+# settles in a flat range of +/- 4096 or so.
+axis 0x00 X flat 4096
+axis 0x01 Y flat 4096
+axis 0x03 Z flat 4096
+axis 0x04 RZ flat 4096
+
+# Triggers.
+axis 0x02 LTRIGGER
+axis 0x05 RTRIGGER
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
diff --git a/data/keyboards/Vendor_046d_Product_c294.kl b/data/keyboards/Vendor_046d_Product_c294.kl
new file mode 100644
index 0000000..5492f49
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_c294.kl
@@ -0,0 +1,53 @@
+# Copyright (C) 2011 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.
+
+#
+# Logitech G25 Racing Wheel (in Compatibility Mode)
+#
+
+# 4 way buttons above hat
+key 0x121    BUTTON_A
+key 0x123    BUTTON_B
+key 0x120    BUTTON_X
+key 0x122    BUTTON_Y
+
+# Row of buttons under hat
+key 0x12b    BUTTON_1
+key 0x128    BUTTON_2
+key 0x129    BUTTON_3
+key 0x12a    BUTTON_4
+
+# Gear shift positions
+#   0x12a top-left gear (aliased as BUTTON_4)
+#   0x12b bottom-left gear (aliased as BUTTON_1)
+
+# Buttons on wheel
+key 0x127    BUTTON_L1
+key 0x126    BUTTON_R1
+
+# Toggles under wheel
+key 0x125    BUTTON_L2
+key 0x124    BUTTON_R2
+
+# Hat
+axis 0x10    HAT_X
+axis 0x11    HAT_Y
+
+# Steering Wheel
+axis 0x00    WHEEL
+
+# Accelerator / Brake
+#   00..7e : accelerator
+#   80..ff : brake
+axis 0x01    split 0x7f GAS BRAKE
diff --git a/data/keyboards/Vendor_046d_Product_c299.kl b/data/keyboards/Vendor_046d_Product_c299.kl
new file mode 100644
index 0000000..d42963d
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_c299.kl
@@ -0,0 +1,62 @@
+# Copyright (C) 2011 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.
+
+#
+# Logitech G25 Racing Wheel (in Native Mode)
+#
+
+# 4 way buttons above hat
+key 0x121    BUTTON_A
+key 0x123    BUTTON_B
+key 0x120    BUTTON_X
+key 0x122    BUTTON_Y
+
+# Row of buttons under hat
+key 0x12b    BUTTON_1
+key 0x128    BUTTON_2
+key 0x129    BUTTON_3
+key 0x12a    BUTTON_4
+
+# Gear shift positions
+key 0x12c    BUTTON_5
+key 0x12d    BUTTON_6
+key 0x12e    BUTTON_7
+key 0x12f    BUTTON_8
+key 0x2d0    BUTTON_9
+key 0x2d1    BUTTON_10
+key 0x2d2    BUTTON_11
+
+# Buttons on wheel
+key 0x127    BUTTON_L1
+key 0x126    BUTTON_R1
+
+# Toggles under wheel
+key 0x125    BUTTON_L2
+key 0x124    BUTTON_R2
+
+# Hat
+axis 0x10    HAT_X
+axis 0x11    HAT_Y
+
+# Steering Wheel
+axis 0x00    WHEEL
+
+# Clutch
+axis 0x01    invert GENERIC_1
+
+# Accelerator
+axis 0x02    invert GAS
+
+# Brake
+axis 0x05    invert BRAKE
diff --git a/data/keyboards/common.mk b/data/keyboards/common.mk
index 5b367b9..335298c 100644
--- a/data/keyboards/common.mk
+++ b/data/keyboards/common.mk
@@ -19,7 +19,10 @@
     Generic.kl \
     AVRCP.kl \
     qwerty.kl \
+    Vendor_045e_Product_028e.kl \
     Vendor_046d_Product_c216.kl \
+    Vendor_046d_Product_c294.kl \
+    Vendor_046d_Product_c299.kl \
     Vendor_046d_Product_c532.kl \
     Vendor_054c_Product_0268.kl \
     Vendor_05ac_Product_0239.kl \
diff --git a/include/ui/KeyLayoutMap.h b/include/ui/KeyLayoutMap.h
index 904c8f3..d82d0c8 100644
--- a/include/ui/KeyLayoutMap.h
+++ b/include/ui/KeyLayoutMap.h
@@ -24,6 +24,36 @@
 
 namespace android {
 
+struct AxisInfo {
+    enum Mode {
+        // Axis value is reported directly.
+        MODE_NORMAL = 0,
+        // Axis value should be inverted before reporting.
+        MODE_INVERT = 1,
+        // Axis value should be split into two axes
+        MODE_SPLIT = 2,
+    };
+
+    // Axis mode.
+    Mode mode;
+
+    // Axis id.
+    // When split, this is the axis used for values smaller than the split position.
+    int32_t axis;
+
+    // When split, this is the axis used for values after higher than the split position.
+    int32_t highAxis;
+
+    // The split value, or 0 if not split.
+    int32_t splitValue;
+
+    // The flat value, or -1 if none.
+    int32_t flatOverride;
+
+    AxisInfo() : mode(MODE_NORMAL), axis(-1), highAxis(-1), splitValue(0), flatOverride(-1) {
+    }
+};
+
 /**
  * Describes a mapping from keyboard scan codes and joystick axes to Android key codes and axes.
  */
@@ -36,7 +66,7 @@
     status_t mapKey(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const;
     status_t findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const;
 
-    status_t mapAxis(int32_t scanCode, int32_t* axis) const;
+    status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
 
 private:
     struct Key {
@@ -45,7 +75,7 @@
     };
 
     KeyedVector<int32_t, Key> mKeys;
-    KeyedVector<int32_t, int32_t> mAxes;
+    KeyedVector<int32_t, AxisInfo> mAxes;
 
     KeyLayoutMap();
 
diff --git a/include/ui/KeycodeLabels.h b/include/ui/KeycodeLabels.h
index bdfbf7c..b912e9b 100755
--- a/include/ui/KeycodeLabels.h
+++ b/include/ui/KeycodeLabels.h
@@ -270,6 +270,11 @@
     { "HAT_Y", 16 },
     { "LTRIGGER", 17 },
     { "RTRIGGER", 18 },
+    { "THROTTLE", 19 },
+    { "RUDDER", 20 },
+    { "WHEEL", 21 },
+    { "GAS", 22 },
+    { "BRAKE", 23 },
     { "GENERIC_1", 32 },
     { "GENERIC_2", 33 },
     { "GENERIC_3", 34 },
diff --git a/libs/ui/KeyLayoutMap.cpp b/libs/ui/KeyLayoutMap.cpp
index 2ed0e66..8626a03 100644
--- a/libs/ui/KeyLayoutMap.cpp
+++ b/libs/ui/KeyLayoutMap.cpp
@@ -113,20 +113,23 @@
     return NO_ERROR;
 }
 
-status_t KeyLayoutMap::mapAxis(int32_t scanCode, int32_t* axis) const {
+status_t KeyLayoutMap::mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const {
     ssize_t index = mAxes.indexOfKey(scanCode);
     if (index < 0) {
 #if DEBUG_MAPPING
         LOGD("mapAxis: scanCode=%d ~ Failed.", scanCode);
 #endif
-        *axis = -1;
         return NAME_NOT_FOUND;
     }
 
-    *axis = mAxes.valueAt(index);
+    *outAxisInfo = mAxes.valueAt(index);
 
 #if DEBUG_MAPPING
-    LOGD("mapAxis: scanCode=%d ~ Result axis=%d.", scanCode, *axis);
+    LOGD("mapAxis: scanCode=%d ~ Result mode=%d, axis=%d, highAxis=%d, "
+            "splitValue=%d, flatOverride=%d.",
+            scanCode,
+            outAxisInfo->mode, outAxisInfo->axis, outAxisInfo->highAxis,
+            outAxisInfo->splitValue, outAxisInfo->flatOverride);
 #endif
     return NO_ERROR;
 }
@@ -249,19 +252,89 @@
         return BAD_VALUE;
     }
 
+    AxisInfo axisInfo;
+
     mTokenizer->skipDelimiters(WHITESPACE);
-    String8 axisToken = mTokenizer->nextToken(WHITESPACE);
-    int32_t axis = getAxisByLabel(axisToken.string());
-    if (axis < 0) {
-        LOGE("%s: Expected axis label, got '%s'.", mTokenizer->getLocation().string(),
-                axisToken.string());
-        return BAD_VALUE;
+    String8 token = mTokenizer->nextToken(WHITESPACE);
+    if (token == "invert") {
+        axisInfo.mode = AxisInfo::MODE_INVERT;
+
+        mTokenizer->skipDelimiters(WHITESPACE);
+        String8 axisToken = mTokenizer->nextToken(WHITESPACE);
+        axisInfo.axis = getAxisByLabel(axisToken.string());
+        if (axisInfo.axis < 0) {
+            LOGE("%s: Expected inverted axis label, got '%s'.",
+                    mTokenizer->getLocation().string(), axisToken.string());
+            return BAD_VALUE;
+        }
+    } else if (token == "split") {
+        axisInfo.mode = AxisInfo::MODE_SPLIT;
+
+        mTokenizer->skipDelimiters(WHITESPACE);
+        String8 splitToken = mTokenizer->nextToken(WHITESPACE);
+        axisInfo.splitValue = int32_t(strtol(splitToken.string(), &end, 0));
+        if (*end) {
+            LOGE("%s: Expected split value, got '%s'.",
+                    mTokenizer->getLocation().string(), splitToken.string());
+            return BAD_VALUE;
+        }
+
+        mTokenizer->skipDelimiters(WHITESPACE);
+        String8 lowAxisToken = mTokenizer->nextToken(WHITESPACE);
+        axisInfo.axis = getAxisByLabel(lowAxisToken.string());
+        if (axisInfo.axis < 0) {
+            LOGE("%s: Expected low axis label, got '%s'.",
+                    mTokenizer->getLocation().string(), lowAxisToken.string());
+            return BAD_VALUE;
+        }
+
+        mTokenizer->skipDelimiters(WHITESPACE);
+        String8 highAxisToken = mTokenizer->nextToken(WHITESPACE);
+        axisInfo.highAxis = getAxisByLabel(highAxisToken.string());
+        if (axisInfo.highAxis < 0) {
+            LOGE("%s: Expected high axis label, got '%s'.",
+                    mTokenizer->getLocation().string(), highAxisToken.string());
+            return BAD_VALUE;
+        }
+    } else {
+        axisInfo.axis = getAxisByLabel(token.string());
+        if (axisInfo.axis < 0) {
+            LOGE("%s: Expected axis label, 'split' or 'invert', got '%s'.",
+                    mTokenizer->getLocation().string(), token.string());
+            return BAD_VALUE;
+        }
+    }
+
+    for (;;) {
+        mTokenizer->skipDelimiters(WHITESPACE);
+        if (mTokenizer->isEol()) {
+            break;
+        }
+        String8 keywordToken = mTokenizer->nextToken(WHITESPACE);
+        if (keywordToken == "flat") {
+            mTokenizer->skipDelimiters(WHITESPACE);
+            String8 flatToken = mTokenizer->nextToken(WHITESPACE);
+            axisInfo.flatOverride = int32_t(strtol(flatToken.string(), &end, 0));
+            if (*end) {
+                LOGE("%s: Expected flat value, got '%s'.",
+                        mTokenizer->getLocation().string(), flatToken.string());
+                return BAD_VALUE;
+            }
+        } else {
+            LOGE("%s: Expected keyword 'flat', got '%s'.",
+                    mTokenizer->getLocation().string(), keywordToken.string());
+            return BAD_VALUE;
+        }
     }
 
 #if DEBUG_PARSER
-    LOGD("Parsed axis: scanCode=%d, axis=%d.", scanCode, axis);
+    LOGD("Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, "
+            "splitValue=%d, flatOverride=%d.",
+            scanCode,
+            axisInfo.mode, axisInfo.axis, axisInfo.highAxis,
+            axisInfo.splitValue, axisInfo.flatOverride);
 #endif
-    mMap->mAxes.add(scanCode, axis);
+    mMap->mAxes.add(scanCode, axisInfo);
     return NO_ERROR;
 }
 
diff --git a/native/include/android/input.h b/native/include/android/input.h
index f19e8be..86be54a 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -359,6 +359,11 @@
     AMOTION_EVENT_AXIS_HAT_Y = 16,
     AMOTION_EVENT_AXIS_LTRIGGER = 17,
     AMOTION_EVENT_AXIS_RTRIGGER = 18,
+    AMOTION_EVENT_AXIS_THROTTLE = 19,
+    AMOTION_EVENT_AXIS_RUDDER = 20,
+    AMOTION_EVENT_AXIS_WHEEL = 21,
+    AMOTION_EVENT_AXIS_GAS = 22,
+    AMOTION_EVENT_AXIS_BRAKE = 23,
     AMOTION_EVENT_AXIS_GENERIC_1 = 32,
     AMOTION_EVENT_AXIS_GENERIC_2 = 33,
     AMOTION_EVENT_AXIS_GENERIC_3 = 34,
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 2fe5980..e37a6b1 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -352,14 +352,13 @@
     return NAME_NOT_FOUND;
 }
 
-status_t EventHub::mapAxis(int32_t deviceId, int scancode,
-        int32_t* outAxis) const
+status_t EventHub::mapAxis(int32_t deviceId, int scancode, AxisInfo* outAxisInfo) const
 {
     AutoMutex _l(mLock);
     Device* device = getDeviceLocked(deviceId);
 
     if (device && device->keyMap.haveKeyLayout()) {
-        status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxis);
+        status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxisInfo);
         if (err == NO_ERROR) {
             return NO_ERROR;
         }
@@ -369,14 +368,13 @@
         device = getDeviceLocked(mBuiltInKeyboardId);
 
         if (device && device->keyMap.haveKeyLayout()) {
-            status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxis);
+            status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxisInfo);
             if (err == NO_ERROR) {
                 return NO_ERROR;
             }
         }
     }
 
-    *outAxis = -1;
     return NAME_NOT_FOUND;
 }
 
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index 445c04b..ab42a1f 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -176,7 +176,7 @@
             int32_t* outKeycode, uint32_t* outFlags) const = 0;
 
     virtual status_t mapAxis(int32_t deviceId, int scancode,
-            int32_t* outAxis) const = 0;
+            AxisInfo* outAxisInfo) const = 0;
 
     // exclude a particular device from opening
     // this can be used to ignore input devices for sensors
@@ -235,7 +235,7 @@
             int32_t* outKeycode, uint32_t* outFlags) const;
 
     virtual status_t mapAxis(int32_t deviceId, int scancode,
-            int32_t* outAxis) const;
+            AxisInfo* outAxisInfo) const;
 
     virtual void addExcludedDevice(const char* deviceName);
 
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index b92c3b5..00c3eb7 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -3854,7 +3854,10 @@
 
     for (size_t i = 0; i < mAxes.size(); i++) {
         const Axis& axis = mAxes.valueAt(i);
-        info->addMotionRange(axis.axis, axis.min, axis.max, axis.flat, axis.fuzz);
+        info->addMotionRange(axis.axisInfo.axis, axis.min, axis.max, axis.flat, axis.fuzz);
+        if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+            info->addMotionRange(axis.axisInfo.highAxis, axis.min, axis.max, axis.flat, axis.fuzz);
+        }
     }
 }
 
@@ -3865,18 +3868,29 @@
     size_t numAxes = mAxes.size();
     for (size_t i = 0; i < numAxes; i++) {
         const Axis& axis = mAxes.valueAt(i);
-        const char* label = getAxisLabel(axis.axis);
-        char name[32];
+        const char* label = getAxisLabel(axis.axisInfo.axis);
         if (label) {
-            strncpy(name, label, sizeof(name));
-            name[sizeof(name) - 1] = '\0';
+            dump.appendFormat(INDENT4 "%s", label);
         } else {
-            snprintf(name, sizeof(name), "%d", axis.axis);
+            dump.appendFormat(INDENT4 "%d", axis.axisInfo.axis);
         }
-        dump.appendFormat(INDENT4 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f, "
-                "scale=%0.3f, offset=%0.3f\n",
-                name, axis.min, axis.max, axis.flat, axis.fuzz,
-                axis.scale, axis.offset);
+        if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+            label = getAxisLabel(axis.axisInfo.highAxis);
+            if (label) {
+                dump.appendFormat(" / %s (split at %d)", label, axis.axisInfo.splitValue);
+            } else {
+                dump.appendFormat(" / %d (split at %d)", axis.axisInfo.highAxis,
+                        axis.axisInfo.splitValue);
+            }
+        } else if (axis.axisInfo.mode == AxisInfo::MODE_INVERT) {
+            dump.append(" (invert)");
+        }
+
+        dump.appendFormat(": min=%0.5f, max=%0.5f, flat=%0.5f, fuzz=%0.5f\n",
+                axis.min, axis.max, axis.flat, axis.fuzz);
+        dump.appendFormat(INDENT4 "  scale=%0.5f, offset=%0.5f, "
+                "highScale=%0.5f, highOffset=%0.5f\n",
+                axis.scale, axis.offset, axis.highScale, axis.highOffset);
         dump.appendFormat(INDENT4 "  rawAxis=%d, rawMin=%d, rawMax=%d, rawFlat=%d, rawFuzz=%d\n",
                 mAxes.keyAt(i), axis.rawAxisInfo.minValue, axis.rawAxisInfo.maxValue,
                 axis.rawAxisInfo.flat, axis.rawAxisInfo.fuzz);
@@ -3891,25 +3905,38 @@
         RawAbsoluteAxisInfo rawAxisInfo;
         getEventHub()->getAbsoluteAxisInfo(getDeviceId(), abs, &rawAxisInfo);
         if (rawAxisInfo.valid) {
-            int32_t axisId;
-            bool explicitlyMapped = !getEventHub()->mapAxis(getDeviceId(), abs, &axisId);
+            // Map axis.
+            AxisInfo axisInfo;
+            bool explicitlyMapped = !getEventHub()->mapAxis(getDeviceId(), abs, &axisInfo);
             if (!explicitlyMapped) {
                 // Axis is not explicitly mapped, will choose a generic axis later.
-                axisId = -1;
+                axisInfo.mode = AxisInfo::MODE_NORMAL;
+                axisInfo.axis = -1;
             }
 
+            // Apply flat override.
+            int32_t rawFlat = axisInfo.flatOverride < 0
+                    ? rawAxisInfo.flat : axisInfo.flatOverride;
+
+            // Calculate scaling factors and limits.
             Axis axis;
-            if (isCenteredAxis(axisId)) {
+            if (axisInfo.mode == AxisInfo::MODE_SPLIT) {
+                float scale = 1.0f / (axisInfo.splitValue - rawAxisInfo.minValue);
+                float highScale = 1.0f / (rawAxisInfo.maxValue - axisInfo.splitValue);
+                axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+                        scale, 0.0f, highScale, 0.0f,
+                        0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale);
+            } else if (isCenteredAxis(axisInfo.axis)) {
                 float scale = 2.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
                 float offset = avg(rawAxisInfo.minValue, rawAxisInfo.maxValue) * -scale;
-                axis.initialize(rawAxisInfo, axisId, explicitlyMapped,
-                        scale, offset, -1.0f, 1.0f,
-                        rawAxisInfo.flat * scale, rawAxisInfo.fuzz * scale);
+                axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+                        scale, offset, scale, offset,
+                        -1.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale);
             } else {
                 float scale = 1.0f / (rawAxisInfo.maxValue - rawAxisInfo.minValue);
-                axis.initialize(rawAxisInfo, axisId, explicitlyMapped,
-                        scale, 0.0f, 0.0f, 1.0f,
-                        rawAxisInfo.flat * scale, rawAxisInfo.fuzz * scale);
+                axis.initialize(rawAxisInfo, axisInfo, explicitlyMapped,
+                        scale, 0.0f, scale, 0.0f,
+                        0.0f, 1.0f, rawFlat * scale, rawAxisInfo.fuzz * scale);
             }
 
             // To eliminate noise while the joystick is at rest, filter out small variations
@@ -3934,14 +3961,14 @@
     size_t numAxes = mAxes.size();
     for (size_t i = 0; i < numAxes; i++) {
         Axis& axis = mAxes.editValueAt(i);
-        if (axis.axis < 0) {
+        if (axis.axisInfo.axis < 0) {
             while (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16
                     && haveAxis(nextGenericAxisId)) {
                 nextGenericAxisId += 1;
             }
 
             if (nextGenericAxisId <= AMOTION_EVENT_AXIS_GENERIC_16) {
-                axis.axis = nextGenericAxisId;
+                axis.axisInfo.axis = nextGenericAxisId;
                 nextGenericAxisId += 1;
             } else {
                 LOGI("Ignoring joystick '%s' axis %d because all of the generic axis ids "
@@ -3954,10 +3981,13 @@
     }
 }
 
-bool JoystickInputMapper::haveAxis(int32_t axis) {
+bool JoystickInputMapper::haveAxis(int32_t axisId) {
     size_t numAxes = mAxes.size();
     for (size_t i = 0; i < numAxes; i++) {
-        if (mAxes.valueAt(i).axis == axis) {
+        const Axis& axis = mAxes.valueAt(i);
+        if (axis.axisInfo.axis == axisId
+                || (axis.axisInfo.mode == AxisInfo::MODE_SPLIT
+                        && axis.axisInfo.highAxis == axisId)) {
             return true;
         }
     }
@@ -3987,6 +4017,8 @@
     case AMOTION_EVENT_AXIS_HAT_X:
     case AMOTION_EVENT_AXIS_HAT_Y:
     case AMOTION_EVENT_AXIS_ORIENTATION:
+    case AMOTION_EVENT_AXIS_RUDDER:
+    case AMOTION_EVENT_AXIS_WHEEL:
         return true;
     default:
         return false;
@@ -4000,7 +4032,7 @@
     size_t numAxes = mAxes.size();
     for (size_t i = 0; i < numAxes; i++) {
         Axis& axis = mAxes.editValueAt(i);
-        axis.newValue = 0;
+        axis.resetValue();
     }
 
     sync(when, true /*force*/);
@@ -4014,10 +4046,34 @@
         ssize_t index = mAxes.indexOfKey(rawEvent->scanCode);
         if (index >= 0) {
             Axis& axis = mAxes.editValueAt(index);
-            float newValue = rawEvent->value * axis.scale + axis.offset;
-            if (newValue != axis.newValue) {
-                axis.newValue = newValue;
+            float newValue, highNewValue;
+            switch (axis.axisInfo.mode) {
+            case AxisInfo::MODE_INVERT:
+                newValue = (axis.rawAxisInfo.maxValue - rawEvent->value)
+                        * axis.scale + axis.offset;
+                highNewValue = 0.0f;
+                break;
+            case AxisInfo::MODE_SPLIT:
+                if (rawEvent->value < axis.axisInfo.splitValue) {
+                    newValue = (axis.axisInfo.splitValue - rawEvent->value)
+                            * axis.scale + axis.offset;
+                    highNewValue = 0.0f;
+                } else if (rawEvent->value > axis.axisInfo.splitValue) {
+                    newValue = 0.0f;
+                    highNewValue = (rawEvent->value - axis.axisInfo.splitValue)
+                            * axis.highScale + axis.highOffset;
+                } else {
+                    newValue = 0.0f;
+                    highNewValue = 0.0f;
+                }
+                break;
+            default:
+                newValue = rawEvent->value * axis.scale + axis.offset;
+                highNewValue = 0.0f;
+                break;
             }
+            axis.newValue = newValue;
+            axis.highNewValue = highNewValue;
         }
         break;
     }
@@ -4033,7 +4089,7 @@
 }
 
 void JoystickInputMapper::sync(nsecs_t when, bool force) {
-    if (!force && !haveAxesChangedSignificantly()) {
+    if (!filterAxes(force)) {
         return;
     }
 
@@ -4044,9 +4100,11 @@
 
     size_t numAxes = mAxes.size();
     for (size_t i = 0; i < numAxes; i++) {
-        Axis& axis = mAxes.editValueAt(i);
-        pointerCoords.setAxisValue(axis.axis, axis.newValue);
-        axis.oldValue = axis.newValue;
+        const Axis& axis = mAxes.valueAt(i);
+        pointerCoords.setAxisValue(axis.axisInfo.axis, axis.currentValue);
+        if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+            pointerCoords.setAxisValue(axis.axisInfo.highAxis, axis.highCurrentValue);
+        }
     }
 
     // Moving a joystick axis should not wake the devide because joysticks can
@@ -4061,12 +4119,49 @@
             1, &pointerId, &pointerCoords, 0, 0, 0);
 }
 
-bool JoystickInputMapper::haveAxesChangedSignificantly() {
+bool JoystickInputMapper::filterAxes(bool force) {
+    bool atLeastOneSignificantChange = force;
     size_t numAxes = mAxes.size();
     for (size_t i = 0; i < numAxes; i++) {
-        const Axis& axis = mAxes.valueAt(i);
-        if (axis.newValue != axis.oldValue
-                && fabs(axis.newValue - axis.oldValue) > axis.filter) {
+        Axis& axis = mAxes.editValueAt(i);
+        if (force || hasValueChangedSignificantly(axis.filter,
+                axis.newValue, axis.currentValue, axis.min, axis.max)) {
+            axis.currentValue = axis.newValue;
+            atLeastOneSignificantChange = true;
+        }
+        if (axis.axisInfo.mode == AxisInfo::MODE_SPLIT) {
+            if (force || hasValueChangedSignificantly(axis.filter,
+                    axis.highNewValue, axis.highCurrentValue, axis.min, axis.max)) {
+                axis.highCurrentValue = axis.highNewValue;
+                atLeastOneSignificantChange = true;
+            }
+        }
+    }
+    return atLeastOneSignificantChange;
+}
+
+bool JoystickInputMapper::hasValueChangedSignificantly(
+        float filter, float newValue, float currentValue, float min, float max) {
+    if (newValue != currentValue) {
+        // Filter out small changes in value unless the value is converging on the axis
+        // bounds or center point.  This is intended to reduce the amount of information
+        // sent to applications by particularly noisy joysticks (such as PS3).
+        if (fabs(newValue - currentValue) > filter
+                || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, min)
+                || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, max)
+                || hasMovedNearerToValueWithinFilteredRange(filter, newValue, currentValue, 0)) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool JoystickInputMapper::hasMovedNearerToValueWithinFilteredRange(
+        float filter, float newValue, float currentValue, float thresholdValue) {
+    float newDistance = fabs(newValue - thresholdValue);
+    if (newDistance < filter) {
+        float oldDistance = fabs(currentValue - thresholdValue);
+        if (newDistance < oldDistance) {
             return true;
         }
     }
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 655f0f0..e2cf08e7 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -1011,38 +1011,50 @@
 private:
     struct Axis {
         RawAbsoluteAxisInfo rawAxisInfo;
+        AxisInfo axisInfo;
 
-        int32_t axis;  // axis id
         bool explicitlyMapped; // true if the axis was explicitly assigned an axis id
 
         float scale;   // scale factor from raw to normalized values
         float offset;  // offset to add after scaling for normalization
+        float highScale;  // scale factor from raw to normalized values of high split
+        float highOffset; // offset to add after scaling for normalization of high split
 
         float min;     // normalized inclusive minimum
         float max;     // normalized inclusive maximum
         float flat;    // normalized flat region size
         float fuzz;    // normalized error tolerance
 
-        float oldValue; // previous value
-        float newValue; // most recent value
-
         float filter;  // filter out small variations of this size
+        float currentValue; // current value
+        float newValue; // most recent value
+        float highCurrentValue; // current value of high split
+        float highNewValue; // most recent value of high split
 
-        void initialize(const RawAbsoluteAxisInfo& rawAxisInfo,
-                int32_t axis, bool explicitlyMapped, float scale, float offset,
+        void initialize(const RawAbsoluteAxisInfo& rawAxisInfo, const AxisInfo& axisInfo,
+                bool explicitlyMapped, float scale, float offset,
+                float highScale, float highOffset,
                 float min, float max, float flat, float fuzz) {
             this->rawAxisInfo = rawAxisInfo;
-            this->axis = axis;
+            this->axisInfo = axisInfo;
             this->explicitlyMapped = explicitlyMapped;
             this->scale = scale;
             this->offset = offset;
+            this->highScale = highScale;
+            this->highOffset = highOffset;
             this->min = min;
             this->max = max;
             this->flat = flat;
             this->fuzz = fuzz;
             this->filter = 0;
-            this->oldValue = 0;
+            resetValue();
+        }
+
+        void resetValue() {
+            this->currentValue = 0;
             this->newValue = 0;
+            this->highCurrentValue = 0;
+            this->highNewValue = 0;
         }
     };
 
@@ -1051,9 +1063,14 @@
 
     void sync(nsecs_t when, bool force);
 
-    bool haveAxis(int32_t axis);
+    bool haveAxis(int32_t axisId);
     void pruneAxes(bool ignoreExplicitlyMappedAxes);
-    bool haveAxesChangedSignificantly();
+    bool filterAxes(bool force);
+
+    static bool hasValueChangedSignificantly(float filter,
+            float newValue, float currentValue, float min, float max);
+    static bool hasMovedNearerToValueWithinFilteredRange(float filter,
+            float newValue, float currentValue, float thresholdValue);
 
     static bool isCenteredAxis(int32_t axis);
 };
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 8404b3a..d77cb2f 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -592,7 +592,7 @@
     }
 
     virtual status_t mapAxis(int32_t deviceId, int scancode,
-            int32_t* outAxis) const {
+            AxisInfo* outAxisInfo) const {
         return NAME_NOT_FOUND;
     }