Merge change 20126

* changes:
  Removing SyncColumns from Contact - they were put there by accident.
diff --git a/api/current.xml b/api/current.xml
index f65b5ce..c4cac83 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -145940,6 +145940,19 @@
  visibility="public"
 >
 </method>
+<method name="findPointerIndex"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pointerId" type="int">
+</parameter>
+</method>
 <method name="getAction"
  return="int"
  abstract="false"
@@ -146031,7 +146044,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 <parameter name="pos" type="int">
 </parameter>
@@ -146059,7 +146072,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 <parameter name="pos" type="int">
 </parameter>
@@ -146087,7 +146100,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 <parameter name="pos" type="int">
 </parameter>
@@ -146115,7 +146128,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 <parameter name="pos" type="int">
 </parameter>
@@ -146153,6 +146166,19 @@
  visibility="public"
 >
 </method>
+<method name="getPointerId"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="index" type="int">
+</parameter>
+</method>
 <method name="getPressure"
  return="float"
  abstract="false"
@@ -146174,7 +146200,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 </method>
 <method name="getRawX"
@@ -146220,7 +146246,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 </method>
 <method name="getX"
@@ -146244,7 +146270,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 </method>
 <method name="getXPrecision"
@@ -146279,7 +146305,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="pointer" type="int">
+<parameter name="pointerIndex" type="int">
 </parameter>
 </method>
 <method name="getYPrecision"
@@ -146615,7 +146641,7 @@
  visibility="public"
 >
 </field>
-<field name="ACTION_POINTER_MASK"
+<field name="ACTION_POINTER_ID_MASK"
  type="int"
  transient="false"
  volatile="false"
@@ -146626,7 +146652,7 @@
  visibility="public"
 >
 </field>
-<field name="ACTION_POINTER_SHIFT"
+<field name="ACTION_POINTER_ID_SHIFT"
  type="int"
  transient="false"
  volatile="false"
diff --git a/cmds/stagefright/record.cpp b/cmds/stagefright/record.cpp
index d8db8b3..cd54958 100644
--- a/cmds/stagefright/record.cpp
+++ b/cmds/stagefright/record.cpp
@@ -114,8 +114,8 @@
     assert(success);
 
     sp<MetaData> enc_meta = new MetaData;
-    // enc_meta->setCString(kKeyMIMEType, "video/3gpp");
-    enc_meta->setCString(kKeyMIMEType, "video/mp4v-es");
+    enc_meta->setCString(kKeyMIMEType, "video/3gpp");
+    // enc_meta->setCString(kKeyMIMEType, "video/mp4v-es");
     enc_meta->setInt32(kKeyWidth, width);
     enc_meta->setInt32(kKeyHeight, height);
 
@@ -129,7 +129,8 @@
     MPEG4Writer writer("/sdcard/output.mp4");
     writer.addSource(enc_meta, encoder);
     writer.start();
-    sleep(120);
+    sleep(20);
+    printf("stopping now.\n");
     writer.stop();
 #else
     encoder->start();
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 1d53eab..70aceeb 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -693,39 +693,6 @@
         return mLaunchComponent.flattenToShortString().startsWith("com.android.browser/");
     }
 
-    /*
-     * Menu.
-     */
-
-    @Override
-    public boolean onCreateOptionsMenu(Menu menu) {
-        // Show search settings menu item if anyone handles the intent for it
-        Intent settingsIntent = new Intent(SearchManager.INTENT_ACTION_SEARCH_SETTINGS);
-        settingsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        PackageManager pm = getContext().getPackageManager();
-        ActivityInfo activityInfo = settingsIntent.resolveActivityInfo(pm, 0);
-        if (activityInfo != null) {
-            settingsIntent.setClassName(activityInfo.applicationInfo.packageName,
-                    activityInfo.name);
-            CharSequence label = activityInfo.loadLabel(getContext().getPackageManager());
-            menu.add(Menu.NONE, Menu.NONE, Menu.NONE, label)
-                    .setIcon(android.R.drawable.ic_menu_preferences)
-                    .setAlphabeticShortcut('P')
-                    .setIntent(settingsIntent);
-            return true;
-        }
-        return super.onCreateOptionsMenu(menu);
-    }
-
-    @Override
-    public boolean onMenuOpened(int featureId, Menu menu) {
-        // The menu shows up above the IME, regardless of whether it is in front
-        // of the drop-down or not. This looks weird when there is no IME, so
-        // we make sure it is visible.
-        mSearchAutoComplete.ensureImeVisible();
-        return super.onMenuOpened(featureId, menu);
-    }
-
     /**
      * Listeners of various types
      */
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
index 4a00e48..bd4e66e 100644
--- a/core/java/android/app/SuggestionsAdapter.java
+++ b/core/java/android/app/SuggestionsAdapter.java
@@ -658,7 +658,14 @@
         if (col == NONE) {
             return null;
         }
-        return cursor.getString(col);
+        try {
+            return cursor.getString(col);
+        } catch (Exception e) {
+            Log.e(LOG_TAG,
+                    "unexpected error retrieving valid column from cursor, "
+                            + "did the remote process die?", e);
+            return null;
+        }
     }
 
 }
diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java
index b95e4e1..92bc814 100644
--- a/core/java/android/provider/Browser.java
+++ b/core/java/android/provider/Browser.java
@@ -107,7 +107,8 @@
     public static final String[] HISTORY_PROJECTION = new String[] {
         BookmarkColumns._ID, BookmarkColumns.URL, BookmarkColumns.VISITS,
         BookmarkColumns.DATE, BookmarkColumns.BOOKMARK, BookmarkColumns.TITLE,
-        BookmarkColumns.FAVICON, BookmarkColumns.THUMBNAIL };
+        BookmarkColumns.FAVICON, BookmarkColumns.THUMBNAIL,
+        BookmarkColumns.TOUCH_ICON };
 
     /* these indices dependent on HISTORY_PROJECTION */
     public static final int HISTORY_PROJECTION_ID_INDEX = 0;
@@ -121,6 +122,10 @@
      * @hide
      */
     public static final int HISTORY_PROJECTION_THUMBNAIL_INDEX = 7;
+    /**
+     * @hide
+     */
+    public static final int HISTORY_PROJECTION_TOUCH_ICON_INDEX = 8;
 
     /* columns needed to determine whether to truncate history */
     public static final String[] TRUNCATE_HISTORY_PROJECTION = new String[] {
@@ -521,6 +526,10 @@
          * @hide
          */
         public static final String THUMBNAIL = "thumbnail";
+        /**
+         * @hide
+         */
+        public static final String TOUCH_ICON = "touch_icon";
     }
 
     public static class SearchColumns implements BaseColumns {
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 75bd989..489d936 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -217,6 +217,13 @@
          * <P>Type: INTEGER (boolean)</P>
          */
         public static final String HIDDEN = "hidden";
+
+        /**
+         * The owner account for this calendar, based on the calendar feed.
+         * This will be different from the _SYNC_ACCOUNT for delegated calendars.
+         * <P>Type: String</P>
+         */
+        public static final String OWNER_ACCOUNT = "ownerAccount";
     }
 
     public interface AttendeesColumns {
@@ -458,6 +465,14 @@
          * <P>Type: INTEGER (long; millis since epoch)</P>
          */
         public static final String LAST_DATE = "lastDate";
+
+        /**
+         * Whether the event has attendee information.  True if the event
+         * has full attendee data, false if the event has information about
+         * self only.
+         * <P>Type: INTEGER (boolean)</P>
+         */
+        public static final String HAS_ATTENDEE_DATA = "hasAttendeeData";
     }
 
     /**
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index ae84e1e..d41d2d1 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -19,6 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.util.Log;
 
 /**
  * Object used to report movement (mouse, pen, finger, trackball) events.  This
@@ -26,6 +27,8 @@
  * it is being used for.
  */
 public final class MotionEvent implements Parcelable {
+    static final boolean DEBUG_POINTERS = false;
+    
     /**
      * Bit mask of the parts of the action code that are the action itself.
      */
@@ -68,58 +71,67 @@
 
     /**
      * A non-primary pointer has gone down.  The bits in
-     * {@link #ACTION_POINTER_MASK} indicate which pointer changed.
+     * {@link #ACTION_POINTER_ID_MASK} indicate which pointer changed.
      */
     public static final int ACTION_POINTER_DOWN     = 5;
     
     /**
-     * The primary pointer has gone done.
+     * Synonym for {@link #ACTION_POINTER_DOWN} with
+     * {@link #ACTION_POINTER_ID_MASK} of 0: the primary pointer has gone done.
      */
     public static final int ACTION_POINTER_1_DOWN   = ACTION_POINTER_DOWN | 0x0000;
     
     /**
-     * The secondary pointer has gone done.
+     * Synonym for {@link #ACTION_POINTER_DOWN} with
+     * {@link #ACTION_POINTER_ID_MASK} of 1: the secondary pointer has gone done.
      */
     public static final int ACTION_POINTER_2_DOWN   = ACTION_POINTER_DOWN | 0x0100;
     
     /**
-     * The tertiary pointer has gone done.
+     * Synonym for {@link #ACTION_POINTER_DOWN} with
+     * {@link #ACTION_POINTER_ID_MASK} of 2: the tertiary pointer has gone done.
      */
     public static final int ACTION_POINTER_3_DOWN   = ACTION_POINTER_DOWN | 0x0200;
     
     /**
      * A non-primary pointer has gone up.  The bits in
-     * {@link #ACTION_POINTER_MASK} indicate which pointer changed.
+     * {@link #ACTION_POINTER_ID_MASK} indicate which pointer changed.
      */
     public static final int ACTION_POINTER_UP       = 6;
     
     /**
-     * The primary pointer has gone up.
+     * Synonym for {@link #ACTION_POINTER_UP} with
+     * {@link #ACTION_POINTER_ID_MASK} of 0: the primary pointer has gone up.
      */
     public static final int ACTION_POINTER_1_UP     = ACTION_POINTER_UP | 0x0000;
     
     /**
-     * The secondary pointer has gone up.
+     * Synonym for {@link #ACTION_POINTER_UP} with
+     * {@link #ACTION_POINTER_ID_MASK} of 1: the secondary pointer has gone up.
      */
     public static final int ACTION_POINTER_2_UP     = ACTION_POINTER_UP | 0x0100;
     
     /**
-     * The tertiary pointer has gone up.
+     * Synonym for {@link #ACTION_POINTER_UP} with
+     * {@link #ACTION_POINTER_ID_MASK} of 2: the tertiary pointer has gone up.
      */
     public static final int ACTION_POINTER_3_UP     = ACTION_POINTER_UP | 0x0200;
     
     /**
      * Bits in the action code that represent a pointer ID, used with
      * {@link #ACTION_POINTER_DOWN} and {@link #ACTION_POINTER_UP}.  Pointer IDs
-     * start at 0, with 0 being the primary (first) pointer in the motion.
+     * start at 0, with 0 being the primary (first) pointer in the motion.  Note
+     * that this not <em>not</em> an index into the array of pointer values,
+     * which is compacted to only contain pointers that are down; the pointer
+     * ID for a particular index can be found with {@link #findPointerIndex}.
      */
-    public static final int ACTION_POINTER_MASK     = 0xff00;
+    public static final int ACTION_POINTER_ID_MASK  = 0xff00;
     
     /**
      * Bit shift for the action bits holding the pointer identifier as
-     * defined by {@link #ACTION_POINTER_MASK}.
+     * defined by {@link #ACTION_POINTER_ID_MASK}.
      */
-    public static final int ACTION_POINTER_SHIFT    = 8;
+    public static final int ACTION_POINTER_ID_SHIFT = 8;
     
     private static final boolean TRACK_RECYCLED_LOCATION = false;
 
@@ -144,18 +156,6 @@
     public static final int EDGE_RIGHT = 0x00000008;
 
     /**
-     * This is the part of the state data that holds the finger identifier
-     * for the sample.
-     */
-    static private final int STATE_FINGER_ID_MASK = 0xff;
-    
-    /**
-     * Special value for STATE_FINGER_ID_MASK indicating that the finger
-     * is not down in that sample.
-     */
-    static private final int STATE_FINGER_ID_NONE = 0xff;
-    
-    /**
      * Offset for the sample's X coordinate.
      * @hide
      */
@@ -212,8 +212,8 @@
     
     private int mNumPointers;
     private int mNumSamples;
-    // Array of (mNumSamples * mNumPointers) size of control data.
-    private int[] mStateSamples;
+    // Array of mNumPointers size of identifiers for each pointer of data.
+    private int[] mPointerIdentifiers;
     // Array of (mNumSamples * mNumPointers * NUM_SAMPLE_DATA) size of event data.
     private float[] mDataSamples;
     // Array of mNumSamples size of time stamps.
@@ -224,7 +224,7 @@
     private boolean mRecycled;
 
     private MotionEvent() {
-        mStateSamples = new int[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES];
+        mPointerIdentifiers = new int[BASE_AVAIL_POINTERS];
         mDataSamples = new float[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES*NUM_SAMPLE_DATA];
         mTimeSamples = new long[BASE_AVAIL_SAMPLES];
     }
@@ -256,16 +256,11 @@
      * @param action The kind of action being performed -- one of either
      * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or
      * {@link #ACTION_CANCEL}.
-     * @param x The X coordinate of this event.
-     * @param y The Y coordinate of this event.
-     * @param pressure The current pressure of this event.  The pressure generally 
-     * ranges from 0 (no pressure at all) to 1 (normal pressure), however 
-     * values higher than 1 may be generated depending on the calibration of 
-     * the input device.
-     * @param size A scaled value of the approximate size of the area being pressed when
-     * touched with the finger. The actual value in pixels corresponding to the finger 
-     * touch is normalized with a device specific range of values
-     * and scaled to a value between 0 and 1.
+     * @param pointers The number of points that will be in this event.
+     * @param inPointerIds An array of <em>pointers</em> values providing
+     * an identifier for each pointer.
+     * @param inData An array of <em>pointers*NUM_SAMPLE_DATA</em> of initial
+     * data samples for the event.
      * @param metaState The state of any meta / modifier keys that were in effect when
      * the event was generated.
      * @param xPrecision The precision of the X coordinate being reported.
@@ -279,7 +274,7 @@
      * @hide
      */
     static public MotionEvent obtainNano(long downTime, long eventTime, long eventTimeNano,
-            int action, int pointers, float[] inData, int metaState,
+            int action, int pointers, int[] inPointerIds, float[] inData, int metaState,
             float xPrecision, float yPrecision, int deviceId, int edgeFlags) {
         MotionEvent ev = obtain();
         ev.mDeviceId = deviceId;
@@ -295,17 +290,25 @@
         ev.mNumPointers = pointers;
         ev.mNumSamples = 1;
         
-        float[] data = ev.mDataSamples;
-        System.arraycopy(inData, 0, data, 0, pointers * NUM_SAMPLE_DATA);
-        
-        int[] state = ev.mStateSamples;
-        while (pointers > 0) {
-            pointers--;
-            state[pointers] = pointers;
-        }
-        
+        System.arraycopy(inPointerIds, 0, ev.mPointerIdentifiers, 0, pointers);
+        System.arraycopy(inData, 0, ev.mDataSamples, 0, pointers * NUM_SAMPLE_DATA);
         ev.mTimeSamples[0] = eventTime;
 
+        if (DEBUG_POINTERS) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("New:");
+            for (int i=0; i<pointers; i++) {
+                sb.append(" #");
+                sb.append(ev.mPointerIdentifiers[i]);
+                sb.append("(");
+                sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]);
+                sb.append(",");
+                sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]);
+                sb.append(")");
+            }
+            Log.v("MotionEvent", sb.toString());
+        }
+        
         return ev;
     }
     
@@ -355,8 +358,8 @@
 
         ev.mNumPointers = 1;
         ev.mNumSamples = 1;
-        int[] state = ev.mStateSamples;
-        state[0] = 0;
+        int[] pointerIds = ev.mPointerIdentifiers;
+        pointerIds[0] = 0;
         float[] data = ev.mDataSamples;
         data[SAMPLE_X] = ev.mRawX = x;
         data[SAMPLE_Y] = ev.mRawY = y;
@@ -415,8 +418,8 @@
 
         ev.mNumPointers = 1;
         ev.mNumSamples = 1;
-        int[] state = ev.mStateSamples;
-        state[0] = 0;
+        int[] pointerIds = ev.mPointerIdentifiers;
+        pointerIds[0] = 0;
         float[] data = ev.mDataSamples;
         data[SAMPLE_X] = ev.mRawX = x;
         data[SAMPLE_Y] = ev.mRawY = y;
@@ -459,8 +462,8 @@
 
         ev.mNumPointers = 1;
         ev.mNumSamples = 1;
-        int[] state = ev.mStateSamples;
-        state[0] = 0;
+        int[] pointerIds = ev.mPointerIdentifiers;
+        pointerIds[0] = 0;
         float[] data = ev.mDataSamples;
         data[SAMPLE_X] = ev.mRawX = x;
         data[SAMPLE_Y] = ev.mRawY = y;
@@ -508,21 +511,21 @@
         ev.mXPrecision = o.mXPrecision;
         ev.mYPrecision = o.mYPrecision;
         
-        final int NT = ev.mNumSamples = o.mNumSamples;
-        if (ev.mTimeSamples.length >= NT) {
-            System.arraycopy(o.mTimeSamples, 0, ev.mTimeSamples, 0, NT);
+        final int NS = ev.mNumSamples = o.mNumSamples;
+        if (ev.mTimeSamples.length >= NS) {
+            System.arraycopy(o.mTimeSamples, 0, ev.mTimeSamples, 0, NS);
         } else {
             ev.mTimeSamples = (long[])o.mTimeSamples.clone();
         }
         
-        final int NS = (ev.mNumPointers=o.mNumPointers) * NT;
-        if (ev.mStateSamples.length >= NS) {
-            System.arraycopy(o.mStateSamples, 0, ev.mStateSamples, 0, NS);
+        final int NP = (ev.mNumPointers=o.mNumPointers);
+        if (ev.mPointerIdentifiers.length >= NP) {
+            System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, NP);
         } else {
-            ev.mStateSamples = (int[])o.mStateSamples.clone();
+            ev.mPointerIdentifiers = (int[])o.mPointerIdentifiers.clone();
         }
         
-        final int ND = NS * NUM_SAMPLE_DATA;
+        final int ND = NP * NS * NUM_SAMPLE_DATA;
         if (ev.mDataSamples.length >= ND) {
             System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, ND);
         } else {
@@ -593,6 +596,38 @@
     }
 
     /**
+     * {@link #getX(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     */
+    public final float getX() {
+        return mDataSamples[SAMPLE_X];
+    }
+
+    /**
+     * {@link #getY(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     */
+    public final float getY() {
+        return mDataSamples[SAMPLE_Y];
+    }
+
+    /**
+     * {@link #getPressure(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     */
+    public final float getPressure() {
+        return mDataSamples[SAMPLE_PRESSURE];
+    }
+
+    /**
+     * {@link #getSize(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
+     */
+    public final float getSize() {
+        return mDataSamples[SAMPLE_SIZE];
+    }
+
+    /**
      * The number of pointers of data contained in this event.  Always
      * >= 1.
      */
@@ -601,80 +636,91 @@
     }
     
     /**
-     * {@link #getX(int)} for the first pointer (pointer 0).
+     * Return the pointer identifier associated with a particular pointer
+     * data index is this event.  The identifier tells you the actual pointer
+     * number associated with the data, accounting for individual pointers
+     * going up and down since the start of the current gesture.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getX() {
-        return mDataSamples[SAMPLE_X];
+    public final int getPointerId(int index) {
+        return mPointerIdentifiers[index];
     }
-
+    
     /**
-     * {@link #getY(int)} for the first pointer (pointer 0).
+     * Given a pointer identifier, find the index of its data in the event.
+     * 
+     * @param pointerId The identifier of the pointer to be found.
+     * @return Returns either the index of the pointer (for use with
+     * {@link #getX(int) et al.), or -1 if there is no data available for
+     * that pointer identifier.
      */
-    public final float getY() {
-        return mDataSamples[SAMPLE_Y];
+    public final int findPointerIndex(int pointerId) {
+        int i = mNumPointers;
+        while (i > 0) {
+            i--;
+            if (mPointerIdentifiers[i] == pointerId) {
+                return i;
+            }
+        }
+        return -1;
     }
-
+    
     /**
-     * {@link #getPressure(int)} for the first pointer (pointer 0).
-     */
-    public final float getPressure() {
-        return mDataSamples[SAMPLE_PRESSURE];
-    }
-
-    /**
-     * {@link #getSize(int)} for the first pointer (pointer 0).
-     */
-    public final float getSize() {
-        return mDataSamples[SAMPLE_SIZE];
-    }
-
-    /**
-     * Returns the X coordinate of this event for the given pointer.
+     * Returns the X coordinate of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
      * Whole numbers are pixels; the 
      * value may have a fraction for input devices that are sub-pixel precise. 
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getX(int pointer) {
-        return mDataSamples[(pointer*NUM_SAMPLE_DATA) + SAMPLE_X];
+    public final float getX(int pointerIndex) {
+        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_X];
     }
 
     /**
-     * Returns the Y coordinate of this event for the given pointer.
+     * Returns the Y coordinate of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
      * Whole numbers are pixels; the
      * value may have a fraction for input devices that are sub-pixel precise.
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getY(int pointer) {
-        return mDataSamples[(pointer*NUM_SAMPLE_DATA) + SAMPLE_Y];
+    public final float getY(int pointerIndex) {
+        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_Y];
     }
 
     /**
-     * Returns the current pressure of this event for the given pointer.
+     * Returns the current pressure of this event for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
      * The pressure generally
      * ranges from 0 (no pressure at all) to 1 (normal pressure), however
      * values higher than 1 may be generated depending on the calibration of
      * the input device.
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getPressure(int pointer) {
-        return mDataSamples[(pointer*NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
+    public final float getPressure(int pointerIndex) {
+        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
     }
 
     /**
-     * Returns a scaled value of the approximate size for the given pointer,
-     * representing the area of the screen being pressed.
-     * The actual value in pixels corresponding to the
+     * Returns a scaled value of the approximate size for the given pointer
+     * <em>index</em> (use {@link #getPointerId(int)} to find the pointer
+     * identifier for this index).
+     * This represents some approximation of the area of the screen being
+     * pressed; the actual value in pixels corresponding to the
      * touch is normalized with the device specific range of values
      * and scaled to a value between 0 and 1. The value of size can be used to
      * determine fat touch events.
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      */
-    public final float getSize(int pointer) {
-        return mDataSamples[(pointer*NUM_SAMPLE_DATA) + SAMPLE_SIZE];
+    public final float getSize(int pointerIndex) {
+        return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_SIZE];
     }
 
     /**
@@ -758,99 +804,107 @@
     }
 
     /**
-     * {@link #getHistoricalX(int)} for the first pointer (pointer 0).
+     * {@link #getHistoricalX(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
      */
     public final float getHistoricalX(int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_X];
     }
 
     /**
-     * {@link #getHistoricalY(int)} for the first pointer (pointer 0).
+     * {@link #getHistoricalY(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
      */
     public final float getHistoricalY(int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_Y];
     }
 
     /**
-     * {@link #getHistoricalPressure(int)} for the first pointer (pointer 0).
+     * {@link #getHistoricalPressure(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
      */
     public final float getHistoricalPressure(int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_PRESSURE];
     }
 
     /**
-     * {@link #getHistoricalSize(int)} for the first pointer (pointer 0).
+     * {@link #getHistoricalSize(int)} for the first pointer index (may be an
+     * arbitrary pointer identifier).
      */
     public final float getHistoricalSize(int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_SIZE];
     }
 
     /**
-     * Returns a historical X coordinate that occurred between this event
-     * and the previous event for the given pointer.  Only applies to ACTION_MOVE events.
+     * Returns a historical X coordinate, as per {@link #getX(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
      *
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      * @param pos Which historical value to return; must be less than
      * {@link #getHistorySize}
      *
      * @see #getHistorySize
      * @see #getX
      */
-    public final float getHistoricalX(int pointer, int pos) {
+    public final float getHistoricalX(int pointerIndex, int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointer * NUM_SAMPLE_DATA) + SAMPLE_X];
+                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_X];
     }
 
     /**
-     * Returns a historical Y coordinate that occurred between this event
-     * and the previous event for the given pointer.  Only applies to ACTION_MOVE events.
+     * Returns a historical Y coordinate, as per {@link #getY(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
      *
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      * @param pos Which historical value to return; must be less than
      * {@link #getHistorySize}
      *
      * @see #getHistorySize
      * @see #getY
      */
-    public final float getHistoricalY(int pointer, int pos) {
+    public final float getHistoricalY(int pointerIndex, int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointer * NUM_SAMPLE_DATA) + SAMPLE_Y];
+                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_Y];
     }
 
     /**
-     * Returns a historical pressure coordinate that occurred between this event
-     * and the previous event for the given pointer.  Only applies to ACTION_MOVE events.
+     * Returns a historical pressure coordinate, as per {@link #getPressure(int)},
+     * that occurred between this event and the previous event for the given
+     * pointer.  Only applies to ACTION_MOVE events.
      *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      * @param pos Which historical value to return; must be less than
      * {@link #getHistorySize}
-     *
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * 
      * @see #getHistorySize
      * @see #getPressure
      */
-    public final float getHistoricalPressure(int pointer, int pos) {
+    public final float getHistoricalPressure(int pointerIndex, int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointer * NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
+                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_PRESSURE];
     }
 
     /**
-     * Returns a historical size coordinate that occurred between this event
-     * and the previous event for the given pointer.  Only applies to ACTION_MOVE events.
+     * Returns a historical size coordinate, as per {@link #getSize(int)}, that
+     * occurred between this event and the previous event for the given pointer.
+     * Only applies to ACTION_MOVE events.
      *
+     * @param pointerIndex Raw index of pointer to retrieve.  Value may be from 0
+     * (the first pointer that is down) to {@link #getPointerCount()}-1.
      * @param pos Which historical value to return; must be less than
      * {@link #getHistorySize}
-     *
-     * @param pointer The desired pointer to retrieve.  Value may be from 0
-     * (the first pointer) to {@link #getPointerCount()}-1.
+     * 
      * @see #getHistorySize
      * @see #getSize
      */
-    public final float getHistoricalSize(int pointer, int pos) {
+    public final float getHistoricalSize(int pointerIndex, int pos) {
         return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers)
-                            + (pointer * NUM_SAMPLE_DATA) + SAMPLE_SIZE];
+                            + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_SIZE];
     }
 
     /**
@@ -938,7 +992,6 @@
      */
     public final void addBatch(long eventTime, float x, float y,
             float pressure, float size, int metaState) {
-        int[] states = mStateSamples;
         float[] data = mDataSamples;
         long[] times = mTimeSamples;
         
@@ -946,14 +999,8 @@
         final int NS = mNumSamples;
         final int NI = NP*NS;
         final int ND = NI * NUM_SAMPLE_DATA;
-        if (states.length < (NI+NP)) {
-            // The state and data arrays are sized together, since their
-            // size is always a fixed factor from each other.
-            final int NEW_NI = NP * (NS+BASE_AVAIL_SAMPLES);
-            int[] newState = new int[NEW_NI];
-            System.arraycopy(states, 0, newState, 0, NI);
-            mStateSamples = states = newState;
-            final int NEW_ND = NEW_NI * NUM_SAMPLE_DATA;
+        if (data.length <= ND) {
+            final int NEW_ND = ND + (NP * (BASE_AVAIL_SAMPLES * NUM_SAMPLE_DATA));
             float[] newData = new float[NEW_ND];
             System.arraycopy(data, 0, newData, 0, ND);
             mDataSamples = data = newData;
@@ -996,7 +1043,6 @@
      * @hide
      */
     public final void addBatch(long eventTime, float[] inData, int metaState) {
-        int[] states = mStateSamples;
         float[] data = mDataSamples;
         long[] times = mTimeSamples;
         
@@ -1004,14 +1050,8 @@
         final int NS = mNumSamples;
         final int NI = NP*NS;
         final int ND = NI * NUM_SAMPLE_DATA;
-        if (states.length < (NI+NP)) {
-            // The state and data arrays are sized together, since their
-            // size is always a fixed factor from each other.
-            final int NEW_NI = NP * (NS+BASE_AVAIL_SAMPLES);
-            int[] newState = new int[NEW_NI];
-            System.arraycopy(states, 0, newState, 0, NI);
-            mStateSamples = states = newState;
-            final int NEW_ND = NEW_NI * NUM_SAMPLE_DATA;
+        if (data.length <= ND) {
+            final int NEW_ND = ND + (NP * (BASE_AVAIL_SAMPLES * NUM_SAMPLE_DATA));
             float[] newData = new float[NEW_ND];
             System.arraycopy(data, 0, newData, 0, ND);
             mDataSamples = data = newData;
@@ -1034,6 +1074,21 @@
         mRawX = inData[SAMPLE_X];
         mRawY = inData[SAMPLE_Y];
         mMetaState |= metaState;
+        
+        if (DEBUG_POINTERS) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Add:");
+            for (int i=0; i<mNumPointers; i++) {
+                sb.append(" #");
+                sb.append(mPointerIdentifiers[i]);
+                sb.append("(");
+                sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]);
+                sb.append(",");
+                sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]);
+                sb.append(")");
+            }
+            Log.v("MotionEvent", sb.toString());
+        }
     }
 
     @Override
@@ -1074,13 +1129,13 @@
         final int NI = NP*NS;
         if (NI > 0) {
             int i;
-            int[] state = mStateSamples;
-            for (i=0; i<NI; i++) {
+            int[] state = mPointerIdentifiers;
+            for (i=0; i<NP; i++) {
                 out.writeInt(state[i]);
             }
-            final int NI4 = NI*NUM_SAMPLE_DATA;
+            final int ND = NI*NUM_SAMPLE_DATA;
             float[] history = mDataSamples;
-            for (i=0; i<NI4; i++) {
+            for (i=0; i<ND; i++) {
                 out.writeFloat(history[i]);
             }
             long[] times = mTimeSamples;
@@ -1107,19 +1162,19 @@
         mNumSamples = NS;
         final int NI = NP*NS;
         if (NI > 0) {
-            final int NI4 = NI*4;
-            int[] state = mStateSamples;
-            if (state.length < NI) {
-                mStateSamples = state = new int[NI];
+            int[] ids = mPointerIdentifiers;
+            if (ids.length < NP) {
+                mPointerIdentifiers = ids = new int[NP];
             }
-            for (int i=0; i<NI; i++) {
-                state[i] = in.readInt();
+            for (int i=0; i<NP; i++) {
+                ids[i] = in.readInt();
             }
             float[] history = mDataSamples;
-            if (history.length < NI4) {
-                mDataSamples = history = new float[NI4];
+            final int ND = NI*NUM_SAMPLE_DATA;
+            if (history.length < ND) {
+                mDataSamples = history = new float[ND];
             }
-            for (int i=0; i<NI4; i++) {
+            for (int i=0; i<ND; i++) {
                 history[i] = in.readFloat();
             }
             long[] times = mTimeSamples;
diff --git a/core/java/android/view/RawInputEvent.java b/core/java/android/view/RawInputEvent.java
index 30da83e..db024b4 100644
--- a/core/java/android/view/RawInputEvent.java
+++ b/core/java/android/view/RawInputEvent.java
@@ -13,6 +13,8 @@
     public static final int CLASS_ALPHAKEY = 0x00000002;
     public static final int CLASS_TOUCHSCREEN = 0x00000004;
     public static final int CLASS_TRACKBALL = 0x00000008;
+    public static final int CLASS_TOUCHSCREEN_MT = 0x00000010;
+    public static final int CLASS_DPAD = 0x00000020;
     
     // More special classes for QueuedEvent below.
     public static final int CLASS_CONFIGURATION_CHANGED = 0x10000000;
@@ -158,8 +160,21 @@
     public static final int ABS_TOOL_WIDTH = 0x1c;
     public static final int ABS_VOLUME = 0x20;
     public static final int ABS_MISC = 0x28;
+    public static final int ABS_MT_TOUCH_MAJOR = 0x30;
+    public static final int ABS_MT_TOUCH_MINOR = 0x31;
+    public static final int ABS_MT_WIDTH_MAJOR = 0x32;
+    public static final int ABS_MT_WIDTH_MINOR = 0x33;
+    public static final int ABS_MT_ORIENTATION = 0x34;
+    public static final int ABS_MT_POSITION_X = 0x35;
+    public static final int ABS_MT_POSITION_Y = 0x36;
+    public static final int ABS_MT_TOOL_TYPE = 0x37;
+    public static final int ABS_MT_BLOB_ID = 0x38;
     public static final int ABS_MAX = 0x3f;
 
+    public static final int SYN_REPORT = 0;
+    public static final int SYN_CONFIG = 1;
+    public static final int SYN_MT_REPORT = 2;
+    
     public int deviceId;
     public int type;
     public int scancode;
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index e6ccd70..06581c1 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -615,6 +615,11 @@
         mCallbackProxy.onReceivedIcon(icon);
     }
 
+    // Called by JNI when an apple-touch-icon attribute was found.
+    private void didReceiveTouchIconUrl(String url) {
+        mCallbackProxy.onReceivedTouchIconUrl(url);
+    }
+
     /**
      * Request a new window from the client.
      * @return The BrowserFrame object stored in the new WebView.
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index ed77ce8..b2277cb 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -104,6 +104,7 @@
     private static final int ADD_MESSAGE_TO_CONSOLE              = 129;
     private static final int GEOLOCATION_PERMISSIONS_SHOW_PROMPT = 130;
     private static final int GEOLOCATION_PERMISSIONS_HIDE_PROMPT = 131;
+    private static final int RECEIVED_TOUCH_ICON_URL             = 132;
 
     // Message triggered by the client to resume execution
     private static final int NOTIFY                              = 200;
@@ -244,6 +245,13 @@
                 }
                 break;
 
+            case RECEIVED_TOUCH_ICON_URL:
+                if (mWebChromeClient != null) {
+                    mWebChromeClient.onReceivedTouchIconUrl(mWebView,
+                            (String) msg.obj);
+                }
+                break;
+
             case RECEIVED_TITLE:
                 if (mWebChromeClient != null) {
                     mWebChromeClient.onReceivedTitle(mWebView,
@@ -1054,6 +1062,21 @@
         sendMessage(obtainMessage(RECEIVED_ICON, icon));
     }
 
+    /* package */ void onReceivedTouchIconUrl(String url) {
+        // We should have a current item but we do not want to crash so check
+        // for null.
+        WebHistoryItem i = mBackForwardList.getCurrentItem();
+        if (i != null) {
+            i.setTouchIconUrl(url);
+        }
+        // Do an unsynchronized quick check to avoid posting if no callback has
+        // been set.
+        if (mWebChromeClient == null) {
+            return;
+        }
+        sendMessage(obtainMessage(RECEIVED_TOUCH_ICON_URL, url));
+    }
+
     public void onReceivedTitle(String title) {
         // Do an unsynchronized quick check to avoid posting if no callback has
         // been set.
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index 2d7376c..21bcdf17 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -186,6 +186,13 @@
     }
 
     /**
+     * Returns the path of the plugin data directory
+     */
+    private String getPluginSharedDataDirectory() {
+        return PluginManager.getInstance(null).getPluginSharedDataDirectory();
+    }
+
+    /**
      * setSharedTimer
      * @param timemillis The relative time when the timer should fire
      */
diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java
index e4a44b92..370d3d2 100644
--- a/core/java/android/webkit/PluginManager.java
+++ b/core/java/android/webkit/PluginManager.java
@@ -154,4 +154,8 @@
         }
         return directories.toArray(new String[directories.size()]);
     }
+
+    String getPluginSharedDataDirectory() {
+        return mContext.getDir("plugins", 0).getPath();
+    }
 }
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index d52406d..c10bc97 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -45,6 +45,14 @@
     public void onReceivedIcon(WebView view, Bitmap icon) {}
 
     /**
+     * Notify the host application of the url for an apple-touch-icon.
+     * @param view The WebView that initiated the callback.
+     * @param url The icon url.
+     * @hide pending council approval
+     */
+    public void onReceivedTouchIconUrl(WebView view, String url) {}
+
+    /**
      * A callback interface used by the host application to notify
      * the current page that its custom view has been dismissed.
      *
diff --git a/core/java/android/webkit/WebHistoryItem.java b/core/java/android/webkit/WebHistoryItem.java
index fd26b98..abd8237 100644
--- a/core/java/android/webkit/WebHistoryItem.java
+++ b/core/java/android/webkit/WebHistoryItem.java
@@ -39,6 +39,8 @@
     private Bitmap mFavicon;
     // The pre-flattened data used for saving the state.
     private byte[] mFlattenedData;
+    // The apple-touch-icon url for use when adding the site to the home screen
+    private String mTouchIconUrl;
 
     /**
      * Basic constructor that assigns a unique id to the item. Called by JNI
@@ -127,6 +129,14 @@
     }
 
     /**
+     * Return the touch icon url.
+     * @hide
+     */
+    public String getTouchIconUrl() {
+        return mTouchIconUrl;
+    }
+
+    /**
      * Set the favicon.
      * @param icon A Bitmap containing the favicon for this history item.
      * Note: The VM ensures 32-bit atomic read/write operations so we don't have
@@ -137,6 +147,14 @@
     }
 
     /**
+     * Set the touch icon url.
+     * @hide
+     */
+    /*package*/ void setTouchIconUrl(String url) {
+        mTouchIconUrl = url;
+    }
+
+    /**
      * Get the pre-flattened data.
      * Note: The VM ensures 32-bit atomic read/write operations so we don't have
      * to synchronize this method.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 3b81eed..444ef54 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2003,6 +2003,15 @@
     }
 
     /**
+     * Get the touch icon url for the apple-touch-icon <link> element.
+     * @hide
+     */
+    public String getTouchIconUrl() {
+        WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem();
+        return h != null ? h.getTouchIconUrl() : null;
+    }
+
+    /**
      * Get the progress for the current page.
      * @return The progress for the current page between 0 and 100.
      */
diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java
index a6fdf1f..c4f7759 100644
--- a/graphics/java/android/renderscript/ProgramVertex.java
+++ b/graphics/java/android/renderscript/ProgramVertex.java
@@ -38,8 +38,8 @@
         mID = 0;
     }
 
-    public void bindAllocation(int slot, MatrixAllocation va) {
-        mRS.nProgramVertexBindAllocation(mID, slot, va.mAlloc.mID);
+    public void bindAllocation(MatrixAllocation va) {
+        mRS.nProgramVertexBindAllocation(mID, va.mAlloc.mID);
     }
 
 
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 5fb7f08..0fb450e 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -164,9 +164,8 @@
     native void nProgramFragmentDestroy(int pgm);
 
     native void nProgramVertexDestroy(int pv);
-    native void nProgramVertexBindAllocation(int pv, int slot, int mID);
+    native void nProgramVertexBindAllocation(int pv, int mID);
     native void nProgramVertexBegin(int inID, int outID);
-    native void nProgramVertexSetType(int slot, int mID);
     native void nProgramVertexSetTextureMatrixEnable(boolean enable);
     native void nProgramVertexAddLight(int id);
     native int  nProgramVertexCreate();
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index 73380b8..e2a8a27 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -909,19 +909,11 @@
 }
 
 static void
-nProgramVertexBindAllocation(JNIEnv *_env, jobject _this, jint vpv, jint slot, jint a)
+nProgramVertexBindAllocation(JNIEnv *_env, jobject _this, jint vpv, jint a)
 {
     RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
     LOG_API("nProgramVertexBindAllocation, con(%p), vpf(%p), slot(%i), a(%p)", con, (RsProgramVertex)vpv, slot, (RsAllocation)a);
-    rsProgramVertexBindAllocation((RsProgramFragment)vpv, slot, (RsAllocation)a);
-}
-
-static void
-nProgramVertexSetType(JNIEnv *_env, jobject _this, jint slot, jint t)
-{
-    RsContext con = (RsContext)(_env->GetIntField(_this, gContextId));
-    LOG_API("nProgramVertexSetType, con(%p), vpf(%p), slot(%i), a(%p)", con, (RsProgramVertex)vpv, slot, (RsType)t);
-    rsProgramVertexSetType(slot, (RsType)t);
+    rsProgramVertexBindAllocation((RsProgramFragment)vpv, (RsAllocation)a);
 }
 
 static void
@@ -1186,9 +1178,8 @@
 {"nProgramFragmentDestroy",        "(I)V",                                 (void*)nProgramFragmentDestroy },
 
 {"nProgramVertexDestroy",          "(I)V",                                 (void*)nProgramVertexDestroy },
-{"nProgramVertexBindAllocation",   "(III)V",                               (void*)nProgramVertexBindAllocation },
+{"nProgramVertexBindAllocation",   "(II)V",                                (void*)nProgramVertexBindAllocation },
 {"nProgramVertexBegin",            "(II)V",                                (void*)nProgramVertexBegin },
-{"nProgramVertexSetType",          "(II)V",                                (void*)nProgramVertexSetType },
 {"nProgramVertexSetTextureMatrixEnable",   "(Z)V",                         (void*)nProgramVertexSetTextureMatrixEnable },
 {"nProgramVertexAddLight",         "(I)V",                                 (void*)nProgramVertexAddLight },
 {"nProgramVertexCreate",           "()I",                                  (void*)nProgramVertexCreate },
diff --git a/include/media/stagefright/OMXDecoder.h b/include/media/stagefright/OMXDecoder.h
index e76fd4c..c6b7cb3 100644
--- a/include/media/stagefright/OMXDecoder.h
+++ b/include/media/stagefright/OMXDecoder.h
@@ -91,8 +91,10 @@
     sp<IOMX> mOMX;
     IOMX::node_id mNode;
     char *mComponentName;
+    char *mMIME;
     bool mIsMP3;
     bool mIsAVC;
+    bool mIsEncoder;
     uint32_t mQuirks;
 
     MediaSource *mSource;
@@ -132,6 +134,7 @@
 
     OMXDecoder(OMXClient *client, IOMX::node_id node,
                const char *mime, const char *codec,
+               bool is_encoder,
                uint32_t quirks);
 
     void setPortStatus(OMX_U32 port_index, PortStatus status);
@@ -148,6 +151,7 @@
             OMX_COLOR_FORMATTYPE colorFormat);
 
     void setVideoOutputFormat(const char *mime, OMX_U32 width, OMX_U32 height);
+    void setVideoInputFormat(const char *mime, OMX_U32 width, OMX_U32 height);
     void setup();
     void dumpPortDefinition(OMX_U32 port_index);
 
diff --git a/include/ui/EventHub.h b/include/ui/EventHub.h
index d62fd7d..bffba07 100644
--- a/include/ui/EventHub.h
+++ b/include/ui/EventHub.h
@@ -55,7 +55,9 @@
         CLASS_KEYBOARD      = 0x00000001,
         CLASS_ALPHAKEY      = 0x00000002,
         CLASS_TOUCHSCREEN   = 0x00000004,
-        CLASS_TRACKBALL     = 0x00000008
+        CLASS_TRACKBALL     = 0x00000008,
+        CLASS_TOUCHSCREEN_MT= 0x00000010,
+        CLASS_DPAD          = 0x00000020
     };
     uint32_t getDeviceClasses(int32_t deviceId) const;
     
@@ -122,6 +124,7 @@
     };
 
     device_t* getDevice(int32_t deviceId) const;
+    bool hasKeycode(device_t* device, int keycode) const;
     
     // Protect all internal state.
     mutable Mutex   mLock;
diff --git a/libs/rs/java/Fall/src/com/android/fall/rs/FallRS.java b/libs/rs/java/Fall/src/com/android/fall/rs/FallRS.java
index ef875fc..833bed0 100644
--- a/libs/rs/java/Fall/src/com/android/fall/rs/FallRS.java
+++ b/libs/rs/java/Fall/src/com/android/fall/rs/FallRS.java
@@ -21,6 +21,7 @@
 import android.renderscript.ScriptC;
 import android.renderscript.ProgramFragment;
 import android.renderscript.ProgramStore;
+import android.renderscript.ProgramVertex;
 import android.renderscript.Allocation;
 import android.renderscript.Sampler;
 import android.renderscript.ProgramVertex;
@@ -79,7 +80,7 @@
         mResources = res;
         initRS();
     }
-    
+
     public void destroy() {
         mScript.destroy();
         mSampler.destroy();
@@ -126,7 +127,7 @@
     }
 
     private void createMesh() {
-        
+
     }
 
     private void createScriptStructures() {
@@ -210,7 +211,7 @@
         ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
         pvb.setTextureMatrixEnable(true);
         mPvBackground = pvb.create();
-        mPvBackground.bindAllocation(0, mPvOrthoAlloc);
+        mPvBackground.bindAllocation(mPvOrthoAlloc);
         mPvBackground.setName("PVBackground");
     }
 }
diff --git a/libs/rs/java/Film/src/com/android/film/FilmRS.java b/libs/rs/java/Film/src/com/android/film/FilmRS.java
index e598e0c..74f88c4 100644
--- a/libs/rs/java/Film/src/com/android/film/FilmRS.java
+++ b/libs/rs/java/Film/src/com/android/film/FilmRS.java
@@ -240,8 +240,8 @@
         initState();
 
         mPVA = new ProgramVertex.MatrixAllocation(mRS);
-        mPVBackground.bindAllocation(0, mPVA);
-        mPVImages.bindAllocation(0, mPVA);
+        mPVBackground.bindAllocation(mPVA);
+        mPVImages.bindAllocation(mPVA);
         mPVA.setupProjectionNormalized(320, 480);
 
 
diff --git a/libs/rs/java/Grass/src/com/android/grass/rs/GrassRS.java b/libs/rs/java/Grass/src/com/android/grass/rs/GrassRS.java
index 4ff7365..8160a41 100644
--- a/libs/rs/java/Grass/src/com/android/grass/rs/GrassRS.java
+++ b/libs/rs/java/Grass/src/com/android/grass/rs/GrassRS.java
@@ -265,7 +265,7 @@
         ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
         pvb.setTextureMatrixEnable(true);
         mPvBackground = pvb.create();
-        mPvBackground.bindAllocation(0, mPvOrthoAlloc);
+        mPvBackground.bindAllocation(mPvOrthoAlloc);
         mPvBackground.setName("PVBackground");
     }
 }
diff --git a/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java b/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java
index f6cf419..32a8f3c 100644
--- a/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java
+++ b/libs/rs/java/Rollo/src/com/android/rollo/RolloRS.java
@@ -168,7 +168,7 @@
         ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
         mPV = pvb.create();
         mPV.setName("PV");
-        mPV.bindAllocation(0, mPVAlloc);
+        mPV.bindAllocation(mPVAlloc);
 
         mPVOrthoAlloc = new ProgramVertex.MatrixAllocation(mRS);
         mPVOrthoAlloc.setupOrthoWindow(mWidth, mHeight);
@@ -176,7 +176,7 @@
         pvb.setTextureMatrixEnable(true);
         mPVOrtho = pvb.create();
         mPVOrtho.setName("PVOrtho");
-        mPVOrtho.bindAllocation(0, mPVOrthoAlloc);
+        mPVOrtho.bindAllocation(mPVOrthoAlloc);
 
         mRS.contextBindProgramVertex(mPV);
 
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index e118ace..05a8c37 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -409,15 +409,9 @@
 
 ProgramVertexBindAllocation {
 	param RsProgramVertex vpgm
-	param uint32_t slot
 	param RsAllocation constants
 	}
 
-ProgramVertexSetType {
-	param uint32_t slot
-	param RsType constants
-	}
-
 ProgramVertexSetTextureMatrixEnable {
 	param bool enable
 	}
diff --git a/libs/rs/rsAllocation.cpp b/libs/rs/rsAllocation.cpp
index ca277ef..ecfbf83 100644
--- a/libs/rs/rsAllocation.cpp
+++ b/libs/rs/rsAllocation.cpp
@@ -501,23 +501,25 @@
     return texAlloc;
 }
 
-
 void rsi_AllocationData(Context *rsc, RsAllocation va, const void *data)
 {
     Allocation *a = static_cast<Allocation *>(va);
     a->data(data);
+    rsc->allocationCheck(a);
 }
 
 void rsi_Allocation1DSubData(Context *rsc, RsAllocation va, uint32_t xoff, uint32_t count, const void *data)
 {
     Allocation *a = static_cast<Allocation *>(va);
     a->subData(xoff, count, data);
+    rsc->allocationCheck(a);
 }
 
 void rsi_Allocation2DSubData(Context *rsc, RsAllocation va, uint32_t xoff, uint32_t yoff, uint32_t w, uint32_t h, const void *data)
 {
     Allocation *a = static_cast<Allocation *>(va);
     a->subData(xoff, yoff, w, h, data);
+    rsc->allocationCheck(a);
 }
 
 
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index e52b0e0..46bd892 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -85,10 +85,15 @@
 
 bool Context::runRootScript()
 {
+#if RS_LOG_TIMES
+    struct timespec beginTime;
+    clock_gettime(CLOCK_MONOTONIC, &beginTime);
+#endif
+
     rsAssert(mRootScript->mEnviroment.mIsRoot);
 
-    glColor4f(1,1,1,1);
-    glEnable(GL_LIGHT0);
+    //glColor4f(1,1,1,1);
+    //glEnable(GL_LIGHT0);
     glViewport(0, 0, mWidth, mHeight);
 
     glDepthMask(GL_TRUE);
@@ -102,19 +107,34 @@
     glClear(GL_COLOR_BUFFER_BIT);
     glClear(GL_DEPTH_BUFFER_BIT);
 
-    return runScript(mRootScript.get(), 0);
+#if RS_LOG_TIMES
+    struct timespec startTime;
+    clock_gettime(CLOCK_MONOTONIC, &startTime);
+#endif
+    bool ret = runScript(mRootScript.get(), 0);
+
+#if RS_LOG_TIMES
+    struct timespec endTime;
+    clock_gettime(CLOCK_MONOTONIC, &endTime);
+
+    int t1 = ((unsigned long)startTime.tv_nsec - (unsigned long)beginTime.tv_nsec) / 1000 / 1000;
+    int t2 = ((unsigned long)endTime.tv_nsec - (unsigned long)startTime.tv_nsec) / 1000 / 1000;
+    LOGE("times  %i,  %i", t1, t2);
+#endif
+
+    return ret;
 }
 
 void Context::setupCheck()
 {
     if (mFragmentStore.get()) {
-        mFragmentStore->setupGL();
+        mFragmentStore->setupGL(&mStateFragmentStore);
     }
     if (mFragment.get()) {
-        mFragment->setupGL();
+        mFragment->setupGL(&mStateFragment);
     }
     if (mVertex.get()) {
-        mVertex->setupGL();
+        mVertex->setupGL(&mStateVertex);
     }
 
 }
@@ -245,7 +265,6 @@
     } else {
         mFragmentStore.set(pfs);
     }
-    mFragmentStore->setupGL();
 }
 
 void Context::setFragment(ProgramFragment *pf)
@@ -255,7 +274,13 @@
     } else {
         mFragment.set(pf);
     }
-    mFragment->setupGL();
+}
+
+void Context::allocationCheck(const Allocation *a)
+{
+    mVertex->checkUpdatedAllocation(a);
+    mFragment->checkUpdatedAllocation(a);
+    mFragmentStore->checkUpdatedAllocation(a);
 }
 
 void Context::setVertex(ProgramVertex *pv)
@@ -265,7 +290,6 @@
     } else {
         mVertex.set(pv);
     }
-    mVertex->setupGL();
 }
 
 void Context::assignName(ObjectBase *obj, const char *name, uint32_t len)
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index a00b8e8..1f8352c 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -45,7 +45,7 @@
 namespace android {
 namespace renderscript {
 
-class Context 
+class Context
 {
 public:
     Context(Device *, Surface *);
@@ -86,6 +86,7 @@
     const ProgramVertex * getVertex() {return mVertex.get();}
 
     void setupCheck();
+    void allocationCheck(const Allocation *);
 
     void assignName(ObjectBase *obj, const char *name, uint32_t len);
     void removeName(ObjectBase *obj);
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index 5a83fb7..6606daa 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -34,12 +34,19 @@
 }
 
 
-void Program::setAllocation(Allocation *alloc)
+void Program::bindAllocation(Allocation *alloc)
 {
     mConstants.set(alloc);
     mDirty = true;
 }
 
+void Program::checkUpdatedAllocation(const Allocation *alloc)
+{
+    if (mConstants.get() == alloc) {
+        mDirty = true;
+    }
+}
+
 void Program::setupGL()
 {
 
diff --git a/libs/rs/rsProgram.h b/libs/rs/rsProgram.h
index 913fdd2..251072f 100644
--- a/libs/rs/rsProgram.h
+++ b/libs/rs/rsProgram.h
@@ -33,10 +33,12 @@
     virtual ~Program();
 
 
-    void setAllocation(Allocation *);
+    void bindAllocation(Allocation *);
 
     virtual void setupGL();
 
+    void checkUpdatedAllocation(const Allocation *);
+
 protected:
     // Components not listed in "in" will be passed though
     // unless overwritten by components in out.
@@ -45,8 +47,7 @@
 
     ObjectBaseRef<Allocation> mConstants;
 
-    bool mDirty;
-
+    mutable bool mDirty;
 };
 
 
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 628f93e..ea507dc 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -39,8 +39,13 @@
 {
 }
 
-void ProgramFragment::setupGL()
+void ProgramFragment::setupGL(ProgramFragmentState *state)
 {
+    if ((state->mLast.get() == this) && !mDirty) {
+        return;
+    }
+    state->mLast.set(this);
+
     for (uint32_t ct=0; ct < MAX_TEXTURE; ct++) {
         glActiveTexture(GL_TEXTURE0 + ct);
         if (!(mTextureEnableMask & (1 << ct)) || !mTextures[ct].get()) {
@@ -90,8 +95,8 @@
         }
     }
 
-
     glActiveTexture(GL_TEXTURE0);
+    mDirty = false;
 }
 
 
@@ -104,6 +109,7 @@
 
     //LOGE("bindtex %i %p", slot, a);
     mTextures[slot].set(a);
+    mDirty = true;
 }
 
 void ProgramFragment::bindSampler(uint32_t slot, Sampler *s)
@@ -114,6 +120,7 @@
     }
 
     mSamplers[slot].set(s);
+    mDirty = true;
 }
 
 void ProgramFragment::setType(uint32_t slot, const Element *e, uint32_t dim)
@@ -190,7 +197,7 @@
     ProgramFragment *pf = static_cast<ProgramFragment *>(vpf);
     pf->bindTexture(slot, static_cast<Allocation *>(a));
     if (pf == rsc->getFragment()) {
-        pf->setupGL();
+        //pf->setupGL();
     }
 }
 
@@ -200,7 +207,7 @@
     pf->bindSampler(slot, static_cast<Sampler *>(s));
 
     if (pf == rsc->getFragment()) {
-        pf->setupGL();
+        //pf->setupGL();
     }
 }
 
diff --git a/libs/rs/rsProgramFragment.h b/libs/rs/rsProgramFragment.h
index 896d8dd..57fb6a5 100644
--- a/libs/rs/rsProgramFragment.h
+++ b/libs/rs/rsProgramFragment.h
@@ -23,19 +23,19 @@
 namespace android {
 namespace renderscript {
 
+class ProgramFragmentState;
 
 class ProgramFragment : public Program
 {
 public:
     const static uint32_t MAX_TEXTURE = 2;
-    const static uint32_t MAX_CONSTANTS = 2;
 
 
 
     ProgramFragment(Element *in, Element *out);
     virtual ~ProgramFragment();
 
-    virtual void setupGL();
+    virtual void setupGL(ProgramFragmentState *);
 
 
 
@@ -53,7 +53,7 @@
     // Texture lookups go though a sampler which in effect converts normalized
     // coordinates into type specific.  Multiple samples may also be taken
     // and filtered.
-    // 
+    //
     // Constants are strictly accessed by programetic loads.
     ObjectBaseRef<Allocation> mTextures[MAX_TEXTURE];
     ObjectBaseRef<Sampler> mSamplers[MAX_TEXTURE];
@@ -61,21 +61,12 @@
     uint32_t mTextureDimensions[MAX_TEXTURE];
 
 
-    ObjectBaseRef<Allocation> mConstants[MAX_CONSTANTS];
-    ObjectBaseRef<Type> mConstantTypes[MAX_CONSTANTS];
-
-
     // Hacks to create a program for now
     RsTexEnvMode mEnvModes[MAX_TEXTURE];
     uint32_t mTextureEnableMask;
-
-
-
-
-
 };
 
-class ProgramFragmentState 
+class ProgramFragmentState
 {
 public:
     ProgramFragmentState();
@@ -87,6 +78,8 @@
     ObjectBaseRef<Type> mTextureTypes[ProgramFragment::MAX_TEXTURE];
     ObjectBaseRef<ProgramFragment> mDefault;
     Vector<ProgramFragment *> mPrograms;
+
+    ObjectBaseRef<ProgramFragment> mLast;
 };
 
 
diff --git a/libs/rs/rsProgramFragmentStore.cpp b/libs/rs/rsProgramFragmentStore.cpp
index 9ee270f..27f4015 100644
--- a/libs/rs/rsProgramFragmentStore.cpp
+++ b/libs/rs/rsProgramFragmentStore.cpp
@@ -48,8 +48,13 @@
 {
 }
 
-void ProgramFragmentStore::setupGL()
+void ProgramFragmentStore::setupGL(ProgramFragmentStoreState *state)
 {
+    if (state->mLast.get() == this) {
+        return;
+    }
+    state->mLast.set(this);
+
     glColorMask(mColorRWriteEnable,
                 mColorGWriteEnable,
                 mColorBWriteEnable,
@@ -123,7 +128,7 @@
 void ProgramFragmentStore::setBlendFunc(RsBlendSrcFunc src, RsBlendDstFunc dst)
 {
     mBlendEnable = true;
-    if ((src == RS_BLEND_SRC_ONE) && 
+    if ((src == RS_BLEND_SRC_ONE) &&
         (dst == RS_BLEND_DST_ZERO)) {
         mBlendEnable = false;
     }
diff --git a/libs/rs/rsProgramFragmentStore.h b/libs/rs/rsProgramFragmentStore.h
index bd3a9f4..0de5c3a 100644
--- a/libs/rs/rsProgramFragmentStore.h
+++ b/libs/rs/rsProgramFragmentStore.h
@@ -23,18 +23,15 @@
 namespace android {
 namespace renderscript {
 
+class ProgramFragmentStoreState;
 
 class ProgramFragmentStore : public Program
 {
 public:
-
-
-
     ProgramFragmentStore(Element *in, Element *out);
     virtual ~ProgramFragmentStore();
 
-    virtual void setupGL();
-
+    virtual void setupGL(ProgramFragmentStoreState *);
 
     void setDepthFunc(RsDepthFunc);
     void setDepthMask(bool);
@@ -55,21 +52,14 @@
     int32_t mBlendSrc;
     int32_t mBlendDst;
 
-
-
     bool mDepthTestEnable;
     bool mDepthWriteEnable;
     int32_t mDepthFunc;
 
-
-
     bool mStencilTestEnable;
-
-
-
 };
 
-class ProgramFragmentStoreState 
+class ProgramFragmentStoreState
 {
 public:
     ProgramFragmentStoreState();
@@ -77,6 +67,9 @@
     void init(Context *rsc, int32_t w, int32_t h);
 
     ObjectBaseRef<ProgramFragmentStore> mDefault;
+    ObjectBaseRef<ProgramFragmentStore> mLast;
+
+
     ProgramFragmentStore *mPFS;
 };
 
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index 792135d..c66e488 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -44,9 +44,14 @@
     LOGV("%6.2f, %6.2f, %6.2f, %6.2f", f[3], f[7], f[11], f[15]);
 }
 
-void ProgramVertex::setupGL()
+void ProgramVertex::setupGL(ProgramVertexState *state)
 {
-    const float *f = static_cast<const float *>(mConstants[0]->getPtr());
+    if ((state->mLast.get() == this) && !mDirty) {
+        return;
+    }
+    state->mLast.set(this);
+
+    const float *f = static_cast<const float *>(mConstants->getPtr());
 
     glMatrixMode(GL_TEXTURE);
     if (mTextureMatrixEnable) {
@@ -72,7 +77,7 @@
     } else {
         glDisable(GL_LIGHTING);
     }
-    
+
     if (!f) {
         LOGE("Must bind constants to vertex program");
     }
@@ -81,16 +86,8 @@
     glLoadMatrixf(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET]);
     glMatrixMode(GL_MODELVIEW);
     glLoadMatrixf(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET]);
-}
 
-void ProgramVertex::setConstantType(uint32_t slot, const Type *t)
-{
-    mConstantTypes[slot].set(t);
-}
-
-void ProgramVertex::bindAllocation(uint32_t slot, Allocation *a)
-{
-    mConstants[slot].set(a);
+    mDirty = false;
 }
 
 void ProgramVertex::addLight(const Light *l)
@@ -103,20 +100,23 @@
 
 void ProgramVertex::setProjectionMatrix(const rsc_Matrix *m) const
 {
-    float *f = static_cast<float *>(mConstants[0]->getPtr());
+    float *f = static_cast<float *>(mConstants->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], m, sizeof(rsc_Matrix));
+    mDirty = true;
 }
 
 void ProgramVertex::setModelviewMatrix(const rsc_Matrix *m) const
 {
-    float *f = static_cast<float *>(mConstants[0]->getPtr());
+    float *f = static_cast<float *>(mConstants->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_MODELVIEW_OFFSET], m, sizeof(rsc_Matrix));
+    mDirty = true;
 }
 
 void ProgramVertex::setTextureMatrix(const rsc_Matrix *m) const
 {
-    float *f = static_cast<float *>(mConstants[0]->getPtr());
+    float *f = static_cast<float *>(mConstants->getPtr());
     memcpy(&f[RS_PROGRAM_VERTEX_TEXTURE_OFFSET], m, sizeof(rsc_Matrix));
+    mDirty = true;
 }
 
 
@@ -139,8 +139,8 @@
     mDefaultAlloc.set(alloc);
     mDefault.set(pv);
 
-    pv->bindAllocation(0, alloc);
-    
+    pv->bindAllocation(alloc);
+
     Matrix m;
     m.loadOrtho(0,w, h,0, -1,1);
     alloc->subData(RS_PROGRAM_VERTEX_PROJECTION_OFFSET, 16, &m.m[0]);
@@ -167,15 +167,10 @@
     return pv;
 }
 
-void rsi_ProgramVertexBindAllocation(Context *rsc, RsProgramVertex vpgm, uint32_t slot, RsAllocation constants)
+void rsi_ProgramVertexBindAllocation(Context *rsc, RsProgramVertex vpgm, RsAllocation constants)
 {
     ProgramVertex *pv = static_cast<ProgramVertex *>(vpgm);
-    pv->bindAllocation(slot, static_cast<Allocation *>(constants));
-}
-
-void rsi_ProgramVertexSetType(Context *rsc, uint32_t slot, RsType constants)
-{
-    rsc->mStateVertex.mPV->setConstantType(slot, static_cast<const Type *>(constants));
+    pv->bindAllocation(static_cast<Allocation *>(constants));
 }
 
 void rsi_ProgramVertexSetTextureMatrixEnable(Context *rsc, bool enable)
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index da5ed81..c9ce7aa 100644
--- a/libs/rs/rsProgramVertex.h
+++ b/libs/rs/rsProgramVertex.h
@@ -23,21 +23,19 @@
 namespace android {
 namespace renderscript {
 
+class ProgramVertexState;
 
 class ProgramVertex : public Program
 {
 public:
-    const static uint32_t MAX_CONSTANTS = 2;
     const static uint32_t MAX_LIGHTS = 8;
 
     ProgramVertex(Element *in, Element *out);
     virtual ~ProgramVertex();
 
-    virtual void setupGL();
+    virtual void setupGL(ProgramVertexState *state);
 
 
-    void setConstantType(uint32_t slot, const Type *);
-    void bindAllocation(uint32_t slot, Allocation *);
     void setTextureMatrixEnable(bool e) {mTextureMatrixEnable = e;}
     void addLight(const Light *);
 
@@ -46,21 +44,15 @@
     void setTextureMatrix(const rsc_Matrix *) const;
 
 protected:
-    bool mDirty;
     uint32_t mLightCount;
-
-    ObjectBaseRef<Allocation> mConstants[MAX_CONSTANTS];
-    ObjectBaseRef<const Type> mConstantTypes[MAX_CONSTANTS];
     ObjectBaseRef<const Light> mLights[MAX_LIGHTS];
 
-
     // Hacks to create a program for now
     bool mTextureMatrixEnable;
-
 };
 
 
-class ProgramVertexState 
+class ProgramVertexState
 {
 public:
     ProgramVertexState();
@@ -69,8 +61,9 @@
     void init(Context *rsc, int32_t w, int32_t h);
 
     ObjectBaseRef<ProgramVertex> mDefault;
+    ObjectBaseRef<ProgramVertex> mLast;
     ObjectBaseRef<Allocation> mDefaultAlloc;
-    
+
 
 
     ProgramVertex *mPV;
diff --git a/libs/rs/rsUtils.h b/libs/rs/rsUtils.h
index b13e88f..f0e4750 100644
--- a/libs/rs/rsUtils.h
+++ b/libs/rs/rsUtils.h
@@ -17,12 +17,13 @@
 #ifndef ANDROID_RS_UTILS_H
 #define ANDROID_RS_UTILS_H
 
-#define LOG_NDEBUG 0 
+#define LOG_NDEBUG 0
 #define LOG_TAG "rs"
 #include <utils/Log.h>
 #include <utils/Vector.h>
 #include <stdlib.h>
 #include <pthread.h>
+#include <time.h>
 
 #include <EGL/egl.h>
 #include <math.h>
@@ -38,6 +39,8 @@
 #define rsAssert(v) while(0)
 #endif
 
+#define RS_LOG_TIMES 0
+
 template<typename T>
 T rsMin(T in1, T in2)
 {
diff --git a/libs/ui/EventHub.cpp b/libs/ui/EventHub.cpp
index 59c9476..27334b7 100644
--- a/libs/ui/EventHub.cpp
+++ b/libs/ui/EventHub.cpp
@@ -16,6 +16,7 @@
 //#define LOG_NDEBUG 0
 
 #include <ui/EventHub.h>
+#include <ui/KeycodeLabels.h>
 #include <hardware_legacy/power.h>
 
 #include <cutils/properties.h>
@@ -58,6 +59,18 @@
 #define SEQ_SHIFT 16
 #define id_to_index(id)         ((id&ID_MASK)+1)
 
+#ifndef ABS_MT_TOUCH_MAJOR
+#define ABS_MT_TOUCH_MAJOR      0x30    /* Major axis of touching ellipse */
+#endif
+
+#ifndef ABS_MT_POSITION_X
+#define ABS_MT_POSITION_X       0x35    /* Center X ellipse position */
+#endif
+
+#ifndef ABS_MT_POSITION_Y
+#define ABS_MT_POSITION_Y       0x36    /* Center Y ellipse position */
+#endif
+
 namespace android {
 
 static const char *WAKE_LOCK_ID = "KeyEvents";
@@ -590,6 +603,8 @@
     mFDs[mFDCount].events = POLLIN;
 
     // figure out the kinds of events the device reports
+    
+    // See if this is a keyboard, and classify it.
     uint8_t key_bitmask[(KEY_MAX+1)/8];
     memset(key_bitmask, 0, sizeof(key_bitmask));
     LOGV("Getting keys...");
@@ -601,15 +616,11 @@
         for (int i=0; i<((BTN_MISC+7)/8); i++) {
             if (key_bitmask[i] != 0) {
                 device->classes |= CLASS_KEYBOARD;
-                // 'Q' key support = cheap test of whether this is an alpha-capable kbd
-                if (test_bit(KEY_Q, key_bitmask)) {
-                    device->classes |= CLASS_ALPHAKEY;
-                }
                 break;
             }
         }
         if ((device->classes & CLASS_KEYBOARD) != 0) {
-            device->keyBitmask = new uint8_t[(KEY_MAX+1)/8];
+            device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
             if (device->keyBitmask != NULL) {
                 memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
             } else {
@@ -619,6 +630,8 @@
             }
         }
     }
+    
+    // See if this is a trackball.
     if (test_bit(BTN_MOUSE, key_bitmask)) {
         uint8_t rel_bitmask[(REL_MAX+1)/8];
         memset(rel_bitmask, 0, sizeof(rel_bitmask));
@@ -630,16 +643,22 @@
             }
         }
     }
-    if (test_bit(BTN_TOUCH, key_bitmask)) {
-        uint8_t abs_bitmask[(ABS_MAX+1)/8];
-        memset(abs_bitmask, 0, sizeof(abs_bitmask));
-        LOGV("Getting absolute controllers...");
-        if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask) >= 0)
-        {
-            if (test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
-                device->classes |= CLASS_TOUCHSCREEN;
-            }
-        }
+    
+    uint8_t abs_bitmask[(ABS_MAX+1)/8];
+    memset(abs_bitmask, 0, sizeof(abs_bitmask));
+    LOGV("Getting absolute controllers...");
+    ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bitmask)), abs_bitmask);
+    
+    // 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)
+            && test_bit(ABS_MT_POSITION_Y, abs_bitmask)) {
+        device->classes |= CLASS_TOUCHSCREEN | CLASS_TOUCHSCREEN_MT;
+        
+    // Is this an old style single-touch driver?
+    } else if (test_bit(BTN_TOUCH, key_bitmask)
+            && test_bit(ABS_X, abs_bitmask) && test_bit(ABS_Y, abs_bitmask)) {
+        device->classes |= CLASS_TOUCHSCREEN;
     }
 
 #ifdef EV_SW
@@ -658,9 +677,6 @@
     }
 #endif
 
-    LOGI("New device: path=%s name=%s id=0x%x (of 0x%x) index=%d fd=%d classes=0x%x\n",
-         deviceName, name, device->id, mNumDevicesById, mFDCount, fd, device->classes);
-
     if ((device->classes&CLASS_KEYBOARD) != 0) {
         char devname[101];
         char tmpfn[101];
@@ -707,10 +723,27 @@
         sprintf(propName, "hw.keyboards.%u.devname", publicID);
         property_set(propName, devname);
 
-        LOGI("New keyboard: publicID=%d device->id=%d devname='%s' propName='%s' keylayout='%s'\n",
+        // 'Q' key support = cheap test of whether this is an alpha-capable kbd
+        if (hasKeycode(device, kKeyCodeQ)) {
+            device->classes |= CLASS_ALPHAKEY;
+        }
+        
+        // See if this has a DPAD.
+        if (hasKeycode(device, kKeyCodeDpadUp) &&
+                hasKeycode(device, kKeyCodeDpadDown) &&
+                hasKeycode(device, kKeyCodeDpadLeft) &&
+                hasKeycode(device, kKeyCodeDpadRight) &&
+                hasKeycode(device, kKeyCodeDpadCenter)) {
+            device->classes |= CLASS_DPAD;
+        }
+        
+        LOGI("New keyboard: publicID=%d device->id=0x%x devname='%s' propName='%s' keylayout='%s'\n",
                 publicID, device->id, devname, propName, keylayoutFilename);
     }
 
+    LOGI("New device: path=%s name=%s id=0x%x (of 0x%x) index=%d fd=%d classes=0x%x\n",
+         deviceName, name, device->id, mNumDevicesById, mFDCount, fd, device->classes);
+         
     LOGV("Adding device %s %p at %d, id = %d, classes = 0x%x\n",
          deviceName, device, mFDCount, devid, device->classes);
 
@@ -723,6 +756,25 @@
     return 0;
 }
 
+bool EventHub::hasKeycode(device_t* device, int keycode) const
+{
+    if (device->keyBitmask == NULL || device->layoutMap == NULL) {
+        return false;
+    }
+    
+    Vector<int32_t> scanCodes;
+    device->layoutMap->findScancodes(keycode, &scanCodes);
+    const size_t N = scanCodes.size();
+    for (size_t i=0; i<N && i<=KEY_MAX; i++) {
+        int32_t sc = scanCodes.itemAt(i);
+        if (sc >= 0 && sc <= KEY_MAX && test_bit(sc, device->keyBitmask)) {
+            return true;
+        }
+    }
+    
+    return false;
+}
+
 int EventHub::close_device(const char *deviceName)
 {
     AutoMutex _l(mLock);
diff --git a/libs/ui/Surface.cpp b/libs/ui/Surface.cpp
index a4710aa..c73909f 100644
--- a/libs/ui/Surface.cpp
+++ b/libs/ui/Surface.cpp
@@ -108,6 +108,9 @@
 status_t SurfaceBuffer::writeToParcel(Parcel* reply, 
         android_native_buffer_t const* buffer)
 {
+    if (buffer == NULL) {
+        return BAD_VALUE;
+    }
     reply->writeInt32(buffer->width);
     reply->writeInt32(buffer->height);
     reply->writeInt32(buffer->stride);
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 6bdf282..b53bb29 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -342,7 +342,12 @@
                 ++offset;
             }
 
-            assert(offset + 3 < size);
+            // assert(offset + 3 < size);
+            if (offset + 3 >= size) {
+                // XXX assume the entire first chunk of data is the codec specific
+                // data.
+                offset = size;
+            }
 
             mCodecSpecificDataSize = offset;
             mCodecSpecificData = malloc(offset);
diff --git a/media/libstagefright/OMXDecoder.cpp b/media/libstagefright/OMXDecoder.cpp
index 780cd2e..3ea3d01 100644
--- a/media/libstagefright/OMXDecoder.cpp
+++ b/media/libstagefright/OMXDecoder.cpp
@@ -76,9 +76,12 @@
     { "audio/3gpp", "OMX.PV.amrencnb" },
     { "audio/mp4a-latm", "OMX.PV.aacenc" },
     { "video/mp4v-es", "OMX.qcom.video.encoder.mpeg4" },
+    { "video/mp4v-es", "OMX.TI.Video.encoder" },
     { "video/mp4v-es", "OMX.PV.mpeg4enc" },
     { "video/3gpp", "OMX.qcom.video.encoder.h263" },
+    { "video/3gpp", "OMX.TI.Video.encoder" },
     { "video/3gpp", "OMX.PV.h263enc" },
+    { "video/avc", "OMX.TI.Video.encoder" },
     { "video/avc", "OMX.PV.avcenc" },
 };
 
@@ -158,7 +161,8 @@
         quirks |= kMeasuresTimeInMilliseconds;
     }
 
-    OMXDecoder *decoder = new OMXDecoder(client, node, mime, codec, quirks);
+    OMXDecoder *decoder = new OMXDecoder(
+            client, node, mime, codec, createEncoder, quirks);
 
     uint32_t type;
     const void *data;
@@ -166,7 +170,7 @@
     if (meta->findData(kKeyESDS, &type, &data, &size)) {
         ESDS esds((const char *)data, size);
         assert(esds.InitCheck() == OK);
-        
+
         const void *codec_specific_data;
         size_t codec_specific_data_size;
         esds.getCodecSpecificInfo(
@@ -211,13 +215,16 @@
 
 OMXDecoder::OMXDecoder(OMXClient *client, IOMX::node_id node,
                        const char *mime, const char *codec,
+                       bool is_encoder,
                        uint32_t quirks)
     : mClient(client),
       mOMX(mClient->interface()),
       mNode(node),
       mComponentName(strdup(codec)),
+      mMIME(strdup(mime)),
       mIsMP3(!strcasecmp(mime, "audio/mpeg")),
       mIsAVC(!strcasecmp(mime, "video/avc")),
+      mIsEncoder(is_encoder),
       mQuirks(quirks),
       mSource(NULL),
       mCodecSpecificDataIterator(mCodecSpecificData.begin()),
@@ -252,6 +259,9 @@
     assert(err == OK);
     mNode = 0;
 
+    free(mMIME);
+    mMIME = NULL;
+
     free(mComponentName);
     mComponentName = NULL;
 }
@@ -512,6 +522,27 @@
         // The following assertion is violated by TI's video decoder.
         // assert(format.nIndex == index);
 
+#if 1
+        LOGI("portIndex: %ld, index: %ld, eCompressionFormat=%d eColorFormat=%d",
+             portIndex,
+             index, format.eCompressionFormat, format.eColorFormat);
+#endif
+
+        if (!strcmp("OMX.TI.Video.encoder", mComponentName)) {
+            if (portIndex == kPortIndexInput
+                    && colorFormat == format.eColorFormat) {
+                // eCompressionFormat does not seem right.
+                found = true;
+                break;
+            }
+            if (portIndex == kPortIndexOutput
+                    && compressionFormat == format.eCompressionFormat) {
+                // eColorFormat does not seem right.
+                found = true;
+                break;
+            }
+        }
+
         if (format.eCompressionFormat == compressionFormat
             && format.eColorFormat == colorFormat) {
             found = true;
@@ -525,6 +556,7 @@
         return UNKNOWN_ERROR;
     }
 
+    LOGI("found a match.");
     status_t err = mOMX->set_parameter(
             mNode, OMX_IndexParamVideoPortFormat,
             &format, sizeof(format));
@@ -532,7 +564,83 @@
     return err;
 }
 
-#if 1
+void OMXDecoder::setVideoInputFormat(
+        const char *mime, OMX_U32 width, OMX_U32 height) {
+    LOGI("setVideoInputFormat width=%ld, height=%ld", width, height);
+
+    OMX_VIDEO_CODINGTYPE compressionFormat = OMX_VIDEO_CodingUnused;
+    if (!strcasecmp("video/avc", mMIME)) {
+        compressionFormat = OMX_VIDEO_CodingAVC;
+    } else if (!strcasecmp("video/mp4v-es", mMIME)) {
+        compressionFormat = OMX_VIDEO_CodingMPEG4;
+    } else if (!strcasecmp("video/3gpp", mMIME)) {
+        compressionFormat = OMX_VIDEO_CodingH263;
+    } else {
+        LOGE("Not a supported video mime type: %s", mime);
+        assert(!"Should not be here. Not a supported video mime type.");
+    }
+
+    OMX_COLOR_FORMATTYPE colorFormat =
+        0 ? OMX_COLOR_FormatYCbYCr : OMX_COLOR_FormatCbYCrY;
+
+    setVideoPortFormatType(
+            kPortIndexInput, OMX_VIDEO_CodingUnused,
+            colorFormat);
+
+    setVideoPortFormatType(
+            kPortIndexOutput, compressionFormat, OMX_COLOR_FormatUnused);
+
+    OMX_PARAM_PORTDEFINITIONTYPE def;
+    OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+
+    def.nSize = sizeof(def);
+    def.nVersion.s.nVersionMajor = 1;
+    def.nVersion.s.nVersionMinor = 1;
+    def.nPortIndex = kPortIndexOutput;
+
+    status_t err = mOMX->get_parameter(
+            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+    assert(err == NO_ERROR);
+
+    assert(def.eDomain == OMX_PortDomainVideo);
+
+    video_def->nFrameWidth = width;
+    video_def->nFrameHeight = height;
+
+    video_def->eCompressionFormat = compressionFormat;
+    video_def->eColorFormat = OMX_COLOR_FormatUnused;
+
+    err = mOMX->set_parameter(
+            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+    assert(err == NO_ERROR);
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    def.nSize = sizeof(def);
+    def.nVersion.s.nVersionMajor = 1;
+    def.nVersion.s.nVersionMinor = 1;
+    def.nPortIndex = kPortIndexInput;
+
+    err = mOMX->get_parameter(
+            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+    assert(err == NO_ERROR);
+
+    def.nBufferSize = (width * height * 2); // (width * height * 3) / 2;
+    LOGI("setting nBufferSize = %ld", def.nBufferSize);
+
+    assert(def.eDomain == OMX_PortDomainVideo);
+
+    video_def->nFrameWidth = width;
+    video_def->nFrameHeight = height;
+    video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
+    video_def->eColorFormat = colorFormat;
+
+    err = mOMX->set_parameter(
+            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+    assert(err == NO_ERROR);
+}
+
 void OMXDecoder::setVideoOutputFormat(
         const char *mime, OMX_U32 width, OMX_U32 height) {
     LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height);
@@ -541,7 +649,8 @@
     // Enabling this code appears to be the right thing(tm), but,...
     // the TI decoder then loses the ability to output YUV420 and only outputs
     // YCbYCr (16bit)
-    if (!strcasecmp("video/avc", mime)) {
+    if (!strcmp("OMX.TI.Video.Decoder", mComponentName)
+        && !strcasecmp("video/avc", mime)) {
         OMX_PARAM_COMPONENTROLETYPE role;
         role.nSize = sizeof(role);
         role.nVersion.s.nVersionMajor = 1;
@@ -565,6 +674,7 @@
     } else if (!strcasecmp("video/3gpp", mime)) {
         compressionFormat = OMX_VIDEO_CodingH263;
     } else {
+        LOGE("Not a supported video mime type: %s", mime);
         assert(!"Should not be here. Not a supported video mime type.");
     }
 
@@ -604,12 +714,10 @@
     OMX_PARAM_PORTDEFINITIONTYPE def;
     OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
 
-    bool is_encoder = strstr(mComponentName, ".encoder.") != NULL;  // XXX
-
     def.nSize = sizeof(def);
     def.nVersion.s.nVersionMajor = 1;
     def.nVersion.s.nVersionMinor = 1;
-    def.nPortIndex = is_encoder ? kPortIndexOutput : kPortIndexInput;
+    def.nPortIndex = kPortIndexInput;
 
     status_t err = mOMX->get_parameter(
             mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
@@ -625,7 +733,7 @@
 #endif
 
     assert(def.eDomain == OMX_PortDomainVideo);
-    
+
     video_def->nFrameWidth = width;
     video_def->nFrameHeight = height;
 
@@ -640,14 +748,14 @@
     def.nSize = sizeof(def);
     def.nVersion.s.nVersionMajor = 1;
     def.nVersion.s.nVersionMinor = 1;
-    def.nPortIndex = is_encoder ? kPortIndexInput : kPortIndexOutput;
+    def.nPortIndex = kPortIndexOutput;
 
     err = mOMX->get_parameter(
             mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
     assert(err == NO_ERROR);
 
     assert(def.eDomain == OMX_PortDomainVideo);
-    
+
 #if 0
     def.nBufferSize =
         (((width + 15) & -16) * ((height + 15) & -16) * 3) / 2;  // YUV420
@@ -661,176 +769,6 @@
     assert(err == NO_ERROR);
 }
 
-#else
-static void hexdump(const void *_data, size_t size) {
-    char line[256];
-    char tmp[16];
-
-    const uint8_t *data = (const uint8_t *)_data;
-    size_t offset = 0;
-    while (offset < size) {
-        sprintf(line, "0x%04x  ", offset);
-
-        size_t n = size - offset;
-        if (n > 16) {
-            n = 16;
-        }
-
-        for (size_t i = 0; i < 16; ++i) {
-            if (i == 8) {
-                strcat(line, " ");
-            }
-
-            if (offset + i < size) {
-                sprintf(tmp, "%02x ", data[offset + i]);
-                strcat(line, tmp);
-            } else {
-                strcat(line, "   ");
-            }
-        }
-
-        strcat(line, " ");
-
-        for (size_t i = 0; i < n; ++i) {
-            if (isprint(data[offset + i])) {
-                sprintf(tmp, "%c", data[offset + i]);
-                strcat(line, tmp);
-            } else {
-                strcat(line, ".");
-            }
-        }
-
-        LOGI(line);
-
-        offset += 16;
-    }
-}
-
-static void DumpPortDefinitionType(const void *_param) {
-    OMX_PARAM_PORTDEFINITIONTYPE *param = (OMX_PARAM_PORTDEFINITIONTYPE *)_param;
-
-    LOGI("nPortIndex=%ld eDir=%s nBufferCountActual=%ld nBufferCountMin=%ld nBufferSize=%ld", param->nPortIndex, param->eDir == OMX_DirInput ? "input" : "output",
-        param->nBufferCountActual, param->nBufferCountMin, param->nBufferSize);
-
-    if (param->eDomain == OMX_PortDomainVideo) {
-        OMX_VIDEO_PORTDEFINITIONTYPE *video = &param->format.video;
-        LOGI("nFrameWidth=%ld nFrameHeight=%ld nStride=%ld nSliceHeight=%ld nBitrate=%ld xFramerate=%ld eCompressionFormat=%d eColorFormat=%d",
-            video->nFrameWidth, video->nFrameHeight, video->nStride, video->nSliceHeight, video->nBitrate, video->xFramerate, video->eCompressionFormat, video->eColorFormat);
-    } else {
-        hexdump(param, param->nSize);
-    }
-}
-
-void OMXDecoder::setVideoOutputFormat(
-        const char *mime, OMX_U32 width, OMX_U32 height) {
-    LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height);
-
-#if 0
-    // Enabling this code appears to be the right thing(tm), but,...
-    // the decoder then loses the ability to output YUV420 and only outputs
-    // YCbYCr (16bit)
-    {
-        OMX_PARAM_COMPONENTROLETYPE role;
-        role.nSize = sizeof(role);
-        role.nVersion.s.nVersionMajor = 1;
-        role.nVersion.s.nVersionMinor = 1;
-        strncpy((char *)role.cRole, "video_decoder.avc",
-                OMX_MAX_STRINGNAME_SIZE - 1);
-        role.cRole[OMX_MAX_STRINGNAME_SIZE - 1] = '\0';
-
-        status_t err = mOMX->set_parameter(
-                mNode, OMX_IndexParamStandardComponentRole,
-                &role, sizeof(role));
-        assert(err == OK);
-    }
-#endif
-
-    setVideoPortFormatType(
-            kPortIndexInput, OMX_VIDEO_CodingAVC, OMX_COLOR_FormatUnused);
-
-#if 1
-    {
-        OMX_VIDEO_PARAM_PORTFORMATTYPE format;
-        format.nSize = sizeof(format);
-        format.nVersion.s.nVersionMajor = 1;
-        format.nVersion.s.nVersionMinor = 1;
-        format.nPortIndex = kPortIndexOutput;
-        format.nIndex = 0;
-
-        status_t err = mOMX->get_parameter(
-                mNode, OMX_IndexParamVideoPortFormat,
-                &format, sizeof(format));
-        assert(err == OK);
-
-        LOGI("XXX MyOMX_GetParameter OMX_IndexParamVideoPortFormat");
-        hexdump(&format, format.nSize);
-
-        assert(format.eCompressionFormat == OMX_VIDEO_CodingUnused);
-        assert(format.eColorFormat == OMX_COLOR_FormatYUV420Planar
-               || format.eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar
-               || format.eColorFormat == OMX_COLOR_FormatCbYCrY);
-
-        err = mOMX->set_parameter(
-                mNode, OMX_IndexParamVideoPortFormat,
-                &format, sizeof(format));
-        assert(err == OK);
-    }
-#endif
-
-    OMX_PORT_PARAM_TYPE ptype;
-    ptype.nSize = sizeof(ptype);
-    ptype.nVersion.s.nVersionMajor = 1;
-    ptype.nVersion.s.nVersionMinor = 1;
-
-    status_t err = mOMX->get_parameter(
-            mNode, OMX_IndexParamVideoInit, &ptype, sizeof(ptype));
-    assert(err == OK);
-
-    LOGI("XXX MyOMX_GetParameter OMX_IndexParamVideoInit");
-    hexdump(&ptype, ptype.nSize);
-
-    OMX_PARAM_PORTDEFINITIONTYPE def;
-    def.nSize = sizeof(def);
-    def.nVersion.s.nVersionMajor = 1;
-    def.nVersion.s.nVersionMinor = 1;
-    def.nPortIndex = kPortIndexInput;
-
-    err = mOMX->get_parameter(
-            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
-    assert(err == OK);
-
-    LOGI("XXX MyOMX_GetParameter OMX_IndexParamPortDefinition");
-    DumpPortDefinitionType(&def);
-
-    OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
-    video_def->nFrameWidth = width;
-    video_def->nFrameHeight = height;
-
-    err = mOMX->set_parameter(
-            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
-    assert(err == OK);
-
-    ////////////////////////////////////////////////////////////////////////////
-
-    def.nPortIndex = kPortIndexOutput;
-
-    err = mOMX->get_parameter(
-            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
-    assert(err == OK);
-
-    LOGI("XXX MyOMX_GetParameter OMX_IndexParamPortDefinition");
-    DumpPortDefinitionType(&def);
-
-    video_def->nFrameWidth = width;
-    video_def->nFrameHeight = height;
-
-    err = mOMX->set_parameter(
-            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
-    assert(err == OK);
-}
-
-#endif
-
 void OMXDecoder::setup() {
     const sp<MetaData> &meta = mSource->getFormat();
 
@@ -848,7 +786,11 @@
         success = success && meta->findInt32(kKeyHeight, &height);
         assert(success);
 
-        setVideoOutputFormat(mime, width, height);
+        if (mIsEncoder) {
+            setVideoInputFormat(mime, width, height);
+        } else {
+            setVideoOutputFormat(mime, width, height);
+        }
     }
 
     // dumpPortDefinition(0);
@@ -1253,7 +1195,7 @@
     if (mShutdownInitiated) {
         return;
     }
-    
+
     if (mState == OMX_StateLoaded) {
         return;
     }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 4adb04e..d8867d3 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -342,7 +342,6 @@
         File h263MemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(h263MemoryOut, true));
         output.write("H263 Video Playback Only\n");
-        getMemoryWriteToLog(output);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             mediaStressPlayback(MediaNames.VIDEO_HIGHRES_H263);
             getMemoryWriteToLog(output);
@@ -363,7 +362,6 @@
         File h264MemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(h264MemoryOut, true));
         output.write("H264 Video Playback only\n");
-        getMemoryWriteToLog(output);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             mediaStressPlayback(MediaNames.VIDEO_H264_AMR);
             getMemoryWriteToLog(output);
@@ -384,7 +382,6 @@
         File wmvMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(wmvMemoryOut, true));
         output.write("WMV video playback only\n");
-        getMemoryWriteToLog(output);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             mediaStressPlayback(MediaNames.VIDEO_WMV);
             getMemoryWriteToLog(output);
@@ -405,7 +402,6 @@
         File videoH263RecordOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoH263RecordOnlyMemoryOut, true));
         output.write("H263 video record only\n");
-        getMemoryWriteToLog(output);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.H263,
                     MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true);
@@ -427,7 +423,6 @@
         File videoMp4RecordOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoMp4RecordOnlyMemoryOut, true));
         output.write("MPEG4 video record only\n");
-        getMemoryWriteToLog(output);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.MPEG_4_SP,
                     MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, true);
@@ -450,7 +445,6 @@
         File videoRecordAudioMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(videoRecordAudioMemoryOut, true));
         output.write("Audio and h263 video record\n");
-        getMemoryWriteToLog(output);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             stressVideoRecord(20, 352, 288, MediaRecorder.VideoEncoder.H263,
                     MediaRecorder.OutputFormat.MPEG_4, MediaNames.RECORDED_VIDEO_3GP, false);
@@ -472,7 +466,6 @@
         File audioOnlyMemoryOut = new File(MEDIA_MEMORY_OUTPUT);
         Writer output = new BufferedWriter(new FileWriter(audioOnlyMemoryOut, true));
         output.write("Audio record only\n");
-        getMemoryWriteToLog(output);
         for (int i = 0; i < NUM_STRESS_LOOP; i++) {
             stressAudioRecord(MediaNames.RECORDER_OUTPUT);
             getMemoryWriteToLog(output);
diff --git a/opengl/tests/angeles/app-linux.c b/opengl/tests/angeles/app-linux.c
index 7d0d320..6be4876 100644
--- a/opengl/tests/angeles/app-linux.c
+++ b/opengl/tests/angeles/app-linux.c
@@ -132,6 +132,7 @@
      };
      
      EGLint numConfigs = -1;
+     EGLint n = 0;
      EGLint majorVersion;
      EGLint minorVersion;
      EGLConfig config;
@@ -148,15 +149,43 @@
      egl_error("eglInitialize");
 
      eglGetConfigs(dpy, NULL, 0, &numConfigs);
-     egl_error("eglGetConfigs");
-     fprintf(stderr,"num configs %d\n", numConfigs);
+
+     // Get all the "potential match" configs...
+     EGLConfig* const configs = malloc(sizeof(EGLConfig)*numConfigs);
+     eglChooseConfig(dpy, s_configAttribs, configs, numConfigs, &n);
+     config = configs[0];
+     if (n > 1) {
+         // if there is more than one candidate, go through the list
+         // and pick one that matches our framebuffer format
+         int fbSzA = 0; // should not hardcode
+         int fbSzR = 5; // should not hardcode
+         int fbSzG = 6; // should not hardcode
+         int fbSzB = 5; // should not hardcode
+         int i;
+         for (i=0 ; i<n ; i++) {
+             EGLint r,g,b,a;
+             eglGetConfigAttrib(dpy, configs[i], EGL_RED_SIZE,   &r);
+             eglGetConfigAttrib(dpy, configs[i], EGL_GREEN_SIZE, &g);
+             eglGetConfigAttrib(dpy, configs[i], EGL_BLUE_SIZE,  &b);
+             eglGetConfigAttrib(dpy, configs[i], EGL_ALPHA_SIZE, &a);
+             if (fbSzA == a && fbSzR == r && fbSzG == g && fbSzB  == b) {
+                 config = configs[i];
+                 break;
+             }
+         }
+     }
+     free(configs);
      
-     eglChooseConfig(dpy, s_configAttribs, &config, 1, &numConfigs);
-     egl_error("eglChooseConfig");
+     
+     //eglGetConfigs(dpy, NULL, 0, &numConfigs);
+     //egl_error("eglGetConfigs");
+     //fprintf(stderr,"num configs %d\n", numConfigs);     
+     //eglChooseConfig(dpy, s_configAttribs, &config, 1, &numConfigs);
+     //egl_error("eglChooseConfig");
 
      surface = eglCreateWindowSurface(dpy, config,
              android_createDisplaySurface(), NULL);
-     egl_error("eglMapWindowSurface");
+     egl_error("eglCreateWindowSurface");
 
      fprintf(stderr,"surface = %p\n", surface);
 
diff --git a/opengl/tests/fillrate/Android.mk b/opengl/tests/fillrate/Android.mk
new file mode 100644
index 0000000..a7d30c2
--- /dev/null
+++ b/opengl/tests/fillrate/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+	fillrate.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+	libcutils \
+    libEGL \
+    libGLESv1_CM \
+    libui
+
+LOCAL_MODULE:= test-opengl-fillrate
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_EXECUTABLE)
diff --git a/opengl/tests/fillrate/fillrate.cpp b/opengl/tests/fillrate/fillrate.cpp
new file mode 100644
index 0000000..c814e8d
--- /dev/null
+++ b/opengl/tests/fillrate/fillrate.cpp
@@ -0,0 +1,149 @@
+/*
+**
+** Copyright 2006, 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.
+*/
+
+#define LOG_TAG "fillrate"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <EGL/egl.h>
+#include <GLES/gl.h>
+#include <GLES/glext.h>
+
+#include <utils/StopWatch.h>
+#include <ui/FramebufferNativeWindow.h>
+
+using namespace android;
+
+int main(int argc, char** argv)
+{
+    EGLint configAttribs[] = {
+         EGL_DEPTH_SIZE, 0,
+         EGL_NONE
+     };
+     
+     EGLint numConfigs = -1, n=0;
+     EGLint majorVersion;
+     EGLint minorVersion;
+     EGLConfig config;
+     EGLContext context;
+     EGLSurface surface;
+     EGLint w, h;
+     
+     EGLDisplay dpy;
+
+     dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+     eglInitialize(dpy, &majorVersion, &minorVersion);
+     
+     // Get all the "potential match" configs...
+     eglGetConfigs(dpy, NULL, 0, &numConfigs);
+     EGLConfig* const configs = (EGLConfig*)malloc(sizeof(EGLConfig)*numConfigs);
+     eglChooseConfig(dpy, configAttribs, configs, numConfigs, &n);
+     config = configs[0];
+     if (n > 1) {
+         // if there is more than one candidate, go through the list
+         // and pick one that matches our framebuffer format
+         int fbSzA = 0; // should not hardcode
+         int fbSzR = 5; // should not hardcode
+         int fbSzG = 6; // should not hardcode
+         int fbSzB = 5; // should not hardcode
+         int i;
+         for (i=0 ; i<n ; i++) {
+             EGLint r,g,b,a;
+             eglGetConfigAttrib(dpy, configs[i], EGL_RED_SIZE,   &r);
+             eglGetConfigAttrib(dpy, configs[i], EGL_GREEN_SIZE, &g);
+             eglGetConfigAttrib(dpy, configs[i], EGL_BLUE_SIZE,  &b);
+             eglGetConfigAttrib(dpy, configs[i], EGL_ALPHA_SIZE, &a);
+             if (fbSzA == a && fbSzR == r && fbSzG == g && fbSzB  == b) {
+                 config = configs[i];
+                 break;
+             }
+         }
+     }
+     free(configs);
+     
+     surface = eglCreateWindowSurface(dpy, config,
+             android_createDisplaySurface(), NULL);
+     context = eglCreateContext(dpy, config, NULL, NULL);
+     eglMakeCurrent(dpy, surface, surface, context);   
+     eglQuerySurface(dpy, surface, EGL_WIDTH, &w);
+     eglQuerySurface(dpy, surface, EGL_HEIGHT, &h);
+     
+     printf("w=%d, h=%d\n", w, h);
+     
+     glBindTexture(GL_TEXTURE_2D, 0);
+     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+     glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+     glDisable(GL_DITHER);
+     glDisable(GL_BLEND);
+     glEnable(GL_TEXTURE_2D);
+     glColor4f(1,1,1,1);
+
+     uint32_t* t32 = (uint32_t*)malloc(512*512*4); 
+     for (int y=0 ; y<512 ; y++) {
+         for (int x=0 ; x<512 ; x++) {
+             t32[x+y*512] = 0x10FFFFFF;
+         }
+     }
+
+     const GLfloat vertices[4][2] = {
+             { 0,  0 },
+             { 0,  h },
+             { w,  h },
+             { w,  0 }
+     };
+
+     const GLfloat texCoords[4][2] = {
+             { 0,  0 },
+             { 0,  1 },
+             { 1,  1 },
+             { 1,  0 }
+     };
+
+     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, t32);
+
+     glViewport(0, 0, w, h);
+     glMatrixMode(GL_PROJECTION);
+     glLoadIdentity();
+     glOrthof(0, w, 0, h, 0, 1);
+
+     glEnableClientState(GL_VERTEX_ARRAY);
+     glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+     glVertexPointer(2, GL_FIXED, 0, vertices);
+     glTexCoordPointer(2, GL_FLOAT, 0, texCoords);
+
+     glClearColor(1,0,0,0);
+     glClear(GL_COLOR_BUFFER_BIT);
+     glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 
+     eglSwapBuffers(dpy, surface);
+     
+     for (int c=1 ; c<32 ; c++) {
+         glClear(GL_COLOR_BUFFER_BIT);
+         nsecs_t now = systemTime();
+         for (int i=0 ; i<c ; i++) {
+             glDrawArrays(GL_TRIANGLE_FAN, 0, 4); 
+         }
+         eglSwapBuffers(dpy, surface);
+         nsecs_t t = systemTime() - now;
+         printf("%lld\t%d\t%f\n", t, c, (double(t)/c)/1000000.0);
+     }
+     return 0;
+}
diff --git a/opengl/tests/textures/textures.c b/opengl/tests/textures/textures.c
index 214291b..d877e74 100644
--- a/opengl/tests/textures/textures.c
+++ b/opengl/tests/textures/textures.c
@@ -31,7 +31,7 @@
          EGL_NONE
      };
      
-     EGLint numConfigs = -1;
+     EGLint numConfigs = -1, n=0;
      EGLint majorVersion;
      EGLint minorVersion;
      EGLConfig config;
@@ -43,7 +43,36 @@
 
      dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
      eglInitialize(dpy, &majorVersion, &minorVersion);
-     eglChooseConfig(dpy, s_configAttribs, &config, 1, &numConfigs);
+     
+     // Get all the "potential match" configs...
+     eglGetConfigs(dpy, NULL, 0, &numConfigs);
+     EGLConfig* const configs = malloc(sizeof(EGLConfig)*numConfigs);
+     eglChooseConfig(dpy, s_configAttribs, configs, numConfigs, &n);
+     config = configs[0];
+     if (n > 1) {
+         // if there is more than one candidate, go through the list
+         // and pick one that matches our framebuffer format
+         int fbSzA = 0; // should not hardcode
+         int fbSzR = 5; // should not hardcode
+         int fbSzG = 6; // should not hardcode
+         int fbSzB = 5; // should not hardcode
+         int i;
+         for (i=0 ; i<n ; i++) {
+             EGLint r,g,b,a;
+             eglGetConfigAttrib(dpy, configs[i], EGL_RED_SIZE,   &r);
+             eglGetConfigAttrib(dpy, configs[i], EGL_GREEN_SIZE, &g);
+             eglGetConfigAttrib(dpy, configs[i], EGL_BLUE_SIZE,  &b);
+             eglGetConfigAttrib(dpy, configs[i], EGL_ALPHA_SIZE, &a);
+             if (fbSzA == a && fbSzR == r && fbSzG == g && fbSzB  == b) {
+                 config = configs[i];
+                 break;
+             }
+         }
+     }
+     free(configs);
+     
+     
+     
      surface = eglCreateWindowSurface(dpy, config,
              android_createDisplaySurface(), NULL);
      context = eglCreateContext(dpy, config, NULL, NULL);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 835c683..6a1f6f8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -432,7 +432,7 @@
             } finally {
                 db.endTransaction();
             }
-            upgradeVersion = 36;
+            upgradeVersion = 37;
         }
 
         if (upgradeVersion == 37) {
diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java
index 0ac5740..cb23c45 100644
--- a/services/java/com/android/server/InputDevice.java
+++ b/services/java/com/android/server/InputDevice.java
@@ -23,11 +23,13 @@
 import android.view.WindowManagerPolicy;
 
 public class InputDevice {
+    static final boolean DEBUG_POINTERS = false;
+    
     /** Amount that trackball needs to move in order to generate a key event. */
     static final int TRACKBALL_MOVEMENT_THRESHOLD = 6;
 
     /** Maximum number of pointers we will track and report. */
-    static final int MAX_POINTERS = 2;
+    static final int MAX_POINTERS = 10;
     
     final int id;
     final int classes;
@@ -51,100 +53,316 @@
         float yMoveScale;
         MotionEvent currentMove = null;
         boolean changed = false;
-        boolean mLastAnyDown = false;
         long mDownTime = 0;
-        final boolean[] mLastDown = new boolean[MAX_POINTERS];
-        final boolean[] mDown = new boolean[MAX_POINTERS];
+        
+        // The currently assigned pointer IDs, corresponding to the last data.
+        int[] mPointerIds = new int[MAX_POINTERS];
+        
+        // This is the last generated pointer data, ordered to match
+        // mPointerIds.
+        int mLastNumPointers = 0;
         final int[] mLastData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];
-        final int[] mCurData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];
+        
+        // This is the next set of pointer data being generated.  It is not
+        // in any known order, and will be propagated in to mLastData
+        // as part of mapping it to the appropriate pointer IDs.
+        // Note that we have one extra sample of data here, to help clients
+        // avoid doing bounds checking.
+        int mNextNumPointers = 0;
+        final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS)
+                                        + MotionEvent.NUM_SAMPLE_DATA];
+        
+        // Temporary data structures for doing the pointer ID mapping.
+        final int[] mLast2Next = new int[MAX_POINTERS];
+        final int[] mNext2Last = new int[MAX_POINTERS];
+        final long[] mNext2LastDistance = new long[MAX_POINTERS];
+        
+        // Temporary data structure for generating the final motion data.
         final float[] mReportData = new float[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];
         
+        // This is not used here, but can be used by callers for state tracking.
+        int mAddingPointerOffset = 0;
+        final boolean[] mDown = new boolean[MAX_POINTERS];
+        
         MotionState(int mx, int my) {
             xPrecision = mx;
             yPrecision = my;
             xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f;
             yMoveScale = my != 0 ? (1.0f/my) : 1.0f;
+            for (int i=0; i<MAX_POINTERS; i++) {
+                mPointerIds[i] = i;
+            }
+        }
+        
+        private boolean assignPointer(int nextIndex, boolean allowOverlap) {
+            final int lastNumPointers = mLastNumPointers;
+            final int[] next2Last = mNext2Last;
+            final long[] next2LastDistance = mNext2LastDistance;
+            final int[] last2Next = mLast2Next;
+            final int[] lastData = mLastData;
+            final int[] nextData = mNextData;
+            final int id = nextIndex * MotionEvent.NUM_SAMPLE_DATA;
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice", "assignPointer: nextIndex="
+                    + nextIndex + " dataOff=" + id);
+            final int x1 = nextData[id + MotionEvent.SAMPLE_X];
+            final int y1 = nextData[id + MotionEvent.SAMPLE_Y];
+            
+            long bestDistance = -1;
+            int bestIndex = -1;
+            for (int j=0; j<lastNumPointers; j++) {
+                if (!allowOverlap && last2Next[j] < 0) {
+                    continue;
+                }
+                final int jd = j * MotionEvent.NUM_SAMPLE_DATA;
+                final int xd = lastData[jd + MotionEvent.SAMPLE_X] - x1;
+                final int yd = lastData[jd + MotionEvent.SAMPLE_Y] - y1;
+                final long distance = xd*(long)xd + yd*(long)yd;
+                if (j == 0 || distance < bestDistance) {
+                    bestDistance = distance;
+                    bestIndex = j;
+                }
+            }
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice", "New index " + nextIndex
+                    + " best old index=" + bestIndex + " (distance="
+                    + bestDistance + ")");
+            next2Last[nextIndex] = bestIndex;
+            next2LastDistance[nextIndex] = bestDistance;
+            
+            if (bestIndex < 0) {
+                return true;
+            }
+            
+            if (last2Next[bestIndex] == -1) {
+                last2Next[bestIndex] = nextIndex;
+                return false;
+            }
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice", "Old index " + bestIndex
+                    + " has multiple best new pointers!");
+            
+            last2Next[bestIndex] = -2;
+            return true;
+        }
+        
+        private int updatePointerIdentifiers() {
+            final int[] lastData = mLastData;
+            final int[] nextData = mNextData;
+            final int nextNumPointers = mNextNumPointers;
+            final int lastNumPointers = mLastNumPointers;
+            
+            if (nextNumPointers == 1 && lastNumPointers == 1) {
+                System.arraycopy(nextData, 0, lastData, 0,
+                        MotionEvent.NUM_SAMPLE_DATA);
+                return -1;
+            }
+            
+            // Clear our old state.
+            final int[] last2Next = mLast2Next;
+            for (int i=0; i<lastNumPointers; i++) {
+                last2Next[i] = -1;
+            }
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice",
+                    "Update pointers: lastNumPointers=" + lastNumPointers
+                    + " nextNumPointers=" + nextNumPointers);
+            
+            // Figure out the closes new points to the previous points.
+            final int[] next2Last = mNext2Last;
+            final long[] next2LastDistance = mNext2LastDistance;
+            boolean conflicts = false;
+            for (int i=0; i<nextNumPointers; i++) {
+                conflicts |= assignPointer(i, true);
+            }
+            
+            // Resolve ambiguities in pointer mappings, when two or more
+            // new pointer locations find their best previous location is
+            // the same.
+            if (conflicts) {
+                if (DEBUG_POINTERS) Log.v("InputDevice", "Resolving conflicts");
+                
+                for (int i=0; i<lastNumPointers; i++) {
+                    if (last2Next[i] != -2) {
+                        continue;
+                    }
+                    
+                    // Note that this algorithm is far from perfect.  Ideally
+                    // we should do something like the one described at
+                    // http://portal.acm.org/citation.cfm?id=997856
+                    
+                    if (DEBUG_POINTERS) Log.v("InputDevice",
+                            "Resolving last index #" + i);
+                    
+                    int numFound;
+                    do {
+                        numFound = 0;
+                        long worstDistance = 0;
+                        int worstJ = -1;
+                        for (int j=0; j<nextNumPointers; j++) {
+                            if (next2Last[j] != i) {
+                                continue;
+                            }
+                            numFound++;
+                            if (worstDistance < next2LastDistance[j]) {
+                                worstDistance = next2LastDistance[j];
+                                worstJ = j;
+                            }
+                        }
+                        
+                        if (worstJ >= 0) {
+                            if (DEBUG_POINTERS) Log.v("InputDevice",
+                                    "Worst new pointer: " + worstJ
+                                    + " (distance=" + worstDistance + ")");
+                            if (assignPointer(worstJ, false)) {
+                                // In this case there is no last pointer
+                                // remaining for this new one!
+                                next2Last[worstJ] = -1;
+                            }
+                        }
+                    } while (numFound > 2);
+                }
+            }
+            
+            int retIndex = -1;
+            
+            if (lastNumPointers < nextNumPointers) {
+                // We have one or more new pointers that are down.  Create a
+                // new pointer identifier for one of them.
+                if (DEBUG_POINTERS) Log.v("InputDevice", "Adding new pointer");
+                int nextId = 0;
+                int i=0;
+                while (i < lastNumPointers) {
+                    if (mPointerIds[i] > nextId) {
+                        // Found a hole, insert the pointer here.
+                        if (DEBUG_POINTERS) Log.v("InputDevice",
+                                "Inserting new pointer at hole " + i);
+                        System.arraycopy(mPointerIds, i, mPointerIds,
+                                i+1, lastNumPointers-i);
+                        System.arraycopy(lastData, i*MotionEvent.NUM_SAMPLE_DATA,
+                                lastData, (i+1)*MotionEvent.NUM_SAMPLE_DATA,
+                                (lastNumPointers-i)*MotionEvent.NUM_SAMPLE_DATA);
+                        break;
+                    }
+                    i++;
+                    nextId++;
+                }
+                
+                if (DEBUG_POINTERS) Log.v("InputDevice",
+                        "New pointer id " + nextId + " at index " + i);
+                
+                mLastNumPointers++;
+                retIndex = i;
+                mPointerIds[i] = nextId;
+                
+                // And assign this identifier to the first new pointer.
+                for (int j=0; j<nextNumPointers; j++) {
+                    if (next2Last[j] < 0) {
+                        if (DEBUG_POINTERS) Log.v("InputDevice",
+                                "Assigning new id to new pointer index " + j);
+                        next2Last[j] = i;
+                        break;
+                    }
+                }
+            }
+            
+            // Propagate all of the current data into the appropriate
+            // location in the old data to match the pointer ID that was
+            // assigned to it.
+            for (int i=0; i<nextNumPointers; i++) {
+                int lastIndex = next2Last[i];
+                if (lastIndex >= 0) {
+                    if (DEBUG_POINTERS) Log.v("InputDevice",
+                            "Copying next pointer index " + i
+                            + " to last index " + lastIndex);
+                    System.arraycopy(nextData, i*MotionEvent.NUM_SAMPLE_DATA,
+                            lastData, lastIndex*MotionEvent.NUM_SAMPLE_DATA,
+                            MotionEvent.NUM_SAMPLE_DATA);
+                }
+            }
+            
+            if (lastNumPointers > nextNumPointers) {
+                // One or more pointers has gone up.  Find the first one,
+                // and adjust accordingly.
+                if (DEBUG_POINTERS) Log.v("InputDevice", "Removing old pointer");
+                for (int i=0; i<lastNumPointers; i++) {
+                    if (last2Next[i] == -1) {
+                        if (DEBUG_POINTERS) Log.v("InputDevice",
+                                "Removing old pointer at index " + i);
+                        retIndex = i;
+                        break;
+                    }
+                }
+            }
+            
+            return retIndex;
+        }
+        
+        void removeOldPointer(int index) {
+            final int lastNumPointers = mLastNumPointers;
+            if (index >= 0 && index < lastNumPointers) {
+                System.arraycopy(mPointerIds, index+1, mPointerIds,
+                        index, lastNumPointers-index-1);
+                System.arraycopy(mLastData, (index+1)*MotionEvent.NUM_SAMPLE_DATA,
+                        mLastData, (index)*MotionEvent.NUM_SAMPLE_DATA,
+                        (lastNumPointers-index-1)*MotionEvent.NUM_SAMPLE_DATA);
+                mLastNumPointers--;
+            }
         }
         
         MotionEvent generateAbsMotion(InputDevice device, long curTime,
                 long curTimeNano, Display display, int orientation,
                 int metaState) {
             
-            final float[] scaled = mReportData;
-            final int[] cur = mCurData;
-            
-            boolean anyDown = false;
-            int firstDownChanged = -1;
-            int numPointers = 0;
-            for (int i=0; i<MAX_POINTERS; i++) {
-                boolean d = mDown[i];
-                anyDown |= d;
-                if (d != mLastDown[i] && firstDownChanged < 0) {
-                    firstDownChanged = i;
-                    mLastDown[i] = mDown[i];
-                    d = true;
-                }
-                
-                if (d) {
-                    final int src = i * MotionEvent.NUM_SAMPLE_DATA;
-                    final int dest = numPointers * MotionEvent.NUM_SAMPLE_DATA;
-                    numPointers++;
-                    scaled[dest + MotionEvent.SAMPLE_X] = cur[src + MotionEvent.SAMPLE_X];
-                    scaled[dest + MotionEvent.SAMPLE_Y] = cur[src + MotionEvent.SAMPLE_Y];
-                    scaled[dest + MotionEvent.SAMPLE_PRESSURE] = cur[src + MotionEvent.SAMPLE_PRESSURE];
-                    scaled[dest + MotionEvent.SAMPLE_SIZE] = cur[src + MotionEvent.SAMPLE_SIZE];
-                }
+            if (mNextNumPointers <= 0 && mLastNumPointers <= 0) {
+                return null;
             }
             
-            if (numPointers <= 0) {
-                return null;
+            final int lastNumPointers = mLastNumPointers;
+            final int nextNumPointers = mNextNumPointers;
+            if (mNextNumPointers > MAX_POINTERS) {
+                Log.w("InputDevice", "Number of pointers " + mNextNumPointers
+                        + " exceeded maximum of " + MAX_POINTERS);
+                mNextNumPointers = MAX_POINTERS;
+            }
+            
+            int upOrDownPointer = updatePointerIdentifiers();
+            
+            final float[] reportData = mReportData;
+            final int[] rawData = mLastData;
+            
+            final int numPointers = mLastNumPointers;
+            
+            if (DEBUG_POINTERS) Log.v("InputDevice", "Processing "
+                    + numPointers + " pointers (going from " + lastNumPointers
+                    + " to " + nextNumPointers + ")");
+            
+            for (int i=0; i<numPointers; i++) {
+                final int pos = i * MotionEvent.NUM_SAMPLE_DATA;
+                reportData[pos + MotionEvent.SAMPLE_X] = rawData[pos + MotionEvent.SAMPLE_X];
+                reportData[pos + MotionEvent.SAMPLE_Y] = rawData[pos + MotionEvent.SAMPLE_Y];
+                reportData[pos + MotionEvent.SAMPLE_PRESSURE] = rawData[pos + MotionEvent.SAMPLE_PRESSURE];
+                reportData[pos + MotionEvent.SAMPLE_SIZE] = rawData[pos + MotionEvent.SAMPLE_SIZE];
             }
             
             int action;
             int edgeFlags = 0;
-            if (anyDown != mLastAnyDown) {
-                final AbsoluteInfo absX = device.absX;
-                final AbsoluteInfo absY = device.absY;
-                if (anyDown && absX != null && absY != null) {
-                    // We don't let downs start unless we are
-                    // inside of the screen.  There are two reasons for
-                    // this: to avoid spurious touches when holding
-                    // the edges of the device near the touchscreen,
-                    // and to avoid reporting events if there are virtual
-                    // keys on the touchscreen outside of the display
-                    // area.
-                    // Note that we are only looking at the first pointer,
-                    // since what we are handling here is the first pointer
-                    // going down, and this is the coordinate that will be
-                    // used to dispatch the event.
-                    if (cur[MotionEvent.SAMPLE_X] < absX.minValue
-                            || cur[MotionEvent.SAMPLE_X] > absX.maxValue
-                            || cur[MotionEvent.SAMPLE_Y] < absY.minValue
-                            || cur[MotionEvent.SAMPLE_Y] > absY.maxValue) {
-                        if (false) Log.v("InputDevice", "Rejecting ("
-                                + cur[MotionEvent.SAMPLE_X] + ","
-                                + cur[MotionEvent.SAMPLE_Y] + "): outside of ("
-                                + absX.minValue + "," + absY.minValue
-                                + ")-(" + absX.maxValue + ","
-                                + absY.maxValue + ")");
-                        return null;
+            if (nextNumPointers != lastNumPointers) {
+                if (nextNumPointers > lastNumPointers) {
+                    if (lastNumPointers == 0) {
+                        action = MotionEvent.ACTION_DOWN;
+                        mDownTime = curTime;
+                    } else {
+                        action = MotionEvent.ACTION_POINTER_DOWN
+                                | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT);
                     }
-                }
-                mLastAnyDown = anyDown;
-                if (anyDown) {
-                    action = MotionEvent.ACTION_DOWN;
-                    mDownTime = curTime;
                 } else {
-                    action = MotionEvent.ACTION_UP;
-                }
-                currentMove = null;
-            } else if (firstDownChanged >= 0) {
-                if (mDown[firstDownChanged]) {
-                    action = MotionEvent.ACTION_POINTER_DOWN
-                            | (firstDownChanged << MotionEvent.ACTION_POINTER_SHIFT);
-                } else {
-                    action = MotionEvent.ACTION_POINTER_UP
-                            | (firstDownChanged << MotionEvent.ACTION_POINTER_SHIFT);
+                    if (numPointers == 1) {
+                        action = MotionEvent.ACTION_UP;
+                    } else {
+                        action = MotionEvent.ACTION_POINTER_UP
+                                | (upOrDownPointer << MotionEvent.ACTION_POINTER_ID_SHIFT);
+                    }
                 }
                 currentMove = null;
             } else {
@@ -170,42 +388,42 @@
                 final int j = i * MotionEvent.NUM_SAMPLE_DATA;
             
                 if (absX != null) {
-                    scaled[j + MotionEvent.SAMPLE_X] =
-                            ((scaled[j + MotionEvent.SAMPLE_X]-absX.minValue)
+                    reportData[j + MotionEvent.SAMPLE_X] =
+                            ((reportData[j + MotionEvent.SAMPLE_X]-absX.minValue)
                                 / absX.range) * w;
                 }
                 if (absY != null) {
-                    scaled[j + MotionEvent.SAMPLE_Y] =
-                            ((scaled[j + MotionEvent.SAMPLE_Y]-absY.minValue)
+                    reportData[j + MotionEvent.SAMPLE_Y] =
+                            ((reportData[j + MotionEvent.SAMPLE_Y]-absY.minValue)
                                 / absY.range) * h;
                 }
                 if (absPressure != null) {
-                    scaled[j + MotionEvent.SAMPLE_PRESSURE] = 
-                            ((scaled[j + MotionEvent.SAMPLE_PRESSURE]-absPressure.minValue)
+                    reportData[j + MotionEvent.SAMPLE_PRESSURE] = 
+                            ((reportData[j + MotionEvent.SAMPLE_PRESSURE]-absPressure.minValue)
                                 / (float)absPressure.range);
                 }
                 if (absSize != null) {
-                    scaled[j + MotionEvent.SAMPLE_SIZE] = 
-                            ((scaled[j + MotionEvent.SAMPLE_SIZE]-absSize.minValue)
+                    reportData[j + MotionEvent.SAMPLE_SIZE] = 
+                            ((reportData[j + MotionEvent.SAMPLE_SIZE]-absSize.minValue)
                                 / (float)absSize.range);
                 }
                 
                 switch (orientation) {
                     case Surface.ROTATION_90: {
-                        final float temp = scaled[MotionEvent.SAMPLE_X];
-                        scaled[j + MotionEvent.SAMPLE_X] = scaled[j + MotionEvent.SAMPLE_Y];
-                        scaled[j + MotionEvent.SAMPLE_Y] = w-temp;
+                        final float temp = reportData[MotionEvent.SAMPLE_X];
+                        reportData[j + MotionEvent.SAMPLE_X] = reportData[j + MotionEvent.SAMPLE_Y];
+                        reportData[j + MotionEvent.SAMPLE_Y] = w-temp;
                         break;
                     }
                     case Surface.ROTATION_180: {
-                        scaled[j + MotionEvent.SAMPLE_X] = w-scaled[j + MotionEvent.SAMPLE_X];
-                        scaled[j + MotionEvent.SAMPLE_Y] = h-scaled[j + MotionEvent.SAMPLE_Y];
+                        reportData[j + MotionEvent.SAMPLE_X] = w-reportData[j + MotionEvent.SAMPLE_X];
+                        reportData[j + MotionEvent.SAMPLE_Y] = h-reportData[j + MotionEvent.SAMPLE_Y];
                         break;
                     }
                     case Surface.ROTATION_270: {
-                        final float temp = scaled[i + MotionEvent.SAMPLE_X];
-                        scaled[j + MotionEvent.SAMPLE_X] = h-scaled[j + MotionEvent.SAMPLE_Y];
-                        scaled[j + MotionEvent.SAMPLE_Y] = temp;
+                        final float temp = reportData[i + MotionEvent.SAMPLE_X];
+                        reportData[j + MotionEvent.SAMPLE_X] = h-reportData[j + MotionEvent.SAMPLE_Y];
+                        reportData[j + MotionEvent.SAMPLE_Y] = temp;
                         break;
                     }
                 }
@@ -214,24 +432,24 @@
             // We only consider the first pointer when computing the edge
             // flags, since they are global to the event.
             if (action == MotionEvent.ACTION_DOWN) {
-                if (scaled[MotionEvent.SAMPLE_X] <= 0) {
+                if (reportData[MotionEvent.SAMPLE_X] <= 0) {
                     edgeFlags |= MotionEvent.EDGE_LEFT;
-                } else if (scaled[MotionEvent.SAMPLE_X] >= dispW) {
+                } else if (reportData[MotionEvent.SAMPLE_X] >= dispW) {
                     edgeFlags |= MotionEvent.EDGE_RIGHT;
                 }
-                if (scaled[MotionEvent.SAMPLE_Y] <= 0) {
+                if (reportData[MotionEvent.SAMPLE_Y] <= 0) {
                     edgeFlags |= MotionEvent.EDGE_TOP;
-                } else if (scaled[MotionEvent.SAMPLE_Y] >= dispH) {
+                } else if (reportData[MotionEvent.SAMPLE_Y] >= dispH) {
                     edgeFlags |= MotionEvent.EDGE_BOTTOM;
                 }
             }
             
             if (currentMove != null) {
                 if (false) Log.i("InputDevice", "Adding batch x="
-                        + scaled[MotionEvent.SAMPLE_X]
-                        + " y=" + scaled[MotionEvent.SAMPLE_Y]
+                        + reportData[MotionEvent.SAMPLE_X]
+                        + " y=" + reportData[MotionEvent.SAMPLE_Y]
                         + " to " + currentMove);
-                currentMove.addBatch(curTime, scaled, metaState);
+                currentMove.addBatch(curTime, reportData, metaState);
                 if (WindowManagerPolicy.WATCH_POINTER) {
                     Log.i("KeyInputQueue", "Updating: " + currentMove);
                 }
@@ -239,37 +457,53 @@
             }
             
             MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime,
-                    curTimeNano, action, numPointers, scaled, metaState,
-                    xPrecision, yPrecision, device.id, edgeFlags);
+                    curTimeNano, action, numPointers, mPointerIds, reportData,
+                    metaState, xPrecision, yPrecision, device.id, edgeFlags);
             if (action == MotionEvent.ACTION_MOVE) {
                 currentMove = me;
             }
+            
+            if (nextNumPointers < lastNumPointers) {
+                removeOldPointer(upOrDownPointer);
+            }
+            
             return me;
         }
         
+        boolean hasMore() {
+            return mLastNumPointers != mNextNumPointers;
+        }
+        
+        void finish() {
+            mNextNumPointers = mAddingPointerOffset = 0;
+            mNextData[MotionEvent.SAMPLE_PRESSURE] = 0;
+        }
+        
         MotionEvent generateRelMotion(InputDevice device, long curTime,
                 long curTimeNano, int orientation, int metaState) {
             
             final float[] scaled = mReportData;
             
             // For now we only support 1 pointer with relative motions.
-            scaled[MotionEvent.SAMPLE_X] = mCurData[MotionEvent.SAMPLE_X];
-            scaled[MotionEvent.SAMPLE_Y] = mCurData[MotionEvent.SAMPLE_Y];
+            scaled[MotionEvent.SAMPLE_X] = mNextData[MotionEvent.SAMPLE_X];
+            scaled[MotionEvent.SAMPLE_Y] = mNextData[MotionEvent.SAMPLE_Y];
             scaled[MotionEvent.SAMPLE_PRESSURE] = 1.0f;
             scaled[MotionEvent.SAMPLE_SIZE] = 0;
             int edgeFlags = 0;
             
             int action;
-            if (mDown[0] != mLastDown[0]) {
-                mCurData[MotionEvent.SAMPLE_X] =
-                        mCurData[MotionEvent.SAMPLE_Y] = 0;
-                mLastDown[0] = mDown[0];
-                if (mDown[0]) {
+            if (mNextNumPointers != mLastNumPointers) {
+                mNextData[MotionEvent.SAMPLE_X] =
+                        mNextData[MotionEvent.SAMPLE_Y] = 0;
+                if (mNextNumPointers > 0 && mLastNumPointers == 0) {
                     action = MotionEvent.ACTION_DOWN;
                     mDownTime = curTime;
-                } else {
+                } else if (mNextNumPointers == 0) {
                     action = MotionEvent.ACTION_UP;
+                } else {
+                    action = MotionEvent.ACTION_MOVE;
                 }
+                mLastNumPointers = mNextNumPointers;
                 currentMove = null;
             } else {
                 action = MotionEvent.ACTION_MOVE;
@@ -310,7 +544,7 @@
             }
             
             MotionEvent me = MotionEvent.obtainNano(mDownTime, curTime,
-                    curTimeNano, action, 1, scaled, metaState,
+                    curTimeNano, action, 1, mPointerIds, scaled, metaState,
                     xPrecision, yPrecision, device.id, edgeFlags);
             if (action == MotionEvent.ACTION_MOVE) {
                 currentMove = me;
diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index e0ee7ed..cfb3e35 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -49,6 +49,7 @@
     static final String TAG = "KeyInputQueue";
 
     static final boolean DEBUG_VIRTUAL_KEYS = false;
+    static final boolean DEBUG_POINTERS = false;
     
     private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
 
@@ -326,6 +327,10 @@
                         config.navigation
                                 = Configuration.NAVIGATION_TRACKBALL;
                         //Log.i("foo", "***** HAVE TRACKBALL!");
+                    } else if ((d.classes&RawInputEvent.CLASS_DPAD) != 0) {
+                        config.navigation
+                                = Configuration.NAVIGATION_DPAD;
+                        //Log.i("foo", "***** HAVE DPAD!");
                     }
                 }
             }
@@ -364,9 +369,9 @@
             android.os.Process.setThreadPriority(
                     android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
             
-            try {
-                RawInputEvent ev = new RawInputEvent();
-                while (true) {
+            RawInputEvent ev = new RawInputEvent();
+            while (true) {
+                try {
                     InputDevice di;
 
                     // block, doesn't release the monitor
@@ -465,49 +470,81 @@
                                              ? KeyEvent.FLAG_WOKE_HERE : 0));
                         } else if (ev.type == RawInputEvent.EV_KEY) {
                             if (ev.scancode == RawInputEvent.BTN_TOUCH &&
-                                    (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
+                                    (classes&(RawInputEvent.CLASS_TOUCHSCREEN
+                                            |RawInputEvent.CLASS_TOUCHSCREEN_MT))
+                                            == RawInputEvent.CLASS_TOUCHSCREEN) {
                                 di.mAbs.changed = true;
                                 di.mAbs.mDown[0] = ev.value != 0;
                             } else if (ev.scancode == RawInputEvent.BTN_2 &&
-                                    (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
+                                    (classes&(RawInputEvent.CLASS_TOUCHSCREEN
+                                            |RawInputEvent.CLASS_TOUCHSCREEN_MT))
+                                            == RawInputEvent.CLASS_TOUCHSCREEN) {
                                 di.mAbs.changed = true;
                                 di.mAbs.mDown[1] = ev.value != 0;
                             } else if (ev.scancode == RawInputEvent.BTN_MOUSE &&
                                     (classes&RawInputEvent.CLASS_TRACKBALL) != 0) {
                                 di.mRel.changed = true;
-                                di.mRel.mDown[0] = ev.value != 0;
+                                di.mRel.mNextNumPointers = ev.value != 0 ? 1 : 0;
                                 send = true;
                             }
     
                         } else if (ev.type == RawInputEvent.EV_ABS &&
+                                (classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
+                            if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) {
+                                di.mAbs.changed = true;
+                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                        + MotionEvent.SAMPLE_PRESSURE] = ev.value;
+                            } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_X) {
+                                di.mAbs.changed = true;
+                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                    + MotionEvent.SAMPLE_X] = ev.value;
+                                if (DEBUG_POINTERS) Log.v(TAG, "MT @"
+                                        + di.mAbs.mAddingPointerOffset
+                                        + " X:" + ev.value);
+                            } else if (ev.scancode == RawInputEvent.ABS_MT_POSITION_Y) {
+                                di.mAbs.changed = true;
+                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                    + MotionEvent.SAMPLE_Y] = ev.value;
+                                if (DEBUG_POINTERS) Log.v(TAG, "MT @"
+                                        + di.mAbs.mAddingPointerOffset
+                                        + " Y:" + ev.value);
+                            } else if (ev.scancode == RawInputEvent.ABS_MT_WIDTH_MAJOR) {
+                                di.mAbs.changed = true;
+                                di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                    + MotionEvent.SAMPLE_SIZE] = ev.value;
+                            }
+                            
+                        } else if (ev.type == RawInputEvent.EV_ABS &&
                                 (classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
                             // Finger 1
                             if (ev.scancode == RawInputEvent.ABS_X) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.SAMPLE_X] = ev.value;
+                                di.mAbs.mNextData[MotionEvent.SAMPLE_X] = ev.value;
                             } else if (ev.scancode == RawInputEvent.ABS_Y) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.SAMPLE_Y] = ev.value;
+                                di.mAbs.mNextData[MotionEvent.SAMPLE_Y] = ev.value;
                             } else if (ev.scancode == RawInputEvent.ABS_PRESSURE) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.SAMPLE_PRESSURE] = ev.value;
-                                di.mAbs.mCurData[MotionEvent.NUM_SAMPLE_DATA
+                                di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] = ev.value;
+                                di.mAbs.mNextData[MotionEvent.NUM_SAMPLE_DATA
                                                  + MotionEvent.SAMPLE_PRESSURE] = ev.value;
                             } else if (ev.scancode == RawInputEvent.ABS_TOOL_WIDTH) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.SAMPLE_SIZE] = ev.value;
-                                di.mAbs.mCurData[MotionEvent.NUM_SAMPLE_DATA
+                                di.mAbs.mNextData[MotionEvent.SAMPLE_SIZE] = ev.value;
+                                di.mAbs.mNextData[MotionEvent.NUM_SAMPLE_DATA
                                                  + MotionEvent.SAMPLE_SIZE] = ev.value;
 
                             // Finger 2
                             } else if (ev.scancode == RawInputEvent.ABS_HAT0X) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.NUM_SAMPLE_DATA
-                                                 + MotionEvent.SAMPLE_X] = ev.value;
+                                di.mAbs.mNextData[(di.mAbs.mDown[0] ?
+                                        MotionEvent.NUM_SAMPLE_DATA : 0)
+                                         + MotionEvent.SAMPLE_X] = ev.value;
                             } else if (ev.scancode == RawInputEvent.ABS_HAT0Y) {
                                 di.mAbs.changed = true;
-                                di.mAbs.mCurData[MotionEvent.NUM_SAMPLE_DATA
-                                                 + MotionEvent.SAMPLE_Y] = ev.value;
+                                di.mAbs.mNextData[(di.mAbs.mDown[0] ?
+                                        MotionEvent.NUM_SAMPLE_DATA : 0)
+                                        + MotionEvent.SAMPLE_Y] = ev.value;
                             }
     
                         } else if (ev.type == RawInputEvent.EV_REL &&
@@ -515,14 +552,40 @@
                             // Add this relative movement into our totals.
                             if (ev.scancode == RawInputEvent.REL_X) {
                                 di.mRel.changed = true;
-                                di.mRel.mCurData[MotionEvent.SAMPLE_X] += ev.value;
+                                di.mRel.mNextData[MotionEvent.SAMPLE_X] += ev.value;
                             } else if (ev.scancode == RawInputEvent.REL_Y) {
                                 di.mRel.changed = true;
-                                di.mRel.mCurData[MotionEvent.SAMPLE_Y] += ev.value;
+                                di.mRel.mNextData[MotionEvent.SAMPLE_Y] += ev.value;
                             }
                         }
                         
-                        if (send || ev.type == RawInputEvent.EV_SYN) {
+                        if (ev.type == RawInputEvent.EV_SYN
+                                && ev.scancode == RawInputEvent.SYN_MT_REPORT
+                                && di.mAbs != null) {
+                            di.mAbs.changed = true;
+                            if (di.mAbs.mNextData[MotionEvent.SAMPLE_PRESSURE] > 0) {
+                                // If the value is <= 0, the pointer is not
+                                // down, so keep it in the count.
+                                
+                                if (di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+                                                      + MotionEvent.SAMPLE_PRESSURE] != 0) {
+                                    final int num = di.mAbs.mNextNumPointers+1;
+                                    di.mAbs.mNextNumPointers = num;
+                                    if (DEBUG_POINTERS) Log.v(TAG,
+                                            "MT_REPORT: now have " + num + " pointers");
+                                    final int newOffset = (num <= InputDevice.MAX_POINTERS)
+                                            ? (num * MotionEvent.NUM_SAMPLE_DATA)
+                                            : (InputDevice.MAX_POINTERS *
+                                                    MotionEvent.NUM_SAMPLE_DATA);
+                                    di.mAbs.mAddingPointerOffset = newOffset;
+                                    di.mAbs.mNextData[newOffset
+                                            + MotionEvent.SAMPLE_PRESSURE] = 0;
+                                } else {
+                                    if (DEBUG_POINTERS) Log.v(TAG, "MT_REPORT: no pointer");
+                                }
+                            }
+                        } else if (send || (ev.type == RawInputEvent.EV_SYN
+                                && ev.scancode == RawInputEvent.SYN_REPORT)) {
                             if (mDisplay != null) {
                                 if (!mHaveGlobalMetaState) {
                                     computeGlobalMetaStateLocked();
@@ -534,72 +597,21 @@
                                 if (ms.changed) {
                                     ms.changed = false;
                                     
-                                    boolean doMotion = true;
-                                    
-                                    // Look for virtual buttons.
-                                    VirtualKey vk = mPressedVirtualKey;
-                                    if (vk != null) {
-                                        doMotion = false;
-                                        if (!ms.mDown[0]) {
-                                            mPressedVirtualKey = null;
-                                            ms.mLastDown[0] = ms.mDown[0];
-                                            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
-                                                    "Generate key up for: " + vk.scancode);
-                                            KeyEvent event = newKeyEvent(di,
-                                                    di.mKeyDownTime, curTime, false,
-                                                    vk.lastKeycode,
-                                                    0, vk.scancode,
-                                                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
-                                            mHapticFeedbackCallback.virtualKeyFeedback(event);
-                                            addLocked(di, curTimeNano, ev.flags,
-                                                    RawInputEvent.CLASS_KEYBOARD,
-                                                    event);
-                                        } else if (isInsideDisplay(di)) {
-                                            // Whoops the pointer has moved into
-                                            // the display area!  Cancel the
-                                            // virtual key and start a pointer
-                                            // motion.
-                                            mPressedVirtualKey = null;
-                                            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
-                                                    "Cancel key up for: " + vk.scancode);
-                                            KeyEvent event = newKeyEvent(di,
-                                                    di.mKeyDownTime, curTime, false,
-                                                    vk.lastKeycode,
-                                                    0, vk.scancode,
-                                                    KeyEvent.FLAG_CANCELED |
-                                                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
-                                            mHapticFeedbackCallback.virtualKeyFeedback(event);
-                                            addLocked(di, curTimeNano, ev.flags,
-                                                    RawInputEvent.CLASS_KEYBOARD,
-                                                    event);
-                                            doMotion = true;
-                                            for (int i=InputDevice.MAX_POINTERS-1; i>=0; i--) {
-                                                ms.mLastDown[i] = false;
-                                            }
-                                        }
+                                    if ((classes&(RawInputEvent.CLASS_TOUCHSCREEN
+                                            |RawInputEvent.CLASS_TOUCHSCREEN_MT))
+                                            == RawInputEvent.CLASS_TOUCHSCREEN) {
+                                        ms.mNextNumPointers = 0;
+                                        if (ms.mDown[0]) ms.mNextNumPointers++;
+                                        if (ms.mDown[1]) ms.mNextNumPointers++;
                                     }
-                                    if (doMotion && ms.mDown[0] && !ms.mLastDown[0]) {
-                                        vk = findSoftButton(di);
-                                        if (vk != null) {
-                                            doMotion = false;
-                                            mPressedVirtualKey = vk;
-                                            vk.lastKeycode = scancodeToKeycode(
-                                                    di.id, vk.scancode);
-                                            ms.mLastDown[0] = ms.mDown[0];
-                                            di.mKeyDownTime = curTime;
-                                            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
-                                                    "Generate key down for: " + vk.scancode
-                                                    + " (keycode=" + vk.lastKeycode + ")");
-                                            KeyEvent event = newKeyEvent(di,
-                                                    di.mKeyDownTime, curTime, true,
-                                                    vk.lastKeycode, 0,
-                                                    vk.scancode,
-                                                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
-                                            mHapticFeedbackCallback.virtualKeyFeedback(event);
-                                            addLocked(di, curTimeNano, ev.flags,
-                                                    RawInputEvent.CLASS_KEYBOARD,
-                                                    event);
-                                        }
+                                    
+                                    boolean doMotion = !monitorVirtualKey(di,
+                                            ev, curTime, curTimeNano);
+                                    
+                                    if (doMotion && ms.mNextNumPointers > 0
+                                            && ms.mLastNumPointers == 0) {
+                                        doMotion = !generateVirtualKeyDown(di,
+                                                ev, curTime, curTimeNano);
                                     }
                                     
                                     if (doMotion) {
@@ -607,22 +619,26 @@
                                         // multiple events here, for example
                                         // if two fingers change up/down state
                                         // at the same time.
-                                        me = ms.generateAbsMotion(di, curTime,
-                                                curTimeNano, mDisplay,
-                                                mOrientation, mGlobalMetaState);
-                                        if (false) Log.v(TAG, "Absolute: x="
-                                                + di.mAbs.mCurData[MotionEvent.SAMPLE_X]
-                                                + " y="
-                                                + di.mAbs.mCurData[MotionEvent.SAMPLE_Y]
-                                                + " ev=" + me);
-                                        if (me != null) {
-                                            if (WindowManagerPolicy.WATCH_POINTER) {
-                                                Log.i(TAG, "Enqueueing: " + me);
+                                        do {
+                                            me = ms.generateAbsMotion(di, curTime,
+                                                    curTimeNano, mDisplay,
+                                                    mOrientation, mGlobalMetaState);
+                                            if (false) Log.v(TAG, "Absolute: x="
+                                                    + di.mAbs.mNextData[MotionEvent.SAMPLE_X]
+                                                    + " y="
+                                                    + di.mAbs.mNextData[MotionEvent.SAMPLE_Y]
+                                                    + " ev=" + me);
+                                            if (me != null) {
+                                                if (WindowManagerPolicy.WATCH_POINTER) {
+                                                    Log.i(TAG, "Enqueueing: " + me);
+                                                }
+                                                addLocked(di, curTimeNano, ev.flags,
+                                                        RawInputEvent.CLASS_TOUCHSCREEN, me);
                                             }
-                                            addLocked(di, curTimeNano, ev.flags,
-                                                    RawInputEvent.CLASS_TOUCHSCREEN, me);
-                                        }
+                                        } while (ms.hasMore());
                                     }
+                                    
+                                    ms.finish();
                                 }
                                 
                                 ms = di.mRel;
@@ -633,22 +649,24 @@
                                             curTimeNano,
                                             mOrientation, mGlobalMetaState);
                                     if (false) Log.v(TAG, "Relative: x="
-                                            + di.mRel.mCurData[MotionEvent.SAMPLE_X]
+                                            + di.mRel.mNextData[MotionEvent.SAMPLE_X]
                                             + " y="
-                                            + di.mRel.mCurData[MotionEvent.SAMPLE_Y]
+                                            + di.mRel.mNextData[MotionEvent.SAMPLE_Y]
                                             + " ev=" + me);
                                     if (me != null) {
                                         addLocked(di, curTimeNano, ev.flags,
                                                 RawInputEvent.CLASS_TRACKBALL, me);
                                     }
+                                    
+                                    ms.finish();
                                 }
                             }
                         }
                     }
-                }
                 
-            } catch (RuntimeException exc) {
-                Log.e(TAG, "InputReaderThread uncaught exception", exc);
+                } catch (RuntimeException exc) {
+                    Log.e(TAG, "InputReaderThread uncaught exception", exc);
+                }
             }
         }
     };
@@ -661,13 +679,13 @@
             return true;
         }
         
-        if (absm.mCurData[MotionEvent.SAMPLE_X] >= absx.minValue
-                && absm.mCurData[MotionEvent.SAMPLE_X] <= absx.maxValue
-                && absm.mCurData[MotionEvent.SAMPLE_Y] >= absy.minValue
-                && absm.mCurData[MotionEvent.SAMPLE_Y] <= absy.maxValue) {
+        if (absm.mNextData[MotionEvent.SAMPLE_X] >= absx.minValue
+                && absm.mNextData[MotionEvent.SAMPLE_X] <= absx.maxValue
+                && absm.mNextData[MotionEvent.SAMPLE_Y] >= absy.minValue
+                && absm.mNextData[MotionEvent.SAMPLE_Y] <= absy.maxValue) {
             if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Input ("
-                    + absm.mCurData[MotionEvent.SAMPLE_X]
-                    + "," + absm.mCurData[MotionEvent.SAMPLE_Y]
+                    + absm.mNextData[MotionEvent.SAMPLE_X]
+                    + "," + absm.mNextData[MotionEvent.SAMPLE_Y]
                     + ") inside of display");
             return true;
         }
@@ -675,28 +693,24 @@
         return false;
     }
     
-    private VirtualKey findSoftButton(InputDevice dev) {
+    private VirtualKey findVirtualKey(InputDevice dev) {
         final int N = mVirtualKeys.size();
         if (N <= 0) {
             return null;
         }
         
-        if (isInsideDisplay(dev)) {
-            return null;
-        }
-        
         final InputDevice.MotionState absm = dev.mAbs;
         for (int i=0; i<N; i++) {
             VirtualKey sb = mVirtualKeys.get(i);
             sb.computeHitRect(dev, mDisplayWidth, mDisplayHeight);
             if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit test ("
-                    + absm.mCurData[MotionEvent.SAMPLE_X] + ","
-                    + absm.mCurData[MotionEvent.SAMPLE_Y] + ") in code "
+                    + absm.mNextData[MotionEvent.SAMPLE_X] + ","
+                    + absm.mNextData[MotionEvent.SAMPLE_Y] + ") in code "
                     + sb.scancode + " - (" + sb.hitLeft
                     + "," + sb.hitTop + ")-(" + sb.hitRight + ","
                     + sb.hitBottom + ")");
-            if (sb.checkHit(absm.mCurData[MotionEvent.SAMPLE_X],
-                    absm.mCurData[MotionEvent.SAMPLE_Y])) {
+            if (sb.checkHit(absm.mNextData[MotionEvent.SAMPLE_X],
+                    absm.mNextData[MotionEvent.SAMPLE_Y])) {
                 if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Hit!");
                 return sb;
             }
@@ -705,6 +719,97 @@
         return null;
     }
     
+    private boolean generateVirtualKeyDown(InputDevice di, RawInputEvent ev,
+            long curTime, long curTimeNano) {
+        if (isInsideDisplay(di)) {
+            // Didn't consume event.
+            return false;
+        }
+        
+        
+        VirtualKey vk = findVirtualKey(di);
+        if (vk != null) {
+            final InputDevice.MotionState ms = di.mAbs;
+            mPressedVirtualKey = vk;
+            vk.lastKeycode = scancodeToKeycode(di.id, vk.scancode);
+            ms.mLastNumPointers = ms.mNextNumPointers;
+            di.mKeyDownTime = curTime;
+            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG,
+                    "Generate key down for: " + vk.scancode
+                    + " (keycode=" + vk.lastKeycode + ")");
+            KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, true,
+                    vk.lastKeycode, 0, vk.scancode,
+                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
+            mHapticFeedbackCallback.virtualKeyFeedback(event);
+            addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD,
+                    event);
+        }
+        
+        // We always consume the event, even if we didn't
+        // generate a key event.  There are two reasons for
+        // this: to avoid spurious touches when holding
+        // the edges of the device near the touchscreen,
+        // and to avoid reporting events if there are virtual
+        // keys on the touchscreen outside of the display
+        // area.
+        // Note that for all of this we are only looking at the
+        // first pointer, since what we are handling here is the
+        // first pointer going down, and this is the coordinate
+        // that will be used to dispatch the event.
+        if (false) {
+            final InputDevice.AbsoluteInfo absx = di.absX;
+            final InputDevice.AbsoluteInfo absy = di.absY;
+            final InputDevice.MotionState absm = di.mAbs;
+            Log.v(TAG, "Rejecting ("
+                + absm.mNextData[MotionEvent.SAMPLE_X] + ","
+                + absm.mNextData[MotionEvent.SAMPLE_Y] + "): outside of ("
+                + absx.minValue + "," + absy.minValue
+                + ")-(" + absx.maxValue + ","
+                + absx.maxValue + ")");
+        }
+        return true;
+    }
+    
+    private boolean monitorVirtualKey(InputDevice di, RawInputEvent ev,
+            long curTime, long curTimeNano) {
+        VirtualKey vk = mPressedVirtualKey;
+        if (vk == null) {
+            return false;
+        }
+        
+        final InputDevice.MotionState ms = di.mAbs;
+        if (ms.mNextNumPointers <= 0) {
+            mPressedVirtualKey = null;
+            ms.mLastNumPointers = 0;
+            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Generate key up for: " + vk.scancode);
+            KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false,
+                    vk.lastKeycode, 0, vk.scancode,
+                    KeyEvent.FLAG_VIRTUAL_HARD_KEY);
+            mHapticFeedbackCallback.virtualKeyFeedback(event);
+            addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD,
+                    event);
+            return true;
+            
+        } else if (isInsideDisplay(di)) {
+            // Whoops the pointer has moved into
+            // the display area!  Cancel the
+            // virtual key and start a pointer
+            // motion.
+            mPressedVirtualKey = null;
+            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Cancel key up for: " + vk.scancode);
+            KeyEvent event = newKeyEvent(di, di.mKeyDownTime, curTime, false,
+                    vk.lastKeycode, 0, vk.scancode,
+                    KeyEvent.FLAG_CANCELED | KeyEvent.FLAG_VIRTUAL_HARD_KEY);
+            mHapticFeedbackCallback.virtualKeyFeedback(event);
+            addLocked(di, curTimeNano, ev.flags, RawInputEvent.CLASS_KEYBOARD,
+                    event);
+            ms.mLastNumPointers = 0;
+            return false;
+        }
+        
+        return true;
+    }
+    
     /**
      * Returns a new meta state for the given keys and old state.
      */
@@ -851,8 +956,8 @@
             if (ev.event == ev.inputDevice.mRel.currentMove) {
                 if (false) Log.i(TAG, "Detach rel " + ev.event);
                 ev.inputDevice.mRel.currentMove = null;
-                ev.inputDevice.mRel.mCurData[MotionEvent.SAMPLE_X] = 0;
-                ev.inputDevice.mRel.mCurData[MotionEvent.SAMPLE_Y] = 0;
+                ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_X] = 0;
+                ev.inputDevice.mRel.mNextData[MotionEvent.SAMPLE_Y] = 0;
             }
             recycleLocked(ev);
         }
@@ -945,7 +1050,12 @@
         InputDevice.AbsoluteInfo absY;
         InputDevice.AbsoluteInfo absPressure;
         InputDevice.AbsoluteInfo absSize;
-        if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
+        if ((classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
+            absX = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_POSITION_X, "X");
+            absY = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_POSITION_Y, "Y");
+            absPressure = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_TOUCH_MAJOR, "Pressure");
+            absSize = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_MT_WIDTH_MAJOR, "Size");
+        } else if ((classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
             absX = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_X, "X");
             absY = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_Y, "Y");
             absPressure = loadAbsoluteInfo(deviceId, RawInputEvent.ABS_PRESSURE, "Pressure");
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index edba5b6..ceb9c41 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -541,6 +541,17 @@
         mTransitionAnimationScale = Settings.System.getFloat(context.getContentResolver(),
                 Settings.System.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale);
 
+        int max_events_per_sec = 35;
+        try {
+            max_events_per_sec = Integer.parseInt(SystemProperties
+                    .get("windowsmgr.max_events_per_sec"));
+            if (max_events_per_sec < 1) {
+                max_events_per_sec = 35;
+            }
+        } catch (NumberFormatException e) {
+        }
+        mMinWaitTimeBetweenTouchEvents = 1000 / max_events_per_sec;
+
         mQueue = new KeyQ();
 
         mInputThread = new InputDispatcherThread();
@@ -4010,8 +4021,8 @@
             }
         } //end if target
 
-        // TODO remove once we settle on a value or make it app specific
-        if (action == MotionEvent.ACTION_DOWN) {
+        // Enable this for testing the "right" value
+        if (false && action == MotionEvent.ACTION_DOWN) {
             int max_events_per_sec = 35;
             try {
                 max_events_per_sec = Integer.parseInt(SystemProperties