am 36f0cb26: am 8d60866e: Input device calibration and capabilities.

Merge commit '36f0cb26cbb4ef62995ff2e5a540cf8814e7f030'

* commit '36f0cb26cbb4ef62995ff2e5a540cf8814e7f030':
  Input device calibration and capabilities.
diff --git a/api/current.xml b/api/current.xml
index fc0a8cf..8549080 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -186729,14 +186729,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"
@@ -186750,6 +186755,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"
@@ -186782,7 +186809,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="range" type="int">
+<parameter name="rangeType" type="int">
 </parameter>
 </method>
 <method name="getName"
@@ -186807,8 +186834,8 @@
  visibility="public"
 >
 </method>
-<method name="hasKey"
- return="boolean"
+<method name="writeToParcel"
+ return="void"
  abstract="false"
  native="false"
  synchronized="false"
@@ -186817,9 +186844,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"
@@ -187148,14 +187187,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..7a089a4 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 4407e96..79bde7c 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -4383,6 +4383,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;
 }