Input device calibration and capabilities.

Finished the input device capability API.
Added a mechanism for calibrating touch devices to obtain more
accurate information about the touch contact area.
Improved pointer location to show new coordinates and capabilities.
Optimized pointer location display and formatting to avoid allocating large
numbers of temporary objects.  The GC churn was causing the application to
stutter very badly when more than a couple of fingers were down).
Added more diagnostics.

Change-Id: Ie25380278ed6f16c5b04cd9df848015850383498
diff --git a/api/current.xml b/api/current.xml
index 0331d3e..dad5c03 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -177160,14 +177160,19 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<constructor name="InputDevice"
- type="android.view.InputDevice"
+<implements name="android.os.Parcelable">
+</implements>
+<method name="describeContents"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
  static="false"
  final="false"
  deprecated="not deprecated"
  visibility="public"
 >
-</constructor>
+</method>
 <method name="getDevice"
  return="android.view.InputDevice"
  abstract="false"
@@ -177181,6 +177186,28 @@
 <parameter name="id" type="int">
 </parameter>
 </method>
+<method name="getDeviceIds"
+ return="int[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getKeyCharacterMap"
  return="android.view.KeyCharacterMap"
  abstract="false"
@@ -177213,7 +177240,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="range" type="int">
+<parameter name="rangeType" type="int">
 </parameter>
 </method>
 <method name="getName"
@@ -177238,8 +177265,8 @@
  visibility="public"
 >
 </method>
-<method name="hasKey"
- return="boolean"
+<method name="writeToParcel"
+ return="void"
  abstract="false"
  native="false"
  synchronized="false"
@@ -177248,9 +177275,21 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="keyCode" type="int">
+<parameter name="out" type="android.os.Parcel">
+</parameter>
+<parameter name="flags" type="int">
 </parameter>
 </method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="KEYBOARD_TYPE_ALPHABETIC"
  type="int"
  transient="false"
@@ -177579,14 +177618,6 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<constructor name="InputDevice.MotionRange"
- type="android.view.InputDevice.MotionRange"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</constructor>
 <method name="getFlat"
  return="float"
  abstract="false"
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e86e3bf..d4dd05c 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -29,6 +29,7 @@
 import android.view.InputEvent;
 import android.view.MotionEvent;
 import android.view.InputChannel;
+import android.view.InputDevice;
 
 /**
  * System private interface to the window manager.
@@ -125,6 +126,10 @@
     // Report whether the hardware supports the given keys; returns true if successful
     boolean hasKeys(in int[] keycodes, inout boolean[] keyExists);
     
+    // Get input device information.
+    InputDevice getInputDevice(int deviceId);
+    int[] getInputDeviceIds();
+    
     // For testing
     void setInTouchMode(boolean showFocus);
     
diff --git a/core/java/android/view/InputDevice.aidl b/core/java/android/view/InputDevice.aidl
new file mode 100644
index 0000000..dbc40c1
--- /dev/null
+++ b/core/java/android/view/InputDevice.aidl
@@ -0,0 +1,20 @@
+/* //device/java/android/android.view.InputDevice.aidl
+**
+** Copyright 2007, 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;
+
+parcelable InputDevice;
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index d5b2121..fb47b9c 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,6 +16,12 @@
 
 package android.view;
 
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
 /**
  * Describes the capabilities of a particular input device.
  * <p>
@@ -32,12 +38,14 @@
  * the appropriate interpretation.
  * </p>
  */
-public final class InputDevice {
+public final class InputDevice implements Parcelable {
     private int mId;
     private String mName;
     private int mSources;
     private int mKeyboardType;
     
+    private MotionRange[] mMotionRanges;
+    
     /**
      * A mask for input source classes.
      * 
@@ -246,6 +254,8 @@
      */
     public static final int MOTION_RANGE_ORIENTATION = 8;
     
+    private static final int MOTION_RANGE_LAST = MOTION_RANGE_ORIENTATION;
+    
     /**
      * There is no keyboard.
      */
@@ -261,6 +271,11 @@
      * The keyboard supports a complement of alphabetic keys.
      */
     public static final int KEYBOARD_TYPE_ALPHABETIC = 2;
+    
+    // Called by native code.
+    private InputDevice() {
+        mMotionRanges = new MotionRange[MOTION_RANGE_LAST + 1];
+    }
 
     /**
      * Gets information about the input device with the specified id.
@@ -268,8 +283,35 @@
      * @return The input device or null if not found.
      */
     public static InputDevice getDevice(int id) {
-        // TODO
-        return null;
+        IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+        try {
+            return wm.getInputDevice(id);
+        } catch (RemoteException ex) {
+            throw new RuntimeException(
+                    "Could not get input device information from Window Manager.", ex);
+        }
+    }
+    
+    /**
+     * Gets the ids of all input devices in the system.
+     * @return The input device ids.
+     */
+    public static int[] getDeviceIds() {
+        IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+        try {
+            return wm.getInputDeviceIds();
+        } catch (RemoteException ex) {
+            throw new RuntimeException(
+                    "Could not get input device ids from Window Manager.", ex);
+        }
+    }
+    
+    /**
+     * Gets the input device id.
+     * @return The input device id.
+     */
+    public int getId() {
+        return mId;
     }
     
     /**
@@ -307,23 +349,23 @@
     /**
      * Gets information about the range of values for a particular {@link MotionEvent}
      * coordinate.
-     * @param range The motion range constant.
+     * @param rangeType The motion range constant.
      * @return The range of values, or null if the requested coordinate is not
      * supported by the device.
      */
-    public MotionRange getMotionRange(int range) {
-        // TODO
-        return null;
+    public MotionRange getMotionRange(int rangeType) {
+        if (rangeType < 0 || rangeType > MOTION_RANGE_LAST) {
+            throw new IllegalArgumentException("Requested range is out of bounds.");
+        }
+        
+        return mMotionRanges[rangeType];
     }
     
-    /**
-     * Returns true if the device supports a particular button or key.
-     * @param keyCode The key code.
-     * @return True if the device supports the key.
-     */
-    public boolean hasKey(int keyCode) {
-        // TODO
-        return false;
+    private void addMotionRange(int rangeType, float min, float max, float flat, float fuzz) {
+        if (rangeType >= 0 && rangeType <= MOTION_RANGE_LAST) {
+            MotionRange range = new MotionRange(min, max, flat, fuzz);
+            mMotionRanges[rangeType] = range;
+        }
     }
     
     /**
@@ -331,13 +373,24 @@
      * coordinate.
      */
     public static final class MotionRange {
+        private float mMin;
+        private float mMax;
+        private float mFlat;
+        private float mFuzz;
+        
+        private MotionRange(float min, float max, float flat, float fuzz) {
+            mMin = min;
+            mMax = max;
+            mFlat = flat;
+            mFuzz = fuzz;
+        }
+        
         /**
          * Gets the minimum value for the coordinate.
          * @return The minimum value.
          */
         public float getMin() {
-            // TODO
-            return 0;
+            return mMin;
         }
         
         /**
@@ -345,8 +398,7 @@
          * @return The minimum value.
          */
         public float getMax() {
-            // TODO
-            return 0;
+            return mMax;
         }
         
         /**
@@ -354,8 +406,7 @@
          * @return The range of values.
          */
         public float getRange() {
-            // TODO
-            return 0;
+            return mMax - mMin;
         }
         
         /**
@@ -365,8 +416,7 @@
          * @return The extent of the center flat position.
          */
         public float getFlat() {
-            // TODO
-            return 0;
+            return mFlat;
         }
         
         /**
@@ -376,8 +426,127 @@
          * @return The error tolerance.
          */
         public float getFuzz() {
-            // TODO
-            return 0;
+            return mFuzz;
+        }
+    }
+    
+    public static final Parcelable.Creator<InputDevice> CREATOR
+            = new Parcelable.Creator<InputDevice>() {
+        public InputDevice createFromParcel(Parcel in) {
+            InputDevice result = new InputDevice();
+            result.readFromParcel(in);
+            return result;
+        }
+        
+        public InputDevice[] newArray(int size) {
+            return new InputDevice[size];
+        }
+    };
+    
+    private void readFromParcel(Parcel in) {
+        mId = in.readInt();
+        mName = in.readString();
+        mSources = in.readInt();
+        mKeyboardType = in.readInt();
+        
+        for (;;) {
+            int rangeType = in.readInt();
+            if (rangeType < 0) {
+                break;
+            }
+            
+            addMotionRange(rangeType,
+                    in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat());
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mId);
+        out.writeString(mName);
+        out.writeInt(mSources);
+        out.writeInt(mKeyboardType);
+        
+        for (int i = 0; i <= MOTION_RANGE_LAST; i++) {
+            MotionRange range = mMotionRanges[i];
+            if (range != null) {
+                out.writeInt(i);
+                out.writeFloat(range.mMin);
+                out.writeFloat(range.mMax);
+                out.writeFloat(range.mFlat);
+                out.writeFloat(range.mFuzz);
+            }
+        }
+        out.writeInt(-1);
+    }
+    
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+    
+    @Override
+    public String toString() {
+        StringBuilder description = new StringBuilder();
+        description.append("Input Device ").append(mId).append(": ").append(mName).append("\n");
+        
+        description.append("  Keyboard Type: ");
+        switch (mKeyboardType) {
+            case KEYBOARD_TYPE_NONE:
+                description.append("none");
+                break;
+            case KEYBOARD_TYPE_NON_ALPHABETIC:
+                description.append("non-alphabetic");
+                break;
+            case KEYBOARD_TYPE_ALPHABETIC:
+                description.append("alphabetic");
+                break;
+        }
+        description.append("\n");
+        
+        description.append("  Sources:");
+        appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
+        appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
+        appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad");
+        appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHSCREEN, "touchscreen");
+        appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse");
+        appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball");
+        appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
+        appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK_LEFT, "joystick_left");
+        appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK_RIGHT, "joystick_right");
+        description.append("\n");
+        
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_X, "x");
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_Y, "y");
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_PRESSURE, "pressure");
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_SIZE, "size");
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOUCH_MAJOR, "touchMajor");
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOUCH_MINOR, "touchMinor");
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOOL_MAJOR, "toolMajor");
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOOL_MINOR, "toolMinor");
+        appendRangeDescriptionIfApplicable(description, MOTION_RANGE_ORIENTATION, "orientation");
+        
+        return description.toString();
+    }
+    
+    private void appendSourceDescriptionIfApplicable(StringBuilder description, int source,
+            String sourceName) {
+        if ((mSources & source) == source) {
+            description.append(" ");
+            description.append(sourceName);
+        }
+    }
+    
+    private void appendRangeDescriptionIfApplicable(StringBuilder description,
+            int rangeType, String rangeName) {
+        MotionRange range = mMotionRanges[rangeType];
+        if (range != null) {
+            description.append("  Range[").append(rangeName);
+            description.append("]: min=").append(range.mMin);
+            description.append(" max=").append(range.mMax);
+            description.append(" flat=").append(range.mFlat);
+            description.append(" fuzz=").append(range.mFuzz);
+            description.append("\n");
         }
     }
 }
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index d5a9979..939f118 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.RectF;
 import android.graphics.Paint.FontMetricsInt;
 import android.util.Log;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -29,17 +31,45 @@
 import java.util.ArrayList;
 
 public class PointerLocationView extends View {
+    private static final String TAG = "Pointer";
+    
     public static class PointerState {
-        private final ArrayList<Float> mXs = new ArrayList<Float>();
-        private final ArrayList<Float> mYs = new ArrayList<Float>();
+        // Trace of previous points.
+        private float[] mTraceX = new float[32];
+        private float[] mTraceY = new float[32];
+        private int mTraceCount;
+        
+        // True if the pointer is down.
         private boolean mCurDown;
-        private int mCurX;
-        private int mCurY;
-        private float mCurPressure;
-        private float mCurSize;
-        private int mCurWidth;
+        
+        // Most recent coordinates.
+        private MotionEvent.PointerCoords mCoords = new MotionEvent.PointerCoords();
+        
+        // Most recent velocity.
         private float mXVelocity;
         private float mYVelocity;
+        
+        public void clearTrace() {
+            mTraceCount = 0;
+        }
+        
+        public void addTrace(float x, float y) {
+            int traceCapacity = mTraceX.length;
+            if (mTraceCount == traceCapacity) {
+                traceCapacity *= 2;
+                float[] newTraceX = new float[traceCapacity];
+                System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount);
+                mTraceX = newTraceX;
+                
+                float[] newTraceY = new float[traceCapacity];
+                System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount);
+                mTraceY = newTraceY;
+            }
+            
+            mTraceX[mTraceCount] = x;
+            mTraceY[mTraceCount] = y;
+            mTraceCount += 1;
+        }
     }
 
     private final ViewConfiguration mVC;
@@ -54,11 +84,12 @@
     private boolean mCurDown;
     private int mCurNumPointers;
     private int mMaxNumPointers;
-    private final ArrayList<PointerState> mPointers
-             = new ArrayList<PointerState>();
+    private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
     
     private final VelocityTracker mVelocity;
     
+    private final FasterStringBuilder mText = new FasterStringBuilder();
+    
     private boolean mPrintCoords = true;
     
     public PointerLocationView(Context c) {
@@ -94,6 +125,18 @@
         mPointers.add(ps);
         
         mVelocity = VelocityTracker.obtain();
+        
+        logInputDeviceCapabilities();
+    }
+    
+    private void logInputDeviceCapabilities() {
+        int[] deviceIds = InputDevice.getDeviceIds();
+        for (int i = 0; i < deviceIds.length; i++) {
+            InputDevice device = InputDevice.getDevice(deviceIds[i]);
+            if (device != null) {
+                Log.i(TAG, device.toString());
+            }
+        }
     }
 
     public void setPrintCoords(boolean state) {
@@ -113,6 +156,21 @@
                     + " bottom=" + mTextMetrics.bottom);
         }
     }
+    
+    // Draw an oval.  When angle is 0 radians, orients the major axis vertically,
+    // angles less than or greater than 0 radians rotate the major axis left or right.
+    private RectF mReusableOvalRect = new RectF();
+    private void drawOval(Canvas canvas, float x, float y, float major, float minor,
+            float angle, Paint paint) {
+        canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.rotate((float) (angle * 180 / Math.PI), x, y);
+        mReusableOvalRect.left = x - minor / 2;
+        mReusableOvalRect.right = x + minor / 2;
+        mReusableOvalRect.top = y - major / 2;
+        mReusableOvalRect.bottom = y + major / 2;
+        canvas.drawOval(mReusableOvalRect, paint);
+        canvas.restore();
+    }
 
     @Override
     protected void onDraw(Canvas canvas) {
@@ -124,76 +182,80 @@
             
             final int NP = mPointers.size();
             
+            // Labels
             if (NP > 0) {
                 final PointerState ps = mPointers.get(0);
                 canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint);
-                canvas.drawText("P: " + mCurNumPointers + " / " + mMaxNumPointers,
-                        1, base, mTextPaint);
+                canvas.drawText(mText.clear()
+                        .append("P: ").append(mCurNumPointers)
+                        .append(" / ").append(mMaxNumPointers)
+                        .toString(), 1, base, mTextPaint);
                 
-                final int N = ps.mXs.size();
+                final int N = ps.mTraceCount;
                 if ((mCurDown && ps.mCurDown) || N == 0) {
                     canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint);
-                    canvas.drawText("X: " + ps.mCurX, 1 + itemW, base, mTextPaint);
+                    canvas.drawText(mText.clear()
+                            .append("X: ").append(ps.mCoords.x, 1)
+                            .toString(), 1 + itemW, base, mTextPaint);
                     canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint);
-                    canvas.drawText("Y: " + ps.mCurY, 1 + itemW * 2, base, mTextPaint);
+                    canvas.drawText(mText.clear()
+                            .append("Y: ").append(ps.mCoords.y, 1)
+                            .toString(), 1 + itemW * 2, base, mTextPaint);
                 } else {
-                    float dx = ps.mXs.get(N-1) - ps.mXs.get(0);
-                    float dy = ps.mYs.get(N-1) - ps.mYs.get(0);
+                    float dx = ps.mTraceX[N - 1] - ps.mTraceX[0];
+                    float dy = ps.mTraceY[N - 1] - ps.mTraceY[0];
                     canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom,
                             Math.abs(dx) < mVC.getScaledTouchSlop()
                             ? mTextBackgroundPaint : mTextLevelPaint);
-                    canvas.drawText("dX: " + String.format("%.1f", dx), 1 + itemW, base, mTextPaint);
+                    canvas.drawText(mText.clear()
+                            .append("dX: ").append(dx, 1)
+                            .toString(), 1 + itemW, base, mTextPaint);
                     canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom,
                             Math.abs(dy) < mVC.getScaledTouchSlop()
                             ? mTextBackgroundPaint : mTextLevelPaint);
-                    canvas.drawText("dY: " + String.format("%.1f", dy), 1 + itemW * 2, base, mTextPaint);
+                    canvas.drawText(mText.clear()
+                            .append("dY: ").append(dy, 1)
+                            .toString(), 1 + itemW * 2, base, mTextPaint);
                 }
                 
                 canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint);
-                int velocity = (int) (ps.mXVelocity * 1000);
-                canvas.drawText("Xv: " + velocity, 1 + itemW * 3, base, mTextPaint);
+                canvas.drawText(mText.clear()
+                        .append("Xv: ").append(ps.mXVelocity, 3)
+                        .toString(), 1 + itemW * 3, base, mTextPaint);
                 
                 canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint);
-                velocity = (int) (ps.mYVelocity * 1000);
-                canvas.drawText("Yv: " + velocity, 1 + itemW * 4, base, mTextPaint);
+                canvas.drawText(mText.clear()
+                        .append("Yv: ").append(ps.mYVelocity, 3)
+                        .toString(), 1 + itemW * 4, base, mTextPaint);
                 
                 canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint);
-                canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCurPressure * itemW) - 1,
+                canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1,
                         bottom, mTextLevelPaint);
-                canvas.drawText("Prs: " + String.format("%.2f", ps.mCurPressure), 1 + itemW * 5,
-                        base, mTextPaint);
+                canvas.drawText(mText.clear()
+                        .append("Prs: ").append(ps.mCoords.pressure, 2)
+                        .toString(), 1 + itemW * 5, base, mTextPaint);
                 
                 canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint);
-                canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCurSize * itemW) - 1,
+                canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1,
                         bottom, mTextLevelPaint);
-                canvas.drawText("Size: " + String.format("%.2f", ps.mCurSize), 1 + itemW * 6,
-                        base, mTextPaint);
+                canvas.drawText(mText.clear()
+                        .append("Size: ").append(ps.mCoords.size, 2)
+                        .toString(), 1 + itemW * 6, base, mTextPaint);
             }
             
-            for (int p=0; p<NP; p++) {
+            // Pointer trace.
+            for (int p = 0; p < NP; p++) {
                 final PointerState ps = mPointers.get(p);
                 
-                if (mCurDown && ps.mCurDown) {
-                    canvas.drawLine(0, (int)ps.mCurY, getWidth(), (int)ps.mCurY, mTargetPaint);
-                    canvas.drawLine((int)ps.mCurX, 0, (int)ps.mCurX, getHeight(), mTargetPaint);
-                    int pressureLevel = (int)(ps.mCurPressure*255);
-                    mPaint.setARGB(255, pressureLevel, 128, 255-pressureLevel);
-                    canvas.drawPoint(ps.mCurX, ps.mCurY, mPaint);
-                    canvas.drawCircle(ps.mCurX, ps.mCurY, ps.mCurWidth, mPaint);
-                }
-            }
-            
-            for (int p=0; p<NP; p++) {
-                final PointerState ps = mPointers.get(p);
-                
-                final int N = ps.mXs.size();
-                float lastX=0, lastY=0;
+                // Draw path.
+                final int N = ps.mTraceCount;
+                float lastX = 0, lastY = 0;
                 boolean haveLast = false;
                 boolean drawn = false;
                 mPaint.setARGB(255, 128, 255, 255);
-                for (int i=0; i<N; i++) {
-                    float x = ps.mXs.get(i);
-                    float y = ps.mYs.get(i);
+                for (int i=0; i < N; i++) {
+                    float x = ps.mTraceX[i];
+                    float y = ps.mTraceY[i];
                     if (Float.isNaN(x)) {
                         haveLast = false;
                         continue;
@@ -208,21 +270,57 @@
                     haveLast = true;
                 }
                 
+                // Draw velocity vector.
                 if (drawn) {
                     mPaint.setARGB(255, 255, 64, 128);
-                    float xVel = ps.mXVelocity * (1000/60);
-                    float yVel = ps.mYVelocity * (1000/60);
-                    canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint);
+                    float xVel = ps.mXVelocity * (1000 / 60);
+                    float yVel = ps.mYVelocity * (1000 / 60);
+                    canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint);
+                }
+                
+                if (mCurDown && ps.mCurDown) {
+                    // Draw crosshairs.
+                    canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint);
+                    canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint);
+                    
+                    // Draw current point.
+                    int pressureLevel = (int)(ps.mCoords.pressure * 255);
+                    mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel);
+                    canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint);
+                    
+                    // Draw current touch ellipse.
+                    mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128);
+                    drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor,
+                            ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint);
+                    
+                    // Draw current tool ellipse.
+                    mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel);
+                    drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor,
+                            ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint);
                 }
             }
         }
     }
+    
+    private void logPointerCoords(MotionEvent.PointerCoords coords, int id) {
+        Log.i(TAG, mText.clear()
+                .append("Pointer ").append(id + 1)
+                .append(": (").append(coords.x, 3).append(", ").append(coords.y, 3)
+                .append(") Pressure=").append(coords.pressure, 3)
+                .append(" Size=").append(coords.size, 3)
+                .append(" TouchMajor=").append(coords.touchMajor, 3)
+                .append(" TouchMinor=").append(coords.touchMinor, 3)
+                .append(" ToolMajor=").append(coords.toolMajor, 3)
+                .append(" ToolMinor=").append(coords.toolMinor, 3)
+                .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1)
+                .append("deg").toString());
+    }
 
     public void addTouchEvent(MotionEvent event) {
         synchronized (mPointers) {
             int action = event.getAction();
             
-            //Log.i("Pointer", "Motion: action=0x" + Integer.toHexString(action)
+            //Log.i(TAG, "Motion: action=0x" + Integer.toHexString(action)
             //        + " pointers=" + event.getPointerCount());
             
             int NP = mPointers.size();
@@ -235,35 +333,33 @@
             //} else {
             //    mRect.setEmpty();
             //}
-            if (action == MotionEvent.ACTION_DOWN) {
-                mVelocity.clear();
+            if (action == MotionEvent.ACTION_DOWN
+                    || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
+                final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down
+                if (action == MotionEvent.ACTION_DOWN) {
+                    for (int p=0; p<NP; p++) {
+                        final PointerState ps = mPointers.get(p);
+                        ps.clearTrace();
+                        ps.mCurDown = false;
+                    }
+                    mCurDown = true;
+                    mMaxNumPointers = 0;
+                    mVelocity.clear();
+                }
                 
-                for (int p=0; p<NP; p++) {
-                    final PointerState ps = mPointers.get(p);
-                    ps.mXs.clear();
-                    ps.mYs.clear();
-                    ps.mCurDown = false;
-                }
-                mPointers.get(0).mCurDown = true;
-                mMaxNumPointers = 0;
-                if (mPrintCoords) {
-                    Log.i("Pointer", "Pointer 1: DOWN");
-                }
-            }
-            
-            if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) {
-                final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK)
-                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                 final int id = event.getPointerId(index);
                 while (NP <= id) {
                     PointerState ps = new PointerState();
                     mPointers.add(ps);
                     NP++;
                 }
+                
                 final PointerState ps = mPointers.get(id);
                 ps.mCurDown = true;
                 if (mPrintCoords) {
-                    Log.i("Pointer", "Pointer " + (id+1) + ": DOWN");
+                    Log.i(TAG, mText.clear().append("Pointer ")
+                            .append(id + 1).append(": DOWN").toString());
                 }
             }
             
@@ -284,58 +380,38 @@
                 final PointerState ps = mPointers.get(id);
                 final int N = event.getHistorySize();
                 for (int j=0; j<N; j++) {
+                    event.getHistoricalPointerCoords(i, j, ps.mCoords);
                     if (mPrintCoords) {
-                        Log.i("Pointer", "Pointer " + (id+1) + ": ("
-                                + event.getHistoricalX(i, j)
-                                + ", " + event.getHistoricalY(i, j) + ")"
-                                + " Prs=" + event.getHistoricalPressure(i, j)
-                                + " Size=" + event.getHistoricalSize(i, j));
+                        logPointerCoords(ps.mCoords, id);
                     }
-                    ps.mXs.add(event.getHistoricalX(i, j));
-                    ps.mYs.add(event.getHistoricalY(i, j));
+                    ps.addTrace(event.getHistoricalX(i, j), event.getHistoricalY(i, j));
                 }
+                event.getPointerCoords(i, ps.mCoords);
                 if (mPrintCoords) {
-                    Log.i("Pointer", "Pointer " + (id+1) + ": ("
-                            + event.getX(i) + ", " + event.getY(i) + ")"
-                            + " Prs=" + event.getPressure(i)
-                            + " Size=" + event.getSize(i));
+                    logPointerCoords(ps.mCoords, id);
                 }
-                ps.mXs.add(event.getX(i));
-                ps.mYs.add(event.getY(i));
-                ps.mCurX = (int)event.getX(i);
-                ps.mCurY = (int)event.getY(i);
-                //Log.i("Pointer", "Pointer #" + p + ": (" + ps.mCurX
-                //        + "," + ps.mCurY + ")");
-                ps.mCurPressure = event.getPressure(i);
-                ps.mCurSize = event.getSize(i);
-                ps.mCurWidth = (int)(ps.mCurSize*(getWidth()/3));
+                ps.addTrace(ps.mCoords.x, ps.mCoords.y);
                 ps.mXVelocity = mVelocity.getXVelocity(id);
                 ps.mYVelocity = mVelocity.getYVelocity(id);
             }
             
-            if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
-                final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK)
-                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+            if (action == MotionEvent.ACTION_UP
+                    || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
+                final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP
+                
                 final int id = event.getPointerId(index);
                 final PointerState ps = mPointers.get(id);
-                ps.mXs.add(Float.NaN);
-                ps.mYs.add(Float.NaN);
                 ps.mCurDown = false;
                 if (mPrintCoords) {
-                    Log.i("Pointer", "Pointer " + (id+1) + ": UP");
+                    Log.i(TAG, mText.clear().append("Pointer ")
+                            .append(id + 1).append(": UP").toString());
                 }
-            }
-            
-            if (action == MotionEvent.ACTION_UP) {
-                for (int i=0; i<NI; i++) {
-                    final int id = event.getPointerId(i);
-                    final PointerState ps = mPointers.get(id);
-                    if (ps.mCurDown) {
-                        ps.mCurDown = false;
-                        if (mPrintCoords) {
-                            Log.i("Pointer", "Pointer " + (id+1) + ": UP");
-                        }
-                    }
+
+                if (action == MotionEvent.ACTION_UP) {
+                    mCurDown = false;
+                } else {
+                    ps.addTrace(Float.NaN, Float.NaN);
                 }
             }
             
@@ -356,8 +432,120 @@
 
     @Override
     public boolean onTrackballEvent(MotionEvent event) {
-        Log.i("Pointer", "Trackball: " + event);
+        Log.i(TAG, "Trackball: " + event);
         return super.onTrackballEvent(event);
     }
     
+    // HACK
+    // A quick and dirty string builder implementation optimized for GC.
+    // Using the basic StringBuilder implementation causes the application grind to a halt when
+    // more than a couple of pointers are down due to the number of temporary objects allocated
+    // while formatting strings for drawing or logging.
+    private static final class FasterStringBuilder {
+        private char[] mChars;
+        private int mLength;
+        
+        public FasterStringBuilder() {
+            mChars = new char[64];
+        }
+        
+        public FasterStringBuilder clear() {
+            mLength = 0;
+            return this;
+        }
+        
+        public FasterStringBuilder append(String value) {
+            final int valueLength = value.length();
+            final int index = reserve(valueLength);
+            value.getChars(0, valueLength, mChars, index);
+            mLength += valueLength;
+            return this;
+        }
+        
+        public FasterStringBuilder append(int value) {
+            return append(value, 0);
+        }
+        
+        public FasterStringBuilder append(int value, int zeroPadWidth) {
+            final boolean negative = value < 0;
+            if (negative) {
+                value = - value;
+                if (value < 0) {
+                    append("-2147483648");
+                    return this;
+                }
+            }
+            
+            int index = reserve(11);
+            final char[] chars = mChars;
+            
+            if (value == 0) {
+                chars[index++] = '0';
+                mLength += 1;
+                return this;
+            }
+            
+            if (negative) {
+                chars[index++] = '-';
+            }
+
+            int divisor = 1000000000;
+            int numberWidth = 10;
+            while (value < divisor) {
+                divisor /= 10;
+                numberWidth -= 1;
+                if (numberWidth < zeroPadWidth) {
+                    chars[index++] = '0';
+                }
+            }
+            
+            do {
+                int digit = value / divisor;
+                value -= digit * divisor;
+                divisor /= 10;
+                chars[index++] = (char) (digit + '0');
+            } while (divisor != 0);
+            
+            mLength = index;
+            return this;
+        }
+        
+        public FasterStringBuilder append(float value, int precision) {
+            int scale = 1;
+            for (int i = 0; i < precision; i++) {
+                scale *= 10;
+            }
+            value = (float) (Math.rint(value * scale) / scale);
+            
+            append((int) value);
+
+            if (precision != 0) {
+                append(".");
+                value = Math.abs(value);
+                value -= Math.floor(value);
+                append((int) (value * scale), precision);
+            }
+            
+            return this;
+        }
+        
+        @Override
+        public String toString() {
+            return new String(mChars, 0, mLength);
+        }
+        
+        private int reserve(int length) {
+            final int oldLength = mLength;
+            final int newLength = mLength + length;
+            final char[] oldChars = mChars;
+            final int oldCapacity = oldChars.length;
+            if (newLength > oldCapacity) {
+                final int newCapacity = oldCapacity * 2;
+                final char[] newChars = new char[newCapacity];
+                System.arraycopy(oldChars, 0, newChars, 0, oldLength);
+                mChars = newChars;
+            }
+            return oldLength;
+        }
+    }
 }
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index 3d42856..25d5afb 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -82,6 +82,14 @@
     int32_t fuzz;      // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
 
     inline int32_t getRange() { return maxValue - minValue; }
+
+    inline void clear() {
+        valid = false;
+        minValue = 0;
+        maxValue = 0;
+        flat = 0;
+        fuzz = 0;
+    }
 };
 
 /*
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 2385973..49347d3 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -453,6 +453,10 @@
     inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
     inline int32_t getKeyboardType() const { return mKeyboardType; }
 
+    inline const KeyedVector<int32_t, MotionRange> getMotionRanges() const {
+        return mMotionRanges;
+    }
+
 private:
     int32_t mId;
     String8 mName;
diff --git a/include/ui/InputReader.h b/include/ui/InputReader.h
index 56d2765..7a089a45 100644
--- a/include/ui/InputReader.h
+++ b/include/ui/InputReader.h
@@ -35,6 +35,34 @@
 class InputDevice;
 class InputMapper;
 
+/* Describes a virtual key. */
+struct VirtualKeyDefinition {
+    int32_t scanCode;
+
+    // configured position data, specified in display coords
+    int32_t centerX;
+    int32_t centerY;
+    int32_t width;
+    int32_t height;
+};
+
+
+/* Specifies input device calibration settings. */
+class InputDeviceCalibration {
+public:
+    InputDeviceCalibration();
+
+    void clear();
+    void addProperty(const String8& key, const String8& value);
+
+    bool tryGetProperty(const String8& key, String8& outValue) const;
+    bool tryGetProperty(const String8& key, int32_t& outValue) const;
+    bool tryGetProperty(const String8& key, float& outValue) const;
+
+private:
+    KeyedVector<String8, String8> mProperties;
+};
+
 
 /*
  * Input reader policy interface.
@@ -73,17 +101,6 @@
         ACTION_APP_SWITCH_COMING = 0x00000002,
     };
 
-    /* Describes a virtual key. */
-    struct VirtualKeyDefinition {
-        int32_t scanCode;
-
-        // configured position data, specified in display coords
-        int32_t centerX;
-        int32_t centerY;
-        int32_t width;
-        int32_t height;
-    };
-
     /* Gets information about the display with the specified id.
      * Returns true if the display info is available, false otherwise.
      */
@@ -135,6 +152,10 @@
     virtual void getVirtualKeyDefinitions(const String8& deviceName,
             Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) = 0;
 
+    /* Gets the calibration for an input device. */
+    virtual void getInputDeviceCalibration(const String8& deviceName,
+            InputDeviceCalibration& outCalibration) = 0;
+
     /* Gets the excluded device names for the platform. */
     virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) = 0;
 };
@@ -327,6 +348,10 @@
 
     int32_t getMetaState();
 
+    inline const InputDeviceCalibration& getCalibration() {
+        return mCalibration;
+    }
+
 private:
     InputReaderContext* mContext;
     int32_t mId;
@@ -338,6 +363,8 @@
 
     typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
     int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
+
+    InputDeviceCalibration mCalibration;
 };
 
 
@@ -538,12 +565,12 @@
         }
     };
 
+    // Raw data for a single pointer.
     struct PointerData {
         uint32_t id;
         int32_t x;
         int32_t y;
         int32_t pressure;
-        int32_t size;
         int32_t touchMajor;
         int32_t touchMinor;
         int32_t toolMajor;
@@ -551,6 +578,7 @@
         int32_t orientation;
     };
 
+    // Raw data for a collection of pointers including a pointer id mapping table.
     struct TouchData {
         uint32_t pointerCount;
         PointerData pointers[MAX_POINTERS];
@@ -584,18 +612,82 @@
         bool useAveragingTouchFilter;
     } mParameters;
 
-    // Raw axis information.
-    struct Axes {
+    // Immutable calibration parameters in parsed form.
+    struct Calibration {
+        // Touch Area
+        enum TouchAreaCalibration {
+            TOUCH_AREA_CALIBRATION_DEFAULT,
+            TOUCH_AREA_CALIBRATION_NONE,
+            TOUCH_AREA_CALIBRATION_GEOMETRIC,
+            TOUCH_AREA_CALIBRATION_PRESSURE,
+        };
+
+        TouchAreaCalibration touchAreaCalibration;
+
+        // Tool Area
+        enum ToolAreaCalibration {
+            TOOL_AREA_CALIBRATION_DEFAULT,
+            TOOL_AREA_CALIBRATION_NONE,
+            TOOL_AREA_CALIBRATION_GEOMETRIC,
+            TOOL_AREA_CALIBRATION_LINEAR,
+        };
+
+        ToolAreaCalibration toolAreaCalibration;
+        bool haveToolAreaLinearScale;
+        float toolAreaLinearScale;
+        bool haveToolAreaLinearBias;
+        float toolAreaLinearBias;
+        bool haveToolAreaIsSummed;
+        int32_t toolAreaIsSummed;
+
+        // Pressure
+        enum PressureCalibration {
+            PRESSURE_CALIBRATION_DEFAULT,
+            PRESSURE_CALIBRATION_NONE,
+            PRESSURE_CALIBRATION_PHYSICAL,
+            PRESSURE_CALIBRATION_AMPLITUDE,
+        };
+        enum PressureSource {
+            PRESSURE_SOURCE_DEFAULT,
+            PRESSURE_SOURCE_PRESSURE,
+            PRESSURE_SOURCE_TOUCH,
+        };
+
+        PressureCalibration pressureCalibration;
+        PressureSource pressureSource;
+        bool havePressureScale;
+        float pressureScale;
+
+        // Size
+        enum SizeCalibration {
+            SIZE_CALIBRATION_DEFAULT,
+            SIZE_CALIBRATION_NONE,
+            SIZE_CALIBRATION_NORMALIZED,
+        };
+
+        SizeCalibration sizeCalibration;
+
+        // Orientation
+        enum OrientationCalibration {
+            ORIENTATION_CALIBRATION_DEFAULT,
+            ORIENTATION_CALIBRATION_NONE,
+            ORIENTATION_CALIBRATION_INTERPOLATED,
+        };
+
+        OrientationCalibration orientationCalibration;
+    } mCalibration;
+
+    // Raw axis information from the driver.
+    struct RawAxes {
         RawAbsoluteAxisInfo x;
         RawAbsoluteAxisInfo y;
         RawAbsoluteAxisInfo pressure;
-        RawAbsoluteAxisInfo size;
         RawAbsoluteAxisInfo touchMajor;
         RawAbsoluteAxisInfo touchMinor;
         RawAbsoluteAxisInfo toolMajor;
         RawAbsoluteAxisInfo toolMinor;
         RawAbsoluteAxisInfo orientation;
-    } mAxes;
+    } mRawAxes;
 
     // Current and previous touch sample data.
     TouchData mCurrentTouch;
@@ -620,10 +712,13 @@
         float yScale;
         float yPrecision;
 
-        int32_t pressureOrigin;
+        float geometricScale;
+
+        float toolAreaLinearScale;
+        float toolAreaLinearBias;
+
         float pressureScale;
 
-        int32_t sizeOrigin;
         float sizeScale;
 
         float orientationScale;
@@ -632,12 +727,22 @@
         struct OrientedRanges {
             InputDeviceInfo::MotionRange x;
             InputDeviceInfo::MotionRange y;
+
+            bool havePressure;
             InputDeviceInfo::MotionRange pressure;
+
+            bool haveSize;
             InputDeviceInfo::MotionRange size;
+
+            bool haveTouchArea;
             InputDeviceInfo::MotionRange touchMajor;
             InputDeviceInfo::MotionRange touchMinor;
+
+            bool haveToolArea;
             InputDeviceInfo::MotionRange toolMajor;
             InputDeviceInfo::MotionRange toolMinor;
+
+            bool haveOrientation;
             InputDeviceInfo::MotionRange orientation;
         } orientedRanges;
 
@@ -653,9 +758,14 @@
         } currentVirtualKey;
     } mLocked;
 
-    virtual void configureAxes();
+    virtual void configureParameters();
+    virtual void configureRawAxes();
+    virtual void logRawAxes();
     virtual bool configureSurfaceLocked();
     virtual void configureVirtualKeysLocked();
+    virtual void parseCalibration();
+    virtual void resolveCalibration();
+    virtual void logCalibration();
 
     enum TouchResult {
         // Dispatch the touch normally.
@@ -713,7 +823,8 @@
     TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags);
     void dispatchTouches(nsecs_t when, uint32_t policyFlags);
     void dispatchTouch(nsecs_t when, uint32_t policyFlags, TouchData* touch,
-            BitSet32 idBits, uint32_t changedId, int32_t motionEventAction);
+            BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
+            int32_t motionEventAction);
 
     void applyPolicyAndDispatchVirtualKey(nsecs_t when, uint32_t policyFlags,
             int32_t keyEventAction, int32_t keyEventFlags,
@@ -738,7 +849,7 @@
     virtual void process(const RawEvent* rawEvent);
 
 protected:
-    virtual void configureAxes();
+    virtual void configureRawAxes();
 
 private:
     struct Accumulator {
@@ -767,7 +878,7 @@
     int32_t mX;
     int32_t mY;
     int32_t mPressure;
-    int32_t mSize;
+    int32_t mToolWidth;
 
     void initialize();
 
@@ -784,7 +895,7 @@
     virtual void process(const RawEvent* rawEvent);
 
 protected:
-    virtual void configureAxes();
+    virtual void configureRawAxes();
 
 private:
     struct Accumulator {
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 891661d..1d38b4b 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -139,11 +139,7 @@
 
 status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
         RawAbsoluteAxisInfo* outAxisInfo) const {
-    outAxisInfo->valid = false;
-    outAxisInfo->minValue = 0;
-    outAxisInfo->maxValue = 0;
-    outAxisInfo->flat = 0;
-    outAxisInfo->fuzz = 0;
+    outAxisInfo->clear();
 
     AutoMutex _l(mLock);
     device_t* device = getDevice(deviceId);
@@ -709,8 +705,7 @@
     LOGV("Getting absolute controllers...");
     if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) >= 0) {
         // Is this a new modern multi-touch driver?
-        if (test_bit(ABS_MT_TOUCH_MAJOR, abs_bitmask)
-                && test_bit(ABS_MT_POSITION_X, abs_bitmask)
+        if (test_bit(ABS_MT_POSITION_X, abs_bitmask)
                 && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) {
             device->classes |= INPUT_DEVICE_CLASS_TOUCHSCREEN | INPUT_DEVICE_CLASS_TOUCHSCREEN_MT;
 
diff --git a/libs/ui/InputDispatcher.cpp b/libs/ui/InputDispatcher.cpp
index e35050c..886c785 100644
--- a/libs/ui/InputDispatcher.cpp
+++ b/libs/ui/InputDispatcher.cpp
@@ -405,12 +405,15 @@
         sampleCount += 1;
     }
     for (uint32_t i = 0; i < entry->pointerCount; i++) {
-        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f",
+        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
+                "touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
+                "orientation=%f",
                 i, entry->pointerIds[i],
-                sample->pointerCoords[i].x,
-                sample->pointerCoords[i].y,
-                sample->pointerCoords[i].pressure,
-                sample->pointerCoords[i].size);
+                sample->pointerCoords[i].x, sample->pointerCoords[i].y,
+                sample->pointerCoords[i].pressure, sample->pointerCoords[i].size,
+                sample->pointerCoords[i].touchMajor, sample->pointerCoords[i].touchMinor,
+                sample->pointerCoords[i].toolMajor, sample->pointerCoords[i].toolMinor,
+                sample->pointerCoords[i].orientation);
     }
 
     // Keep in mind that due to batching, it is possible for the number of samples actually
@@ -1080,9 +1083,14 @@
             eventTime, deviceId, source, policyFlags, action, metaState, edgeFlags,
             xPrecision, yPrecision, downTime);
     for (uint32_t i = 0; i < pointerCount; i++) {
-        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f",
+        LOGD("  Pointer %d: id=%d, x=%f, y=%f, pressure=%f, size=%f, "
+                "touchMajor=%f, touchMinor=%d, toolMajor=%f, toolMinor=%f, "
+                "orientation=%f",
                 i, pointerIds[i], pointerCoords[i].x, pointerCoords[i].y,
-                pointerCoords[i].pressure, pointerCoords[i].size);
+                pointerCoords[i].pressure, pointerCoords[i].size,
+                pointerCoords[i].touchMajor, pointerCoords[i].touchMinor,
+                pointerCoords[i].toolMajor, pointerCoords[i].toolMinor,
+                pointerCoords[i].orientation);
     }
 #endif
 
diff --git a/libs/ui/InputReader.cpp b/libs/ui/InputReader.cpp
index 6f042ec..8ffb48d 100644
--- a/libs/ui/InputReader.cpp
+++ b/libs/ui/InputReader.cpp
@@ -26,11 +26,14 @@
 #include <ui/InputReader.h>
 
 #include <stddef.h>
+#include <stdlib.h>
 #include <unistd.h>
 #include <errno.h>
 #include <limits.h>
 #include <math.h>
 
+#define INDENT "  "
+
 namespace android {
 
 // --- Static Functions ---
@@ -52,6 +55,14 @@
     b = temp;
 }
 
+inline static float avg(float x, float y) {
+    return (x + y) / 2;
+}
+
+inline static float pythag(float x, float y) {
+    return sqrtf(x * x + y * y);
+}
+
 
 int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
     int32_t mask;
@@ -116,6 +127,64 @@
 }
 
 
+// --- InputDeviceCalibration ---
+
+InputDeviceCalibration::InputDeviceCalibration() {
+}
+
+void InputDeviceCalibration::clear() {
+    mProperties.clear();
+}
+
+void InputDeviceCalibration::addProperty(const String8& key, const String8& value) {
+    mProperties.add(key, value);
+}
+
+bool InputDeviceCalibration::tryGetProperty(const String8& key, String8& outValue) const {
+    ssize_t index = mProperties.indexOfKey(key);
+    if (index < 0) {
+        return false;
+    }
+
+    outValue = mProperties.valueAt(index);
+    return true;
+}
+
+bool InputDeviceCalibration::tryGetProperty(const String8& key, int32_t& outValue) const {
+    String8 stringValue;
+    if (! tryGetProperty(key, stringValue) || stringValue.length() == 0) {
+        return false;
+    }
+
+    char* end;
+    int value = strtol(stringValue.string(), & end, 10);
+    if (*end != '\0') {
+        LOGW("Input device calibration key '%s' has invalid value '%s'.  Expected an integer.",
+                key.string(), stringValue.string());
+        return false;
+    }
+    outValue = value;
+    return true;
+}
+
+bool InputDeviceCalibration::tryGetProperty(const String8& key, float& outValue) const {
+    String8 stringValue;
+    if (! tryGetProperty(key, stringValue) || stringValue.length() == 0) {
+        return false;
+    }
+
+    char* end;
+    float value = strtof(stringValue.string(), & end);
+    if (*end != '\0') {
+        LOGW("Input device calibration key '%s' has invalid value '%s'.  Expected a float.",
+                key.string(), stringValue.string());
+        return false;
+    }
+    outValue = value;
+    return true;
+}
+
+
 // --- InputReader ---
 
 InputReader::InputReader(const sp<EventHubInterface>& eventHub,
@@ -167,9 +236,18 @@
     String8 name = mEventHub->getDeviceName(deviceId);
     uint32_t classes = mEventHub->getDeviceClasses(deviceId);
 
+    // Write a log message about the added device as a heading for subsequent log messages.
+    LOGI("Device added: id=0x%x, name=%s", deviceId, name.string());
+
     InputDevice* device = createDevice(deviceId, name, classes);
     device->configure();
 
+    if (device->isIgnored()) {
+        LOGI(INDENT "Sources: none (device is ignored)");
+    } else {
+        LOGI(INDENT "Sources: 0x%08x", device->getSources());
+    }
+
     bool added = false;
     { // acquire device registry writer lock
         RWLock::AutoWLock _wl(mDeviceRegistryLock);
@@ -187,14 +265,6 @@
         return;
     }
 
-    if (device->isIgnored()) {
-        LOGI("Device added: id=0x%x, name=%s (ignored non-input device)",
-                deviceId, name.string());
-    } else {
-        LOGI("Device added: id=0x%x, name=%s, sources=%08x",
-                deviceId, name.string(), device->getSources());
-    }
-
     handleConfigurationChanged(when);
 }
 
@@ -217,8 +287,7 @@
         return;
     }
 
-    device->reset();
-
+    // Write a log message about the removed device as a heading for subsequent log messages.
     if (device->isIgnored()) {
         LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)",
                 device->getId(), device->getName().string());
@@ -227,6 +296,8 @@
                 device->getId(), device->getName().string(), device->getSources());
     }
 
+    device->reset();
+
     delete device;
 
     handleConfigurationChanged(when);
@@ -537,6 +608,10 @@
 }
 
 void InputDevice::configure() {
+    if (! isIgnored()) {
+        mContext->getPolicy()->getInputDeviceCalibration(mName, mCalibration);
+    }
+
     mSources = 0;
 
     size_t numMappers = mMappers.size();
@@ -1121,13 +1196,35 @@
 
         info->addMotionRange(AINPUT_MOTION_RANGE_X, mLocked.orientedRanges.x);
         info->addMotionRange(AINPUT_MOTION_RANGE_Y, mLocked.orientedRanges.y);
-        info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE, mLocked.orientedRanges.pressure);
-        info->addMotionRange(AINPUT_MOTION_RANGE_SIZE, mLocked.orientedRanges.size);
-        info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MAJOR, mLocked.orientedRanges.touchMajor);
-        info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MINOR, mLocked.orientedRanges.touchMinor);
-        info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MAJOR, mLocked.orientedRanges.toolMajor);
-        info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MINOR, mLocked.orientedRanges.toolMinor);
-        info->addMotionRange(AINPUT_MOTION_RANGE_ORIENTATION, mLocked.orientedRanges.orientation);
+
+        if (mLocked.orientedRanges.havePressure) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_PRESSURE,
+                    mLocked.orientedRanges.pressure);
+        }
+
+        if (mLocked.orientedRanges.haveSize) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_SIZE,
+                    mLocked.orientedRanges.size);
+        }
+
+        if (mLocked.orientedRanges.haveTouchArea) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MAJOR,
+                    mLocked.orientedRanges.touchMajor);
+            info->addMotionRange(AINPUT_MOTION_RANGE_TOUCH_MINOR,
+                    mLocked.orientedRanges.touchMinor);
+        }
+
+        if (mLocked.orientedRanges.haveToolArea) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MAJOR,
+                    mLocked.orientedRanges.toolMajor);
+            info->addMotionRange(AINPUT_MOTION_RANGE_TOOL_MINOR,
+                    mLocked.orientedRanges.toolMinor);
+        }
+
+        if (mLocked.orientedRanges.haveOrientation) {
+            info->addMotionRange(AINPUT_MOTION_RANGE_ORIENTATION,
+                    mLocked.orientedRanges.orientation);
+        }
     } // release lock
 }
 
@@ -1144,77 +1241,72 @@
     mJumpyTouchFilter.jumpyPointsDropped = 0;
 
     mLocked.currentVirtualKey.down = false;
+
+    mLocked.orientedRanges.havePressure = false;
+    mLocked.orientedRanges.haveSize = false;
+    mLocked.orientedRanges.haveTouchArea = false;
+    mLocked.orientedRanges.haveToolArea = false;
+    mLocked.orientedRanges.haveOrientation = false;
+}
+
+static void logAxisInfo(RawAbsoluteAxisInfo axis, const char* name) {
+    if (axis.valid) {
+        LOGI(INDENT "Raw %s axis: min=%d, max=%d, flat=%d, fuzz=%d",
+                name, axis.minValue, axis.maxValue, axis.flat, axis.fuzz);
+    } else {
+        LOGI(INDENT "Raw %s axis: unknown range", name);
+    }
 }
 
 void TouchInputMapper::configure() {
     InputMapper::configure();
 
     // Configure basic parameters.
-    mParameters.useBadTouchFilter = getPolicy()->filterTouchEvents();
-    mParameters.useAveragingTouchFilter = getPolicy()->filterTouchEvents();
-    mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
+    configureParameters();
 
     // Configure absolute axis information.
-    configureAxes();
+    configureRawAxes();
+    logRawAxes();
+
+    // Prepare input device calibration.
+    parseCalibration();
+    resolveCalibration();
+    logCalibration();
 
     { // acquire lock
         AutoMutex _l(mLock);
 
-        // Configure pressure factors.
-        if (mAxes.pressure.valid) {
-            mLocked.pressureOrigin = mAxes.pressure.minValue;
-            mLocked.pressureScale = 1.0f / mAxes.pressure.getRange();
-        } else {
-            mLocked.pressureOrigin = 0;
-            mLocked.pressureScale = 1.0f;
-        }
-
-        mLocked.orientedRanges.pressure.min = 0.0f;
-        mLocked.orientedRanges.pressure.max = 1.0f;
-        mLocked.orientedRanges.pressure.flat = 0.0f;
-        mLocked.orientedRanges.pressure.fuzz = mLocked.pressureScale;
-
-        // Configure size factors.
-        if (mAxes.size.valid) {
-            mLocked.sizeOrigin = mAxes.size.minValue;
-            mLocked.sizeScale = 1.0f / mAxes.size.getRange();
-        } else {
-            mLocked.sizeOrigin = 0;
-            mLocked.sizeScale = 1.0f;
-        }
-
-        mLocked.orientedRanges.size.min = 0.0f;
-        mLocked.orientedRanges.size.max = 1.0f;
-        mLocked.orientedRanges.size.flat = 0.0f;
-        mLocked.orientedRanges.size.fuzz = mLocked.sizeScale;
-
-        // Configure orientation factors.
-        if (mAxes.orientation.valid && mAxes.orientation.maxValue > 0) {
-            mLocked.orientationScale = float(M_PI_2) / mAxes.orientation.maxValue;
-        } else {
-            mLocked.orientationScale = 0.0f;
-        }
-
-        mLocked.orientedRanges.orientation.min = - M_PI_2;
-        mLocked.orientedRanges.orientation.max = M_PI_2;
-        mLocked.orientedRanges.orientation.flat = 0;
-        mLocked.orientedRanges.orientation.fuzz = mLocked.orientationScale;
-
-        // Configure surface dimensions and orientation.
+         // Configure surface dimensions and orientation.
         configureSurfaceLocked();
     } // release lock
 }
 
-void TouchInputMapper::configureAxes() {
-    mAxes.x.valid = false;
-    mAxes.y.valid = false;
-    mAxes.pressure.valid = false;
-    mAxes.size.valid = false;
-    mAxes.touchMajor.valid = false;
-    mAxes.touchMinor.valid = false;
-    mAxes.toolMajor.valid = false;
-    mAxes.toolMinor.valid = false;
-    mAxes.orientation.valid = false;
+void TouchInputMapper::configureParameters() {
+    mParameters.useBadTouchFilter = getPolicy()->filterTouchEvents();
+    mParameters.useAveragingTouchFilter = getPolicy()->filterTouchEvents();
+    mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
+}
+
+void TouchInputMapper::configureRawAxes() {
+    mRawAxes.x.clear();
+    mRawAxes.y.clear();
+    mRawAxes.pressure.clear();
+    mRawAxes.touchMajor.clear();
+    mRawAxes.touchMinor.clear();
+    mRawAxes.toolMajor.clear();
+    mRawAxes.toolMinor.clear();
+    mRawAxes.orientation.clear();
+}
+
+void TouchInputMapper::logRawAxes() {
+    logAxisInfo(mRawAxes.x, "x");
+    logAxisInfo(mRawAxes.y, "y");
+    logAxisInfo(mRawAxes.pressure, "pressure");
+    logAxisInfo(mRawAxes.touchMajor, "touchMajor");
+    logAxisInfo(mRawAxes.touchMinor, "touchMinor");
+    logAxisInfo(mRawAxes.toolMajor, "toolMajor");
+    logAxisInfo(mRawAxes.toolMinor, "toolMinor");
+    logAxisInfo(mRawAxes.orientation, "orientation");
 }
 
 bool TouchInputMapper::configureSurfaceLocked() {
@@ -1228,8 +1320,8 @@
         }
     } else {
         orientation = InputReaderPolicyInterface::ROTATION_0;
-        width = mAxes.x.getRange();
-        height = mAxes.y.getRange();
+        width = mRawAxes.x.getRange();
+        height = mRawAxes.y.getRange();
     }
 
     bool orientationChanged = mLocked.surfaceOrientation != orientation;
@@ -1239,24 +1331,24 @@
 
     bool sizeChanged = mLocked.surfaceWidth != width || mLocked.surfaceHeight != height;
     if (sizeChanged) {
+        LOGI("Device configured: id=0x%x, name=%s (display size was changed)",
+                getDeviceId(), getDeviceName().string());
+
         mLocked.surfaceWidth = width;
         mLocked.surfaceHeight = height;
 
-        // Compute size-dependent translation and scaling factors and place virtual keys.
-        if (mAxes.x.valid && mAxes.y.valid) {
-            mLocked.xOrigin = mAxes.x.minValue;
-            mLocked.yOrigin = mAxes.y.minValue;
-
-            LOGI("Device configured: id=0x%x, name=%s (display size was changed)",
-                    getDeviceId(), getDeviceName().string());
-
-            mLocked.xScale = float(width) / mAxes.x.getRange();
-            mLocked.yScale = float(height) / mAxes.y.getRange();
+        // Configure X and Y factors.
+        if (mRawAxes.x.valid && mRawAxes.y.valid) {
+            mLocked.xOrigin = mRawAxes.x.minValue;
+            mLocked.yOrigin = mRawAxes.y.minValue;
+            mLocked.xScale = float(width) / mRawAxes.x.getRange();
+            mLocked.yScale = float(height) / mRawAxes.y.getRange();
             mLocked.xPrecision = 1.0f / mLocked.xScale;
             mLocked.yPrecision = 1.0f / mLocked.yScale;
 
             configureVirtualKeysLocked();
         } else {
+            LOGW(INDENT "Touch device did not report support for X or Y axis!");
             mLocked.xOrigin = 0;
             mLocked.yOrigin = 0;
             mLocked.xScale = 1.0f;
@@ -1265,22 +1357,112 @@
             mLocked.yPrecision = 1.0f;
         }
 
-        // Configure touch and tool area ranges.
-        float diagonal = sqrt(float(width * width + height * height));
-        float diagonalFuzz = sqrt(mLocked.xScale * mLocked.xScale
-                + mLocked.yScale * mLocked.yScale);
+        // Scale factor for terms that are not oriented in a particular axis.
+        // If the pixels are square then xScale == yScale otherwise we fake it
+        // by choosing an average.
+        mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale);
 
-        InputDeviceInfo::MotionRange area;
-        area.min = 0.0f;
-        area.max = diagonal;
-        area.flat = 0.0f;
-        area.fuzz = diagonalFuzz;
+        // Size of diagonal axis.
+        float diagonalSize = pythag(width, height);
 
-        mLocked.orientedRanges.touchMajor = area;
-        mLocked.orientedRanges.touchMinor = area;
+        // TouchMajor and TouchMinor factors.
+        if (mCalibration.touchAreaCalibration != Calibration::TOUCH_AREA_CALIBRATION_NONE) {
+            mLocked.orientedRanges.haveTouchArea = true;
+            mLocked.orientedRanges.touchMajor.min = 0;
+            mLocked.orientedRanges.touchMajor.max = diagonalSize;
+            mLocked.orientedRanges.touchMajor.flat = 0;
+            mLocked.orientedRanges.touchMajor.fuzz = 0;
+            mLocked.orientedRanges.touchMinor = mLocked.orientedRanges.touchMajor;
+        }
 
-        mLocked.orientedRanges.toolMajor = area;
-        mLocked.orientedRanges.toolMinor = area;
+        // ToolMajor and ToolMinor factors.
+        if (mCalibration.toolAreaCalibration != Calibration::TOOL_AREA_CALIBRATION_NONE) {
+            mLocked.toolAreaLinearScale = 0;
+            mLocked.toolAreaLinearBias = 0;
+            if (mCalibration.toolAreaCalibration == Calibration::TOOL_AREA_CALIBRATION_LINEAR) {
+                if (mCalibration.haveToolAreaLinearScale) {
+                    mLocked.toolAreaLinearScale = mCalibration.toolAreaLinearScale;
+                } else if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) {
+                    mLocked.toolAreaLinearScale = float(min(width, height))
+                            / mRawAxes.toolMajor.maxValue;
+                }
+
+                if (mCalibration.haveToolAreaLinearBias) {
+                    mLocked.toolAreaLinearBias = mCalibration.toolAreaLinearBias;
+                }
+            }
+
+            mLocked.orientedRanges.haveToolArea = true;
+            mLocked.orientedRanges.toolMajor.min = 0;
+            mLocked.orientedRanges.toolMajor.max = diagonalSize;
+            mLocked.orientedRanges.toolMajor.flat = 0;
+            mLocked.orientedRanges.toolMajor.fuzz = 0;
+            mLocked.orientedRanges.toolMinor = mLocked.orientedRanges.toolMajor;
+        }
+
+        // Pressure factors.
+        if (mCalibration.pressureCalibration != Calibration::PRESSURE_CALIBRATION_NONE) {
+            RawAbsoluteAxisInfo rawPressureAxis;
+            switch (mCalibration.pressureSource) {
+            case Calibration::PRESSURE_SOURCE_PRESSURE:
+                rawPressureAxis = mRawAxes.pressure;
+                break;
+            case Calibration::PRESSURE_SOURCE_TOUCH:
+                rawPressureAxis = mRawAxes.touchMajor;
+                break;
+            default:
+                rawPressureAxis.clear();
+            }
+
+            mLocked.pressureScale = 0;
+            if (mCalibration.pressureCalibration == Calibration::PRESSURE_CALIBRATION_PHYSICAL
+                    || mCalibration.pressureCalibration
+                            == Calibration::PRESSURE_CALIBRATION_AMPLITUDE) {
+                if (mCalibration.havePressureScale) {
+                    mLocked.pressureScale = mCalibration.pressureScale;
+                } else if (rawPressureAxis.valid && rawPressureAxis.maxValue != 0) {
+                    mLocked.pressureScale = 1.0f / rawPressureAxis.maxValue;
+                }
+            }
+
+            mLocked.orientedRanges.havePressure = true;
+            mLocked.orientedRanges.pressure.min = 0;
+            mLocked.orientedRanges.pressure.max = 1.0;
+            mLocked.orientedRanges.pressure.flat = 0;
+            mLocked.orientedRanges.pressure.fuzz = 0;
+        }
+
+        // Size factors.
+        if (mCalibration.sizeCalibration != Calibration::SIZE_CALIBRATION_NONE) {
+            mLocked.sizeScale = 0;
+            if (mCalibration.sizeCalibration == Calibration::SIZE_CALIBRATION_NORMALIZED) {
+                if (mRawAxes.toolMajor.valid && mRawAxes.toolMajor.maxValue != 0) {
+                    mLocked.sizeScale = 1.0f / mRawAxes.toolMajor.maxValue;
+                }
+            }
+
+            mLocked.orientedRanges.haveSize = true;
+            mLocked.orientedRanges.size.min = 0;
+            mLocked.orientedRanges.size.max = 1.0;
+            mLocked.orientedRanges.size.flat = 0;
+            mLocked.orientedRanges.size.fuzz = 0;
+        }
+
+        // Orientation
+        if (mCalibration.orientationCalibration != Calibration::ORIENTATION_CALIBRATION_NONE) {
+            mLocked.orientationScale = 0;
+            if (mCalibration.orientationCalibration
+                    == Calibration::ORIENTATION_CALIBRATION_INTERPOLATED) {
+                if (mRawAxes.orientation.valid && mRawAxes.orientation.maxValue != 0) {
+                    mLocked.orientationScale = float(M_PI_2) / mRawAxes.orientation.maxValue;
+                }
+            }
+
+            mLocked.orientedRanges.orientation.min = - M_PI_2;
+            mLocked.orientedRanges.orientation.max = M_PI_2;
+            mLocked.orientedRanges.orientation.flat = 0;
+            mLocked.orientedRanges.orientation.fuzz = 0;
+        }
     }
 
     if (orientationChanged || sizeChanged) {
@@ -1322,10 +1504,10 @@
 }
 
 void TouchInputMapper::configureVirtualKeysLocked() {
-    assert(mAxes.x.valid && mAxes.y.valid);
+    assert(mRawAxes.x.valid && mRawAxes.y.valid);
 
     // Note: getVirtualKeyDefinitions is non-reentrant so we can continue holding the lock.
-    Vector<InputReaderPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions;
+    Vector<VirtualKeyDefinition> virtualKeyDefinitions;
     getPolicy()->getVirtualKeyDefinitions(getDeviceName(), virtualKeyDefinitions);
 
     mLocked.virtualKeys.clear();
@@ -1336,13 +1518,13 @@
 
     mLocked.virtualKeys.setCapacity(virtualKeyDefinitions.size());
 
-    int32_t touchScreenLeft = mAxes.x.minValue;
-    int32_t touchScreenTop = mAxes.y.minValue;
-    int32_t touchScreenWidth = mAxes.x.getRange();
-    int32_t touchScreenHeight = mAxes.y.getRange();
+    int32_t touchScreenLeft = mRawAxes.x.minValue;
+    int32_t touchScreenTop = mRawAxes.y.minValue;
+    int32_t touchScreenWidth = mRawAxes.x.getRange();
+    int32_t touchScreenHeight = mRawAxes.y.getRange();
 
     for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
-        const InputReaderPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition =
+        const VirtualKeyDefinition& virtualKeyDefinition =
                 virtualKeyDefinitions[i];
 
         mLocked.virtualKeys.add();
@@ -1353,7 +1535,8 @@
         uint32_t flags;
         if (getEventHub()->scancodeToKeycode(getDeviceId(), virtualKey.scanCode,
                 & keyCode, & flags)) {
-            LOGW("  VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode);
+            LOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring",
+                    virtualKey.scanCode);
             mLocked.virtualKeys.pop(); // drop the key
             continue;
         }
@@ -1374,12 +1557,316 @@
         virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
                 * touchScreenHeight / mLocked.surfaceHeight + touchScreenTop;
 
-        LOGI("  VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
+        LOGI(INDENT "VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
                 virtualKey.scanCode, virtualKey.keyCode,
                 virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
     }
 }
 
+void TouchInputMapper::parseCalibration() {
+    const InputDeviceCalibration& in = getDevice()->getCalibration();
+    Calibration& out = mCalibration;
+
+    // Touch Area
+    out.touchAreaCalibration = Calibration::TOUCH_AREA_CALIBRATION_DEFAULT;
+    String8 touchAreaCalibrationString;
+    if (in.tryGetProperty(String8("touch.touchArea.calibration"), touchAreaCalibrationString)) {
+        if (touchAreaCalibrationString == "none") {
+            out.touchAreaCalibration = Calibration::TOUCH_AREA_CALIBRATION_NONE;
+        } else if (touchAreaCalibrationString == "geometric") {
+            out.touchAreaCalibration = Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC;
+        } else if (touchAreaCalibrationString == "pressure") {
+            out.touchAreaCalibration = Calibration::TOUCH_AREA_CALIBRATION_PRESSURE;
+        } else if (touchAreaCalibrationString != "default") {
+            LOGW("Invalid value for touch.touchArea.calibration: '%s'",
+                    touchAreaCalibrationString.string());
+        }
+    }
+
+    // Tool Area
+    out.toolAreaCalibration = Calibration::TOOL_AREA_CALIBRATION_DEFAULT;
+    String8 toolAreaCalibrationString;
+    if (in.tryGetProperty(String8("tool.toolArea.calibration"), toolAreaCalibrationString)) {
+        if (toolAreaCalibrationString == "none") {
+            out.toolAreaCalibration = Calibration::TOOL_AREA_CALIBRATION_NONE;
+        } else if (toolAreaCalibrationString == "geometric") {
+            out.toolAreaCalibration = Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC;
+        } else if (toolAreaCalibrationString == "linear") {
+            out.toolAreaCalibration = Calibration::TOOL_AREA_CALIBRATION_LINEAR;
+        } else if (toolAreaCalibrationString != "default") {
+            LOGW("Invalid value for tool.toolArea.calibration: '%s'",
+                    toolAreaCalibrationString.string());
+        }
+    }
+
+    out.haveToolAreaLinearScale = in.tryGetProperty(String8("touch.toolArea.linearScale"),
+            out.toolAreaLinearScale);
+    out.haveToolAreaLinearBias = in.tryGetProperty(String8("touch.toolArea.linearBias"),
+            out.toolAreaLinearBias);
+    out.haveToolAreaIsSummed = in.tryGetProperty(String8("touch.toolArea.isSummed"),
+            out.toolAreaIsSummed);
+
+    // Pressure
+    out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_DEFAULT;
+    String8 pressureCalibrationString;
+    if (in.tryGetProperty(String8("tool.pressure.calibration"), pressureCalibrationString)) {
+        if (pressureCalibrationString == "none") {
+            out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+        } else if (pressureCalibrationString == "physical") {
+            out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_PHYSICAL;
+        } else if (pressureCalibrationString == "amplitude") {
+            out.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+        } else if (pressureCalibrationString != "default") {
+            LOGW("Invalid value for tool.pressure.calibration: '%s'",
+                    pressureCalibrationString.string());
+        }
+    }
+
+    out.pressureSource = Calibration::PRESSURE_SOURCE_DEFAULT;
+    String8 pressureSourceString;
+    if (in.tryGetProperty(String8("touch.pressure.source"), pressureSourceString)) {
+        if (pressureSourceString == "pressure") {
+            out.pressureSource = Calibration::PRESSURE_SOURCE_PRESSURE;
+        } else if (pressureSourceString == "touch") {
+            out.pressureSource = Calibration::PRESSURE_SOURCE_TOUCH;
+        } else if (pressureSourceString != "default") {
+            LOGW("Invalid value for touch.pressure.source: '%s'",
+                    pressureSourceString.string());
+        }
+    }
+
+    out.havePressureScale = in.tryGetProperty(String8("touch.pressure.scale"),
+            out.pressureScale);
+
+    // Size
+    out.sizeCalibration = Calibration::SIZE_CALIBRATION_DEFAULT;
+    String8 sizeCalibrationString;
+    if (in.tryGetProperty(String8("tool.size.calibration"), sizeCalibrationString)) {
+        if (sizeCalibrationString == "none") {
+            out.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+        } else if (sizeCalibrationString == "normalized") {
+            out.sizeCalibration = Calibration::SIZE_CALIBRATION_NORMALIZED;
+        } else if (sizeCalibrationString != "default") {
+            LOGW("Invalid value for tool.size.calibration: '%s'",
+                    sizeCalibrationString.string());
+        }
+    }
+
+    // Orientation
+    out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_DEFAULT;
+    String8 orientationCalibrationString;
+    if (in.tryGetProperty(String8("tool.orientation.calibration"), orientationCalibrationString)) {
+        if (orientationCalibrationString == "none") {
+            out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+        } else if (orientationCalibrationString == "interpolated") {
+            out.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+        } else if (orientationCalibrationString != "default") {
+            LOGW("Invalid value for tool.orientation.calibration: '%s'",
+                    orientationCalibrationString.string());
+        }
+    }
+}
+
+void TouchInputMapper::resolveCalibration() {
+    // Pressure
+    switch (mCalibration.pressureSource) {
+    case Calibration::PRESSURE_SOURCE_DEFAULT:
+        if (mRawAxes.pressure.valid) {
+            mCalibration.pressureSource = Calibration::PRESSURE_SOURCE_PRESSURE;
+        } else if (mRawAxes.touchMajor.valid) {
+            mCalibration.pressureSource = Calibration::PRESSURE_SOURCE_TOUCH;
+        }
+        break;
+
+    case Calibration::PRESSURE_SOURCE_PRESSURE:
+        if (! mRawAxes.pressure.valid) {
+            LOGW("Calibration property touch.pressure.source is 'pressure' but "
+                    "the pressure axis is not available.");
+        }
+        break;
+
+    case Calibration::PRESSURE_SOURCE_TOUCH:
+        if (! mRawAxes.touchMajor.valid) {
+            LOGW("Calibration property touch.pressure.source is 'touch' but "
+                    "the touchMajor axis is not available.");
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    switch (mCalibration.pressureCalibration) {
+    case Calibration::PRESSURE_CALIBRATION_DEFAULT:
+        if (mCalibration.pressureSource != Calibration::PRESSURE_SOURCE_DEFAULT) {
+            mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_AMPLITUDE;
+        } else {
+            mCalibration.pressureCalibration = Calibration::PRESSURE_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    // Tool Area
+    switch (mCalibration.toolAreaCalibration) {
+    case Calibration::TOOL_AREA_CALIBRATION_DEFAULT:
+        if (mRawAxes.toolMajor.valid) {
+            mCalibration.toolAreaCalibration = Calibration::TOOL_AREA_CALIBRATION_LINEAR;
+        } else {
+            mCalibration.toolAreaCalibration = Calibration::TOOL_AREA_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    // Touch Area
+    switch (mCalibration.touchAreaCalibration) {
+    case Calibration::TOUCH_AREA_CALIBRATION_DEFAULT:
+        if (mCalibration.pressureCalibration != Calibration::PRESSURE_CALIBRATION_NONE
+                && mCalibration.toolAreaCalibration != Calibration::TOOL_AREA_CALIBRATION_NONE) {
+            mCalibration.touchAreaCalibration = Calibration::TOUCH_AREA_CALIBRATION_PRESSURE;
+        } else {
+            mCalibration.touchAreaCalibration = Calibration::TOUCH_AREA_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    // Size
+    switch (mCalibration.sizeCalibration) {
+    case Calibration::SIZE_CALIBRATION_DEFAULT:
+        if (mRawAxes.toolMajor.valid) {
+            mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NORMALIZED;
+        } else {
+            mCalibration.sizeCalibration = Calibration::SIZE_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    // Orientation
+    switch (mCalibration.orientationCalibration) {
+    case Calibration::ORIENTATION_CALIBRATION_DEFAULT:
+        if (mRawAxes.orientation.valid) {
+            mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_INTERPOLATED;
+        } else {
+            mCalibration.orientationCalibration = Calibration::ORIENTATION_CALIBRATION_NONE;
+        }
+        break;
+
+    default:
+        break;
+    }
+}
+
+void TouchInputMapper::logCalibration() {
+    // Touch Area
+    switch (mCalibration.touchAreaCalibration) {
+    case Calibration::TOUCH_AREA_CALIBRATION_NONE:
+        LOGI(INDENT "  touch.touchArea.calibration: none");
+        break;
+    case Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC:
+        LOGI(INDENT "  touch.touchArea.calibration: geometric");
+        break;
+    case Calibration::TOUCH_AREA_CALIBRATION_PRESSURE:
+        LOGI(INDENT "  touch.touchArea.calibration: pressure");
+        break;
+    default:
+        assert(false);
+    }
+
+    // Tool Area
+    switch (mCalibration.toolAreaCalibration) {
+    case Calibration::TOOL_AREA_CALIBRATION_NONE:
+        LOGI(INDENT "  touch.toolArea.calibration: none");
+        break;
+    case Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC:
+        LOGI(INDENT "  touch.toolArea.calibration: geometric");
+        break;
+    case Calibration::TOOL_AREA_CALIBRATION_LINEAR:
+        LOGI(INDENT "  touch.toolArea.calibration: linear");
+        break;
+    default:
+        assert(false);
+    }
+
+    if (mCalibration.haveToolAreaLinearScale) {
+        LOGI(INDENT "  touch.toolArea.linearScale: %f", mCalibration.toolAreaLinearScale);
+    }
+
+    if (mCalibration.haveToolAreaLinearBias) {
+        LOGI(INDENT "  touch.toolArea.linearBias: %f", mCalibration.toolAreaLinearBias);
+    }
+
+    if (mCalibration.haveToolAreaIsSummed) {
+        LOGI(INDENT "  touch.toolArea.isSummed: %d", mCalibration.toolAreaIsSummed);
+    }
+
+    // Pressure
+    switch (mCalibration.pressureCalibration) {
+    case Calibration::PRESSURE_CALIBRATION_NONE:
+        LOGI(INDENT "  touch.pressure.calibration: none");
+        break;
+    case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+        LOGI(INDENT "  touch.pressure.calibration: physical");
+        break;
+    case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+        LOGI(INDENT "  touch.pressure.calibration: amplitude");
+        break;
+    default:
+        assert(false);
+    }
+
+    switch (mCalibration.pressureSource) {
+    case Calibration::PRESSURE_SOURCE_PRESSURE:
+        LOGI(INDENT "  touch.pressure.source: pressure");
+        break;
+    case Calibration::PRESSURE_SOURCE_TOUCH:
+        LOGI(INDENT "  touch.pressure.source: touch");
+        break;
+    case Calibration::PRESSURE_SOURCE_DEFAULT:
+        break;
+    default:
+        assert(false);
+    }
+
+    if (mCalibration.havePressureScale) {
+        LOGI(INDENT "  touch.pressure.scale: %f", mCalibration.pressureScale);
+    }
+
+    // Size
+    switch (mCalibration.sizeCalibration) {
+    case Calibration::SIZE_CALIBRATION_NONE:
+        LOGI(INDENT "  touch.size.calibration: none");
+        break;
+    case Calibration::SIZE_CALIBRATION_NORMALIZED:
+        LOGI(INDENT "  touch.size.calibration: normalized");
+        break;
+    default:
+        assert(false);
+    }
+
+    // Orientation
+    switch (mCalibration.orientationCalibration) {
+    case Calibration::ORIENTATION_CALIBRATION_NONE:
+        LOGI(INDENT "  touch.orientation.calibration: none");
+        break;
+    case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+        LOGI(INDENT "  touch.orientation.calibration: interpolated");
+        break;
+    default:
+        assert(false);
+    }
+}
+
 void TouchInputMapper::reset() {
     // Synthesize touch up event if touch is currently down.
     // This will also take care of finishing virtual key processing if needed.
@@ -1584,13 +2071,14 @@
         // The dispatcher takes care of batching moves so we don't have to deal with that here.
         int32_t motionEventAction = AMOTION_EVENT_ACTION_MOVE;
         dispatchTouch(when, policyFlags, & mCurrentTouch,
-                currentIdBits, -1, motionEventAction);
+                currentIdBits, -1, currentPointerCount, motionEventAction);
     } else {
         // There may be pointers going up and pointers going down at the same time when pointer
         // ids are reported by the device driver.
         BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value);
         BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value);
         BitSet32 activeIdBits(lastIdBits.value);
+        uint32_t pointerCount = lastPointerCount;
 
         while (! upIdBits.isEmpty()) {
             uint32_t upId = upIdBits.firstMarkedBit();
@@ -1606,7 +2094,8 @@
             }
 
             dispatchTouch(when, policyFlags, & mLastTouch,
-                    oldActiveIdBits, upId, motionEventAction);
+                    oldActiveIdBits, upId, pointerCount, motionEventAction);
+            pointerCount -= 1;
         }
 
         while (! downIdBits.isEmpty()) {
@@ -1623,16 +2112,16 @@
                 motionEventAction = AMOTION_EVENT_ACTION_POINTER_DOWN;
             }
 
+            pointerCount += 1;
             dispatchTouch(when, policyFlags, & mCurrentTouch,
-                    activeIdBits, downId, motionEventAction);
+                    activeIdBits, downId, pointerCount, motionEventAction);
         }
     }
 }
 
 void TouchInputMapper::dispatchTouch(nsecs_t when, uint32_t policyFlags,
-        TouchData* touch, BitSet32 idBits, uint32_t changedId,
+        TouchData* touch, BitSet32 idBits, uint32_t changedId, uint32_t pointerCount,
         int32_t motionEventAction) {
-    uint32_t pointerCount = 0;
     int32_t pointerIds[MAX_POINTERS];
     PointerCoords pointerCoords[MAX_POINTERS];
     int32_t motionEventEdgeFlags = 0;
@@ -1643,36 +2132,130 @@
 
         // Walk through the the active pointers and map touch screen coordinates (TouchData) into
         // display coordinates (PointerCoords) and adjust for display orientation.
-        while (! idBits.isEmpty()) {
+        for (uint32_t outIndex = 0; ! idBits.isEmpty(); outIndex++) {
             uint32_t id = idBits.firstMarkedBit();
             idBits.clearBit(id);
-            uint32_t index = touch->idToIndex[id];
+            uint32_t inIndex = touch->idToIndex[id];
 
-            float x = float(touch->pointers[index].x - mLocked.xOrigin) * mLocked.xScale;
-            float y = float(touch->pointers[index].y - mLocked.yOrigin) * mLocked.yScale;
-            float pressure = float(touch->pointers[index].pressure - mLocked.pressureOrigin)
-                    * mLocked.pressureScale;
-            float size = float(touch->pointers[index].size - mLocked.sizeOrigin)
-                    * mLocked.sizeScale;
+            const PointerData& in = touch->pointers[inIndex];
 
-            float orientation = float(touch->pointers[index].orientation)
-                    * mLocked.orientationScale;
+            // X and Y
+            float x = float(in.x - mLocked.xOrigin) * mLocked.xScale;
+            float y = float(in.y - mLocked.yOrigin) * mLocked.yScale;
 
-            float touchMajor, touchMinor, toolMajor, toolMinor;
-            if (abs(orientation) <= M_PI_4) {
-                // Nominally vertical orientation: scale major axis by Y, and scale minor axis by X.
-                touchMajor = float(touch->pointers[index].touchMajor) * mLocked.yScale;
-                touchMinor = float(touch->pointers[index].touchMinor) * mLocked.xScale;
-                toolMajor = float(touch->pointers[index].toolMajor) * mLocked.yScale;
-                toolMinor = float(touch->pointers[index].toolMinor) * mLocked.xScale;
-            } else {
-                // Nominally horizontal orientation: scale major axis by X, and scale minor axis by Y.
-                touchMajor = float(touch->pointers[index].touchMajor) * mLocked.xScale;
-                touchMinor = float(touch->pointers[index].touchMinor) * mLocked.yScale;
-                toolMajor = float(touch->pointers[index].toolMajor) * mLocked.xScale;
-                toolMinor = float(touch->pointers[index].toolMinor) * mLocked.yScale;
+            // ToolMajor and ToolMinor
+            float toolMajor, toolMinor;
+            switch (mCalibration.toolAreaCalibration) {
+            case Calibration::TOOL_AREA_CALIBRATION_GEOMETRIC:
+                toolMajor = in.toolMajor * mLocked.geometricScale;
+                if (mRawAxes.toolMinor.valid) {
+                    toolMinor = in.toolMinor * mLocked.geometricScale;
+                } else {
+                    toolMinor = toolMajor;
+                }
+                break;
+            case Calibration::TOOL_AREA_CALIBRATION_LINEAR:
+                toolMajor = in.toolMajor != 0
+                        ? in.toolMajor * mLocked.toolAreaLinearScale + mLocked.toolAreaLinearBias
+                        : 0;
+                if (mRawAxes.toolMinor.valid) {
+                    toolMinor = in.toolMinor != 0
+                            ? in.toolMinor * mLocked.toolAreaLinearScale
+                                    + mLocked.toolAreaLinearBias
+                            : 0;
+                } else {
+                    toolMinor = toolMajor;
+                }
+                break;
+            default:
+                toolMajor = 0;
+                toolMinor = 0;
+                break;
             }
 
+            if (mCalibration.haveToolAreaIsSummed && mCalibration.toolAreaIsSummed) {
+                toolMajor /= pointerCount;
+                toolMinor /= pointerCount;
+            }
+
+            // Pressure
+            float rawPressure;
+            switch (mCalibration.pressureSource) {
+            case Calibration::PRESSURE_SOURCE_PRESSURE:
+                rawPressure = in.pressure;
+                break;
+            case Calibration::PRESSURE_SOURCE_TOUCH:
+                rawPressure = in.touchMajor;
+                break;
+            default:
+                rawPressure = 0;
+            }
+
+            float pressure;
+            switch (mCalibration.pressureCalibration) {
+            case Calibration::PRESSURE_CALIBRATION_PHYSICAL:
+            case Calibration::PRESSURE_CALIBRATION_AMPLITUDE:
+                pressure = rawPressure * mLocked.pressureScale;
+                break;
+            default:
+                pressure = 1;
+                break;
+            }
+
+            // TouchMajor and TouchMinor
+            float touchMajor, touchMinor;
+            switch (mCalibration.touchAreaCalibration) {
+            case Calibration::TOUCH_AREA_CALIBRATION_GEOMETRIC:
+                touchMajor = in.touchMajor * mLocked.geometricScale;
+                if (mRawAxes.touchMinor.valid) {
+                    touchMinor = in.touchMinor * mLocked.geometricScale;
+                } else {
+                    touchMinor = touchMajor;
+                }
+                break;
+            case Calibration::TOUCH_AREA_CALIBRATION_PRESSURE:
+                touchMajor = toolMajor * pressure;
+                touchMinor = toolMinor * pressure;
+                break;
+            default:
+                touchMajor = 0;
+                touchMinor = 0;
+                break;
+            }
+
+            if (touchMajor > toolMajor) {
+                touchMajor = toolMajor;
+            }
+            if (touchMinor > toolMinor) {
+                touchMinor = toolMinor;
+            }
+
+            // Size
+            float size;
+            switch (mCalibration.sizeCalibration) {
+            case Calibration::SIZE_CALIBRATION_NORMALIZED: {
+                float rawSize = mRawAxes.toolMinor.valid
+                        ? avg(in.toolMajor, in.toolMinor)
+                        : in.toolMajor;
+                size = rawSize * mLocked.sizeScale;
+                break;
+            }
+            default:
+                size = 0;
+                break;
+            }
+
+            // Orientation
+            float orientation;
+            switch (mCalibration.orientationCalibration) {
+            case Calibration::ORIENTATION_CALIBRATION_INTERPOLATED:
+                orientation = in.orientation * mLocked.orientationScale;
+                break;
+            default:
+                orientation = 0;
+            }
+
+            // Adjust coords for orientation.
             switch (mLocked.surfaceOrientation) {
             case InputReaderPolicyInterface::ROTATION_90: {
                 float xTemp = x;
@@ -1702,23 +2285,23 @@
             }
             }
 
-            pointerIds[pointerCount] = int32_t(id);
+            // Write output coords.
+            PointerCoords& out = pointerCoords[outIndex];
+            out.x = x;
+            out.y = y;
+            out.pressure = pressure;
+            out.size = size;
+            out.touchMajor = touchMajor;
+            out.touchMinor = touchMinor;
+            out.toolMajor = toolMajor;
+            out.toolMinor = toolMinor;
+            out.orientation = orientation;
 
-            pointerCoords[pointerCount].x = x;
-            pointerCoords[pointerCount].y = y;
-            pointerCoords[pointerCount].pressure = pressure;
-            pointerCoords[pointerCount].size = size;
-            pointerCoords[pointerCount].touchMajor = touchMajor;
-            pointerCoords[pointerCount].touchMinor = touchMinor;
-            pointerCoords[pointerCount].toolMajor = toolMajor;
-            pointerCoords[pointerCount].toolMinor = toolMinor;
-            pointerCoords[pointerCount].orientation = orientation;
+            pointerIds[outIndex] = int32_t(id);
 
             if (id == changedId) {
-                motionEventAction |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+                motionEventAction |= outIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
             }
-
-            pointerCount += 1;
         }
 
         // Check edge flags by looking only at the first pointer since the flags are
@@ -1747,9 +2330,9 @@
 }
 
 bool TouchInputMapper::isPointInsideSurfaceLocked(int32_t x, int32_t y) {
-    if (mAxes.x.valid && mAxes.y.valid) {
-        return x >= mAxes.x.minValue && x <= mAxes.x.maxValue
-                && y >= mAxes.y.minValue && y <= mAxes.y.maxValue;
+    if (mRawAxes.x.valid && mRawAxes.y.valid) {
+        return x >= mRawAxes.x.minValue && x <= mRawAxes.x.maxValue
+                && y >= mRawAxes.y.minValue && y <= mRawAxes.y.maxValue;
     }
     return true;
 }
@@ -1960,7 +2543,7 @@
  * then drop it. */
 bool TouchInputMapper::applyBadTouchFilter() {
     // This hack requires valid axis parameters.
-    if (! mAxes.y.valid) {
+    if (! mRawAxes.y.valid) {
         return false;
     }
 
@@ -1982,7 +2565,7 @@
     // the long size of the screen to be bad.  This was a magic value
     // determined by looking at the maximum distance it is feasible
     // to actually move in one sample.
-    int32_t maxDeltaY = mAxes.y.getRange() * 7 / 16;
+    int32_t maxDeltaY = mRawAxes.y.getRange() * 7 / 16;
 
     // XXX The original code in InputDevice.java included commented out
     //     code for testing the X axis.  Note that when we drop a point
@@ -2044,7 +2627,7 @@
  */
 bool TouchInputMapper::applyJumpyTouchFilter() {
     // This hack requires valid axis parameters.
-    if (! mAxes.y.valid) {
+    if (! mRawAxes.y.valid) {
         return false;
     }
 
@@ -2104,7 +2687,7 @@
     }
 
     if (mJumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) {
-        int jumpyEpsilon = mAxes.y.getRange() / JUMPY_EPSILON_DIVISOR;
+        int jumpyEpsilon = mRawAxes.y.getRange() / JUMPY_EPSILON_DIVISOR;
 
         // We only replace the single worst jumpy point as characterized by pointer distance
         // in a single axis.
@@ -2209,7 +2792,18 @@
         uint32_t id = mCurrentTouch.pointers[currentIndex].id;
         int32_t x = mCurrentTouch.pointers[currentIndex].x;
         int32_t y = mCurrentTouch.pointers[currentIndex].y;
-        int32_t pressure = mCurrentTouch.pointers[currentIndex].pressure;
+        int32_t pressure;
+        switch (mCalibration.pressureSource) {
+        case Calibration::PRESSURE_SOURCE_PRESSURE:
+            pressure = mCurrentTouch.pointers[currentIndex].pressure;
+            break;
+        case Calibration::PRESSURE_SOURCE_TOUCH:
+            pressure = mCurrentTouch.pointers[currentIndex].touchMajor;
+            break;
+        default:
+            pressure = 1;
+            break;
+        }
 
         if (mLastTouch.idBits.hasBit(id)) {
             // Pointer was down before and is still down now.
@@ -2274,17 +2868,19 @@
                     }
                 }
 
-                averagedX /= totalPressure;
-                averagedY /= totalPressure;
+                if (totalPressure != 0) {
+                    averagedX /= totalPressure;
+                    averagedY /= totalPressure;
 
 #if DEBUG_HACKS
-                LOGD("AveragingTouchFilter: Pointer id %d - "
-                        "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure,
-                        averagedX, averagedY);
+                    LOGD("AveragingTouchFilter: Pointer id %d - "
+                            "totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure,
+                            averagedX, averagedY);
 #endif
 
-                mCurrentTouch.pointers[currentIndex].x = averagedX;
-                mCurrentTouch.pointers[currentIndex].y = averagedY;
+                    mCurrentTouch.pointers[currentIndex].x = averagedX;
+                    mCurrentTouch.pointers[currentIndex].y = averagedY;
+                }
             } else {
 #if DEBUG_HACKS
                 LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id);
@@ -2382,8 +2978,8 @@
     mDown = false;
     mX = 0;
     mY = 0;
-    mPressure = 1; // default to 1 for devices that don't report pressure
-    mSize = 0; // default to 0 for devices that don't report size
+    mPressure = 0; // default to 0 for devices that don't report pressure
+    mToolWidth = 0; // default to 0 for devices that don't report tool width
 }
 
 void SingleTouchInputMapper::reset() {
@@ -2460,7 +3056,7 @@
     }
 
     if (fields & Accumulator::FIELD_ABS_TOOL_WIDTH) {
-        mSize = mAccumulator.absToolWidth;
+        mToolWidth = mAccumulator.absToolWidth;
     }
 
     mCurrentTouch.clear();
@@ -2471,11 +3067,10 @@
         mCurrentTouch.pointers[0].x = mX;
         mCurrentTouch.pointers[0].y = mY;
         mCurrentTouch.pointers[0].pressure = mPressure;
-        mCurrentTouch.pointers[0].size = mSize;
-        mCurrentTouch.pointers[0].touchMajor = mSize;
-        mCurrentTouch.pointers[0].touchMinor = mSize;
-        mCurrentTouch.pointers[0].toolMajor = mSize;
-        mCurrentTouch.pointers[0].toolMinor = mSize;
+        mCurrentTouch.pointers[0].touchMajor = 0;
+        mCurrentTouch.pointers[0].touchMinor = 0;
+        mCurrentTouch.pointers[0].toolMajor = mToolWidth;
+        mCurrentTouch.pointers[0].toolMinor = mToolWidth;
         mCurrentTouch.pointers[0].orientation = 0;
         mCurrentTouch.idToIndex[0] = 0;
         mCurrentTouch.idBits.markBit(0);
@@ -2486,20 +3081,13 @@
     mAccumulator.clear();
 }
 
-void SingleTouchInputMapper::configureAxes() {
-    TouchInputMapper::configureAxes();
+void SingleTouchInputMapper::configureRawAxes() {
+    TouchInputMapper::configureRawAxes();
 
-    // The axes are aliased to take into account the manner in which they are presented
-    // as part of the TouchData during the sync.
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mAxes.x);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mAxes.y);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_PRESSURE, & mAxes.pressure);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_TOOL_WIDTH, & mAxes.size);
-
-    mAxes.touchMajor = mAxes.size;
-    mAxes.touchMinor = mAxes.size;
-    mAxes.toolMajor = mAxes.size;
-    mAxes.toolMinor = mAxes.size;
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_X, & mRawAxes.x);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_Y, & mRawAxes.y);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_PRESSURE, & mRawAxes.pressure);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_TOOL_WIDTH, & mRawAxes.toolMajor);
 }
 
 
@@ -2562,6 +3150,10 @@
             pointer->fields |= Accumulator::FIELD_ABS_MT_TRACKING_ID;
             pointer->absMTTrackingId = rawEvent->value;
             break;
+        case ABS_MT_PRESSURE:
+            pointer->fields |= Accumulator::FIELD_ABS_MT_PRESSURE;
+            pointer->absMTPressure = rawEvent->value;
+            break;
         }
         break;
     }
@@ -2596,8 +3188,7 @@
 
 void MultiTouchInputMapper::sync(nsecs_t when) {
     static const uint32_t REQUIRED_FIELDS =
-            Accumulator::FIELD_ABS_MT_POSITION_X
-            | Accumulator::FIELD_ABS_MT_POSITION_Y;
+            Accumulator::FIELD_ABS_MT_POSITION_X | Accumulator::FIELD_ABS_MT_POSITION_Y;
 
     uint32_t inCount = mAccumulator.pointerCount;
     uint32_t outCount = 0;
@@ -2619,60 +3210,59 @@
         outPointer.x = inPointer.absMTPositionX;
         outPointer.y = inPointer.absMTPositionY;
 
-        if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MAJOR) {
-            int32_t value = inPointer.absMTTouchMajor;
-            if (value <= 0) {
-                // Some devices send sync packets with X / Y but with a 0 touch major to indicate
+        if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) {
+            if (inPointer.absMTPressure <= 0) {
+                // Some devices send sync packets with X / Y but with a 0 presure to indicate
                 // a pointer up.  Drop this finger.
                 continue;
             }
+            outPointer.pressure = inPointer.absMTPressure;
+        } else {
+            // Default pressure to 0 if absent.
+            outPointer.pressure = 0;
+        }
+
+        if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MAJOR) {
+            if (inPointer.absMTTouchMajor <= 0) {
+                // Some devices send sync packets with X / Y but with a 0 touch major to indicate
+                // a pointer going up.  Drop this finger.
+                continue;
+            }
             outPointer.touchMajor = inPointer.absMTTouchMajor;
         } else {
+            // Default touch area to 0 if absent.
             outPointer.touchMajor = 0;
         }
 
         if (fields & Accumulator::FIELD_ABS_MT_TOUCH_MINOR) {
             outPointer.touchMinor = inPointer.absMTTouchMinor;
         } else {
+            // Assume touch area is circular.
             outPointer.touchMinor = outPointer.touchMajor;
         }
 
         if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MAJOR) {
             outPointer.toolMajor = inPointer.absMTWidthMajor;
         } else {
-            outPointer.toolMajor = outPointer.touchMajor;
+            // Default tool area to 0 if absent.
+            outPointer.toolMajor = 0;
         }
 
         if (fields & Accumulator::FIELD_ABS_MT_WIDTH_MINOR) {
             outPointer.toolMinor = inPointer.absMTWidthMinor;
         } else {
+            // Assume tool area is circular.
             outPointer.toolMinor = outPointer.toolMajor;
         }
 
         if (fields & Accumulator::FIELD_ABS_MT_ORIENTATION) {
             outPointer.orientation = inPointer.absMTOrientation;
         } else {
+            // Default orientation to vertical if absent.
             outPointer.orientation = 0;
         }
 
-        if (fields & Accumulator::FIELD_ABS_MT_PRESSURE) {
-            outPointer.pressure = inPointer.absMTPressure;
-        } else {
-            // Derive an approximation of pressure.
-            // FIXME Traditionally we have just passed a normalized value based on
-            //       ABS_MT_TOUCH_MAJOR as an estimate of pressure but the result is not
-            //       very meaningful, particularly on large displays.  We should probably let
-            //       pressure = touch_major / tool_major but it is unclear whether that will
-            //       break applications.
-            outPointer.pressure = outPointer.touchMajor;
-        }
-
-        // Size is an alias for a normalized tool width.
-        // FIXME Normalized tool width doesn't actually make much sense since it literally
-        //       means the approaching contact major axis is divided by its full range as
-        //       reported by the driver.  On a large display this could produce very small values.
-        outPointer.size = outPointer.toolMajor;
-
+        // Assign pointer id using tracking id if available.
         if (havePointerIds) {
             if (fields & Accumulator::FIELD_ABS_MT_TRACKING_ID) {
                 uint32_t id = uint32_t(inPointer.absMTTrackingId);
@@ -2705,33 +3295,17 @@
     mAccumulator.clear();
 }
 
-void MultiTouchInputMapper::configureAxes() {
-    TouchInputMapper::configureAxes();
+void MultiTouchInputMapper::configureRawAxes() {
+    TouchInputMapper::configureRawAxes();
 
-    // The axes are aliased to take into account the manner in which they are presented
-    // as part of the TouchData during the sync.
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, & mAxes.x);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, & mAxes.y);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, & mAxes.touchMajor);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, & mAxes.touchMinor);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, & mAxes.toolMajor);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, & mAxes.toolMinor);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, & mAxes.orientation);
-    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_PRESSURE, & mAxes.pressure);
-
-    if (! mAxes.touchMinor.valid) {
-        mAxes.touchMinor = mAxes.touchMajor;
-    }
-
-    if (! mAxes.toolMinor.valid) {
-        mAxes.toolMinor = mAxes.toolMajor;
-    }
-
-    if (! mAxes.pressure.valid) {
-        mAxes.pressure = mAxes.touchMajor;
-    }
-
-    mAxes.size = mAxes.toolMajor;
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_X, & mRawAxes.x);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_POSITION_Y, & mRawAxes.y);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MAJOR, & mRawAxes.touchMajor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_TOUCH_MINOR, & mRawAxes.touchMinor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MAJOR, & mRawAxes.toolMajor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_WIDTH_MINOR, & mRawAxes.toolMinor);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_ORIENTATION, & mRawAxes.orientation);
+    getEventHub()->getAbsoluteAxisInfo(getDeviceId(), ABS_MT_PRESSURE, & mRawAxes.pressure);
 }
 
 
diff --git a/native/include/android/input.h b/native/include/android/input.h
index 243c33c..d9486e5 100644
--- a/native/include/android/input.h
+++ b/native/include/android/input.h
@@ -718,8 +718,6 @@
 int32_t AInputDevice_getMotionRange(AInputDevice* device, int32_t rangeType,
         float* outMin, float* outMax, float* outFlat, float* outFuzz);
 
-//TODO hasKey, keymap stuff, etc...
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/services/java/com/android/server/InputManager.java b/services/java/com/android/server/InputManager.java
index f330d40..314dd8a 100644
--- a/services/java/com/android/server/InputManager.java
+++ b/services/java/com/android/server/InputManager.java
@@ -30,6 +30,7 @@
 import android.util.Slog;
 import android.util.Xml;
 import android.view.InputChannel;
+import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -45,6 +46,7 @@
 import java.io.InputStreamReader;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Properties;
 
 /*
  * Wraps the C++ InputManager and provides its callbacks.
@@ -82,6 +84,8 @@
     private static native void nativeSetInputDispatchMode(boolean enabled, boolean frozen);
     private static native void nativeSetFocusedApplication(InputApplication application);
     private static native void nativePreemptInputDispatch();
+    private static native InputDevice nativeGetInputDevice(int deviceId);
+    private static native int[] nativeGetInputDeviceIds();
     private static native String nativeDump();
     
     // Input event injection constants defined in InputDispatcher.h.
@@ -302,6 +306,23 @@
         return nativeInjectInputEvent(event, injectorPid, injectorUid, syncMode, timeoutMillis);
     }
     
+    /**
+     * Gets information about the input device with the specified id.
+     * @param id The device id.
+     * @return The input device or null if not found.
+     */
+    public InputDevice getInputDevice(int deviceId) {
+        return nativeGetInputDevice(deviceId);
+    }
+    
+    /**
+     * Gets the ids of all input devices in the system.
+     * @return The input device ids.
+     */
+    public int[] getInputDeviceIds() {
+        return nativeGetInputDeviceIds();
+    }
+    
     public void setInputWindows(InputWindow[] windows) {
         nativeSetInputWindows(windows);
     }
@@ -335,6 +356,11 @@
         public int height;
     }
     
+    private static final class InputDeviceCalibration {
+        public String[] keys;
+        public String[] values;
+    }
+    
     /*
      * Callbacks from native.
      */
@@ -343,6 +369,7 @@
         
         private static final boolean DEBUG_VIRTUAL_KEYS = false;
         private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
+        private static final String CALIBRATION_DIR_PATH = "usr/idc/";
         
         @SuppressWarnings("unused")
         public void virtualKeyDownFeedback() {
@@ -438,8 +465,8 @@
                     final int N = it.length-6;
                     for (int i=0; i<=N; i+=6) {
                         if (!"0x01".equals(it[i])) {
-                            Slog.w(TAG, "Unknown virtual key type at elem #" + i
-                                    + ": " + it[i]);
+                            Slog.w(TAG, "Unknown virtual key type at elem #"
+                                    + i + ": " + it[i] + " for device " + deviceName);
                             continue;
                         }
                         try {
@@ -455,22 +482,47 @@
                                     + key.height);
                             keys.add(key);
                         } catch (NumberFormatException e) {
-                            Slog.w(TAG, "Bad number at region " + i + " in: "
-                                    + str, e);
+                            Slog.w(TAG, "Bad number in virtual key definition at region "
+                                    + i + " in: " + str + " for device " + deviceName, e);
                         }
                     }
                 }
                 br.close();
             } catch (FileNotFoundException e) {
-                Slog.i(TAG, "No virtual keys found");
+                Slog.i(TAG, "No virtual keys found for device " + deviceName + ".");
             } catch (IOException e) {
-                Slog.w(TAG, "Error reading virtual keys", e);
+                Slog.w(TAG, "Error reading virtual keys for device " + deviceName + ".", e);
             }
             
             return keys.toArray(new VirtualKeyDefinition[keys.size()]);
         }
         
         @SuppressWarnings("unused")
+        public InputDeviceCalibration getInputDeviceCalibration(String deviceName) {
+            // Calibration is specified as a sequence of colon-delimited key value pairs.
+            Properties properties = new Properties();
+            File calibrationFile = new File(Environment.getRootDirectory(),
+                    CALIBRATION_DIR_PATH + deviceName + ".idc");
+            if (calibrationFile.exists()) {
+                try {
+                    properties.load(new FileInputStream(calibrationFile));
+                } catch (IOException ex) {
+                    Slog.w(TAG, "Error reading input device calibration properties for device "
+                            + deviceName + " from " + calibrationFile + ".", ex);
+                }
+            } else {
+                Slog.i(TAG, "No input device calibration properties found for device "
+                        + deviceName + ".");
+                return null;
+            }
+            
+            InputDeviceCalibration calibration = new InputDeviceCalibration();
+            calibration.keys = properties.keySet().toArray(new String[properties.size()]);
+            calibration.values = properties.values().toArray(new String[properties.size()]);
+            return calibration;
+        }
+        
+        @SuppressWarnings("unused")
         public String[] getExcludedDeviceNames() {
             ArrayList<String> names = new ArrayList<String>();
             
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 9b9d950..3841f75 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -4392,6 +4392,14 @@
         return mInputManager.monitorInput(inputChannelName);
     }
 
+    public InputDevice getInputDevice(int deviceId) {
+        return mInputManager.getInputDevice(deviceId);
+    }
+
+    public int[] getInputDeviceIds() {
+        return mInputManager.getInputDeviceIds();
+    }
+
     public void enableScreenAfterBoot() {
         synchronized(mWindowMap) {
             if (mSystemBooted) {
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index 3addc0d..a237ee9 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -138,6 +138,7 @@
     jmethodID filterTouchEvents;
     jmethodID filterJumpyTouchEvents;
     jmethodID getVirtualKeyDefinitions;
+    jmethodID getInputDeviceCalibration;
     jmethodID getExcludedDeviceNames;
     jmethodID getMaxEventsPerSecond;
 } gCallbacksClassInfo;
@@ -155,6 +156,13 @@
 static struct {
     jclass clazz;
 
+    jfieldID keys;
+    jfieldID values;
+} gInputDeviceCalibrationClassInfo;
+
+static struct {
+    jclass clazz;
+
     jfieldID inputChannel;
     jfieldID layoutParamsFlags;
     jfieldID layoutParamsType;
@@ -189,6 +197,19 @@
     jclass clazz;
 } gMotionEventClassInfo;
 
+static struct {
+    jclass clazz;
+
+    jmethodID ctor;
+    jmethodID addMotionRange;
+
+    jfieldID mId;
+    jfieldID mName;
+    jfieldID mSources;
+    jfieldID mKeyboardType;
+    jfieldID mMotionRanges;
+} gInputDeviceClassInfo;
+
 // ----------------------------------------------------------------------------
 
 static inline nsecs_t now() {
@@ -235,7 +256,9 @@
     virtual bool filterTouchEvents();
     virtual bool filterJumpyTouchEvents();
     virtual void getVirtualKeyDefinitions(const String8& deviceName,
-            Vector<InputReaderPolicyInterface::VirtualKeyDefinition>& outVirtualKeyDefinitions);
+            Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions);
+    virtual void getInputDeviceCalibration(const String8& deviceName,
+            InputDeviceCalibration& outCalibration);
     virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
 
     /* --- InputDispatcherPolicyInterface implementation --- */
@@ -761,7 +784,9 @@
 }
 
 void NativeInputManager::getVirtualKeyDefinitions(const String8& deviceName,
-        Vector<InputReaderPolicyInterface::VirtualKeyDefinition>& outVirtualKeyDefinitions) {
+        Vector<VirtualKeyDefinition>& outVirtualKeyDefinitions) {
+    outVirtualKeyDefinitions.clear();
+
     JNIEnv* env = jniEnv();
 
     jstring deviceNameStr = env->NewStringUTF(deviceName.string());
@@ -793,7 +818,51 @@
     }
 }
 
+void NativeInputManager::getInputDeviceCalibration(const String8& deviceName,
+        InputDeviceCalibration& outCalibration) {
+    outCalibration.clear();
+
+    JNIEnv* env = jniEnv();
+
+    jstring deviceNameStr = env->NewStringUTF(deviceName.string());
+    if (! checkAndClearExceptionFromCallback(env, "getInputDeviceCalibration")) {
+        jobject result = env->CallObjectMethod(mCallbacksObj,
+                gCallbacksClassInfo.getInputDeviceCalibration, deviceNameStr);
+        if (! checkAndClearExceptionFromCallback(env, "getInputDeviceCalibration") && result) {
+            jobjectArray keys = jobjectArray(env->GetObjectField(result,
+                    gInputDeviceCalibrationClassInfo.keys));
+            jobjectArray values = jobjectArray(env->GetObjectField(result,
+                    gInputDeviceCalibrationClassInfo.values));
+
+            jsize length = env->GetArrayLength(keys);
+            for (jsize i = 0; i < length; i++) {
+                jstring keyStr = jstring(env->GetObjectArrayElement(keys, i));
+                jstring valueStr = jstring(env->GetObjectArrayElement(values, i));
+
+                const char* keyChars = env->GetStringUTFChars(keyStr, NULL);
+                String8 key(keyChars);
+                env->ReleaseStringUTFChars(keyStr, keyChars);
+
+                const char* valueChars = env->GetStringUTFChars(valueStr, NULL);
+                String8 value(valueChars);
+                env->ReleaseStringUTFChars(valueStr, valueChars);
+
+                outCalibration.addProperty(key, value);
+
+                env->DeleteLocalRef(keyStr);
+                env->DeleteLocalRef(valueStr);
+            }
+            env->DeleteLocalRef(keys);
+            env->DeleteLocalRef(values);
+            env->DeleteLocalRef(result);
+        }
+        env->DeleteLocalRef(deviceNameStr);
+    }
+}
+
 void NativeInputManager::getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames) {
+    outExcludedDeviceNames.clear();
+
     JNIEnv* env = jniEnv();
 
     jobjectArray result = jobjectArray(env->CallObjectMethod(mCallbacksObj,
@@ -2199,6 +2268,66 @@
     gNativeInputManager->preemptInputDispatch();
 }
 
+static jobject android_server_InputManager_nativeGetInputDevice(JNIEnv* env,
+        jclass clazz, jint deviceId) {
+    if (checkInputManagerUnitialized(env)) {
+        return NULL;
+    }
+
+    InputDeviceInfo deviceInfo;
+    status_t status = gNativeInputManager->getInputManager()->getInputDeviceInfo(
+            deviceId, & deviceInfo);
+    if (status) {
+        return NULL;
+    }
+
+    jobject deviceObj = env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor);
+    if (! deviceObj) {
+        return NULL;
+    }
+
+    jstring deviceNameObj = env->NewStringUTF(deviceInfo.getName().string());
+    if (! deviceNameObj) {
+        return NULL;
+    }
+
+    env->SetIntField(deviceObj, gInputDeviceClassInfo.mId, deviceInfo.getId());
+    env->SetObjectField(deviceObj, gInputDeviceClassInfo.mName, deviceNameObj);
+    env->SetIntField(deviceObj, gInputDeviceClassInfo.mSources, deviceInfo.getSources());
+    env->SetIntField(deviceObj, gInputDeviceClassInfo.mKeyboardType, deviceInfo.getKeyboardType());
+
+    const KeyedVector<int, InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
+    for (size_t i = 0; i < ranges.size(); i++) {
+        int rangeType = ranges.keyAt(i);
+        const InputDeviceInfo::MotionRange& range = ranges.valueAt(i);
+        env->CallVoidMethod(deviceObj, gInputDeviceClassInfo.addMotionRange,
+                rangeType, range.min, range.max, range.flat, range.fuzz);
+        if (env->ExceptionCheck()) {
+            return NULL;
+        }
+    }
+
+    return deviceObj;
+}
+
+static jintArray android_server_InputManager_nativeGetInputDeviceIds(JNIEnv* env,
+        jclass clazz) {
+    if (checkInputManagerUnitialized(env)) {
+        return NULL;
+    }
+
+    Vector<int> deviceIds;
+    gNativeInputManager->getInputManager()->getInputDeviceIds(deviceIds);
+
+    jintArray deviceIdsObj = env->NewIntArray(deviceIds.size());
+    if (! deviceIdsObj) {
+        return NULL;
+    }
+
+    env->SetIntArrayRegion(deviceIdsObj, 0, deviceIds.size(), deviceIds.array());
+    return deviceIdsObj;
+}
+
 static jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) {
     if (checkInputManagerUnitialized(env)) {
         return NULL;
@@ -2242,6 +2371,10 @@
             (void*) android_server_InputManager_nativeSetInputDispatchMode },
     { "nativePreemptInputDispatch", "()V",
             (void*) android_server_InputManager_nativePreemptInputDispatch },
+    { "nativeGetInputDevice", "(I)Landroid/view/InputDevice;",
+            (void*) android_server_InputManager_nativeGetInputDevice },
+    { "nativeGetInputDeviceIds", "()[I",
+            (void*) android_server_InputManager_nativeGetInputDeviceIds },
     { "nativeDump", "()Ljava/lang/String;",
             (void*) android_server_InputManager_nativeDump },
 };
@@ -2311,6 +2444,10 @@
             "getVirtualKeyDefinitions",
             "(Ljava/lang/String;)[Lcom/android/server/InputManager$VirtualKeyDefinition;");
 
+    GET_METHOD_ID(gCallbacksClassInfo.getInputDeviceCalibration, gCallbacksClassInfo.clazz,
+            "getInputDeviceCalibration",
+            "(Ljava/lang/String;)Lcom/android/server/InputManager$InputDeviceCalibration;");
+
     GET_METHOD_ID(gCallbacksClassInfo.getExcludedDeviceNames, gCallbacksClassInfo.clazz,
             "getExcludedDeviceNames", "()[Ljava/lang/String;");
 
@@ -2337,6 +2474,17 @@
     GET_FIELD_ID(gVirtualKeyDefinitionClassInfo.height, gVirtualKeyDefinitionClassInfo.clazz,
             "height", "I");
 
+    // InputDeviceCalibration
+
+    FIND_CLASS(gInputDeviceCalibrationClassInfo.clazz,
+            "com/android/server/InputManager$InputDeviceCalibration");
+
+    GET_FIELD_ID(gInputDeviceCalibrationClassInfo.keys, gInputDeviceCalibrationClassInfo.clazz,
+            "keys", "[Ljava/lang/String;");
+
+    GET_FIELD_ID(gInputDeviceCalibrationClassInfo.values, gInputDeviceCalibrationClassInfo.clazz,
+            "values", "[Ljava/lang/String;");
+
     // InputWindow
 
     FIND_CLASS(gInputWindowClassInfo.clazz, "com/android/server/InputWindow");
@@ -2407,10 +2555,35 @@
 
     FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");
 
-    // MotionEVent
+    // MotionEvent
 
     FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent");
 
+    // InputDevice
+
+    FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
+
+    GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
+            "<init>", "()V");
+
+    GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
+            "addMotionRange", "(IFFFF)V");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mId, gInputDeviceClassInfo.clazz,
+            "mId", "I");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mName, gInputDeviceClassInfo.clazz,
+            "mName", "Ljava/lang/String;");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mSources, gInputDeviceClassInfo.clazz,
+            "mSources", "I");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mKeyboardType, gInputDeviceClassInfo.clazz,
+            "mKeyboardType", "I");
+
+    GET_FIELD_ID(gInputDeviceClassInfo.mMotionRanges, gInputDeviceClassInfo.clazz,
+            "mMotionRanges", "[Landroid/view/InputDevice$MotionRange;");
+
     return 0;
 }