Merge "Use Harfbuzz for Paint drawText / measureText / breakText APIs"
diff --git a/api/current.txt b/api/current.txt
index 975444e..05b7c35 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2180,7 +2180,7 @@
     method public void closeContextMenu();
     method public void closeOptionsMenu();
     method public android.app.PendingIntent createPendingResult(int, android.content.Intent, int);
-    method public final void dismissDialog(int);
+    method public final deprecated void dismissDialog(int);
     method public boolean dispatchGenericMotionEvent(android.view.MotionEvent);
     method public boolean dispatchKeyEvent(android.view.KeyEvent);
     method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent);
@@ -2202,7 +2202,7 @@
     method public android.view.View getCurrentFocus();
     method public android.app.FragmentManager getFragmentManager();
     method public android.content.Intent getIntent();
-    method public java.lang.Object getLastNonConfigurationInstance();
+    method public deprecated java.lang.Object getLastNonConfigurationInstance();
     method public android.view.LayoutInflater getLayoutInflater();
     method public android.app.LoaderManager getLoaderManager();
     method public java.lang.String getLocalClassName();
@@ -2239,7 +2239,7 @@
     method public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo);
     method public java.lang.CharSequence onCreateDescription();
     method protected deprecated android.app.Dialog onCreateDialog(int);
-    method protected android.app.Dialog onCreateDialog(int, android.os.Bundle);
+    method protected deprecated android.app.Dialog onCreateDialog(int, android.os.Bundle);
     method public boolean onCreateOptionsMenu(android.view.Menu);
     method public boolean onCreatePanelMenu(int, android.view.Menu);
     method public android.view.View onCreatePanelView(int);
@@ -2265,13 +2265,13 @@
     method protected void onPostCreate(android.os.Bundle);
     method protected void onPostResume();
     method protected deprecated void onPrepareDialog(int, android.app.Dialog);
-    method protected void onPrepareDialog(int, android.app.Dialog, android.os.Bundle);
+    method protected deprecated void onPrepareDialog(int, android.app.Dialog, android.os.Bundle);
     method public boolean onPrepareOptionsMenu(android.view.Menu);
     method public boolean onPreparePanel(int, android.view.View, android.view.Menu);
     method protected void onRestart();
     method protected void onRestoreInstanceState(android.os.Bundle);
     method protected void onResume();
-    method public java.lang.Object onRetainNonConfigurationInstance();
+    method public deprecated java.lang.Object onRetainNonConfigurationInstance();
     method protected void onSaveInstanceState(android.os.Bundle);
     method public boolean onSearchRequested();
     method protected void onStart();
@@ -2289,7 +2289,7 @@
     method public void overridePendingTransition(int, int);
     method public void recreate();
     method public void registerForContextMenu(android.view.View);
-    method public final void removeDialog(int);
+    method public final deprecated void removeDialog(int);
     method public final boolean requestWindowFeature(int);
     method public final void runOnUiThread(java.lang.Runnable);
     method public void setContentView(int);
@@ -2315,8 +2315,8 @@
     method public void setTitleColor(int);
     method public void setVisible(boolean);
     method public final void setVolumeControlStream(int);
-    method public final void showDialog(int);
-    method public final boolean showDialog(int, android.os.Bundle);
+    method public final deprecated void showDialog(int);
+    method public final deprecated boolean showDialog(int, android.os.Bundle);
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
     method public void startActivityForResult(android.content.Intent, int);
     method public void startActivityFromChild(android.app.Activity, android.content.Intent, int);
@@ -3042,12 +3042,12 @@
   }
 
   public class KeyguardManager {
-    method public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
+    method public deprecated void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
     method public boolean inKeyguardRestrictedInputMode();
-    method public android.app.KeyguardManager.KeyguardLock newKeyguardLock(java.lang.String);
+    method public deprecated android.app.KeyguardManager.KeyguardLock newKeyguardLock(java.lang.String);
   }
 
-  public class KeyguardManager.KeyguardLock {
+  public deprecated class KeyguardManager.KeyguardLock {
     method public void disableKeyguard();
     method public void reenableKeyguard();
   }
@@ -13961,7 +13961,7 @@
     field public static final int FULL_WAKE_LOCK = 26; // 0x1a
     field public static final int ON_AFTER_RELEASE = 536870912; // 0x20000000
     field public static final int PARTIAL_WAKE_LOCK = 1; // 0x1
-    field public static final int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa
+    field public static final deprecated int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa
     field public static final int SCREEN_DIM_WAKE_LOCK = 6; // 0x6
   }
 
@@ -24069,6 +24069,7 @@
     ctor public LinearLayout(android.content.Context, android.util.AttributeSet);
     ctor public LinearLayout(android.content.Context, android.util.AttributeSet, int);
     method public int getBaselineAlignedChildIndex();
+    method public int getDividerPadding();
     method public int getOrientation();
     method public int getShowDividers();
     method public float getWeightSum();
@@ -24078,6 +24079,7 @@
     method public void setBaselineAligned(boolean);
     method public void setBaselineAlignedChildIndex(int);
     method public void setDividerDrawable(android.graphics.drawable.Drawable);
+    method public void setDividerPadding(int);
     method public void setGravity(int);
     method public void setHorizontalGravity(int);
     method public void setMeasureWithLargestChildEnabled(boolean);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ed39457..389d4856 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1437,6 +1437,10 @@
     }
     
     /**
+     * @deprecated Use the new {@link Fragment} API
+     * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Retrieve the non-configuration instance data that was previously
      * returned by {@link #onRetainNonConfigurationInstance()}.  This will
      * be available from the initial {@link #onCreate} and
@@ -1453,12 +1457,17 @@
      * @return Returns the object previously returned by
      * {@link #onRetainNonConfigurationInstance()}.
      */
+    @Deprecated
     public Object getLastNonConfigurationInstance() {
         return mLastNonConfigurationInstances != null
                 ? mLastNonConfigurationInstances.activity : null;
     }
     
     /**
+     * @deprecated Use the new {@link Fragment} API
+     * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Called by the system, as part of destroying an
      * activity due to a configuration change, when it is known that a new
      * instance will immediately be created for the new configuration.  You
@@ -1674,6 +1683,10 @@
     }
 
     /**
+     * @deprecated Use the new {@link android.content.CursorLoader} class with
+     * {@link LoaderManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * This method allows the activity to take care of managing the given
      * {@link Cursor}'s lifecycle for you based on the activity's lifecycle.
      * That is, when the activity is stopped it will automatically call
@@ -1689,8 +1702,6 @@
      * 
      * @see #managedQuery(android.net.Uri , String[], String, String[], String)
      * @see #stopManagingCursor
-     *
-     * @deprecated Use {@link CursorLoader} instead.
      */
     @Deprecated
     public void startManagingCursor(Cursor c) {
@@ -1700,6 +1711,10 @@
     }
 
     /**
+     * @deprecated Use the new {@link android.content.CursorLoader} class with
+     * {@link LoaderManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Given a Cursor that was previously given to
      * {@link #startManagingCursor}, stop the activity's management of that
      * cursor.
@@ -1707,8 +1722,6 @@
      * @param c The Cursor that was being managed.
      * 
      * @see #startManagingCursor
-     *
-     * @deprecated Use {@link CursorLoader} instead.
      */
     @Deprecated
     public void stopManagingCursor(Cursor c) {
@@ -2714,6 +2727,10 @@
     }
 
     /**
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Callback for creating dialogs that are managed (saved and restored) for you
      * by the activity.  The default implementation calls through to
      * {@link #onCreateDialog(int)} for compatibility.
@@ -2742,6 +2759,7 @@
      * @see #dismissDialog(int)
      * @see #removeDialog(int)
      */
+    @Deprecated
     protected Dialog onCreateDialog(int id, Bundle args) {
         return onCreateDialog(id);
     }
@@ -2756,6 +2774,10 @@
     }
 
     /**
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Provides an opportunity to prepare a managed dialog before it is being
      * shown.  The default implementation calls through to
      * {@link #onPrepareDialog(int, Dialog)} for compatibility.
@@ -2775,20 +2797,30 @@
      * @see #dismissDialog(int)
      * @see #removeDialog(int)
      */
+    @Deprecated
     protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
         onPrepareDialog(id, dialog);
     }
 
     /**
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Simple version of {@link #showDialog(int, Bundle)} that does not
      * take any arguments.  Simply calls {@link #showDialog(int, Bundle)}
      * with null arguments.
      */
+    @Deprecated
     public final void showDialog(int id) {
         showDialog(id, null);
     }
 
     /**
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Show a dialog managed by this activity.  A call to {@link #onCreateDialog(int, Bundle)}
      * will be made with the same id the first time this is called for a given
      * id.  From thereafter, the dialog will be automatically saved and restored.
@@ -2814,6 +2846,7 @@
      * @see #dismissDialog(int)
      * @see #removeDialog(int)
      */
+    @Deprecated
     public final boolean showDialog(int id, Bundle args) {
         if (mManagedDialogs == null) {
             mManagedDialogs = new SparseArray<ManagedDialog>();
@@ -2835,6 +2868,10 @@
     }
 
     /**
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Dismiss a dialog that was previously shown via {@link #showDialog(int)}.
      *
      * @param id The id of the managed dialog.
@@ -2847,6 +2884,7 @@
      * @see #showDialog(int)
      * @see #removeDialog(int)
      */
+    @Deprecated
     public final void dismissDialog(int id) {
         if (mManagedDialogs == null) {
             throw missingDialog(id);
@@ -2869,6 +2907,10 @@
     }
 
     /**
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     *
      * Removes any internal references to a dialog managed by this Activity.
      * If the dialog is showing, it will dismiss it as part of the clean up.
      *
@@ -2886,6 +2928,7 @@
      * @see #showDialog(int)
      * @see #dismissDialog(int)
      */
+    @Deprecated
     public final void removeDialog(int id) {
         if (mManagedDialogs != null) {
             final ManagedDialog md = mManagedDialogs.get(id);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index dd158f9..6f0bbd7 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -164,11 +164,21 @@
  *
  * <p>Topics covered here:
  * <ol>
+ * <li><a href="#OlderPlatforms">Older Platforms</a>
  * <li><a href="#Lifecycle">Lifecycle</a>
  * <li><a href="#Layout">Layout</a>
  * <li><a href="#BackStack">Back Stack</a>
  * </ol>
  *
+ * <a name="OlderPlatforms"></a>
+ * <h3>Older Platforms</h3>
+ *
+ * While the Fragment API was introduced in
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
+ * is also available for use on older platforms.  See the blog post
+ * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
+ * Fragments For All</a> for more details.
+ *
  * <a name="Lifecycle"></a>
  * <h3>Lifecycle</h3>
  *
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index a601fbf..0fe7b5c 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -35,6 +35,12 @@
     private IWindowManager mWM;
 
     /**
+     * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+     * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+     * instead; this allows you to seamlessly hide the keyguard as your application
+     * moves in and out of the foreground and does not require that any special
+     * permissions be requested.
+     *
      * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows
      * you to disable / reenable the keyguard.
      */
@@ -103,6 +109,12 @@
     }
 
     /**
+     * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+     * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+     * instead; this allows you to seamlessly hide the keyguard as your application
+     * moves in and out of the foreground and does not require that any special
+     * permissions be requested.
+     *
      * Enables you to lock or unlock the keyboard. Get an instance of this class by
      * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. 
      * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}.
@@ -112,6 +124,7 @@
      * @return A {@link KeyguardLock} handle to use to disable and reenable the
      *   keyguard.
      */
+    @Deprecated
     public KeyguardLock newKeyguardLock(String tag) {
         return new KeyguardLock(tag);
     }
@@ -168,6 +181,12 @@
     }
 
     /**
+     * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+     * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+     * instead; this allows you to seamlessly hide the keyguard as your application
+     * moves in and out of the foreground and does not require that any special
+     * permissions be requested.
+     *
      * Exit the keyguard securely.  The use case for this api is that, after
      * disabling the keyguard, your app, which was granted permission to
      * disable the keyguard and show a limited amount of information deemed
@@ -181,6 +200,7 @@
      *   it is safe to launch anything that would normally be considered safe
      *   once the user has gotten past the keyguard.
      */
+    @Deprecated
     public void exitKeyguardSecurely(final OnKeyguardExitResult callback) {
         try {
             mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() {
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 1ee386d..164141c 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -34,12 +34,18 @@
  * {@link android.content.CursorLoader}, however applications are free to write
  * their own loaders for loading other types of data.
  *
+ * While the LoaderManager API was introduced in
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
+ * is also available for use on older platforms.  See the blog post
+ * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
+ * Fragments For All</a> for more details.
+ *
  * <p>As an example, here is the full implementation of a {@link Fragment}
  * that displays a {@link android.widget.ListView} containing the results of
  * a query against the contacts content provider.  It uses a
  * {@link android.content.CursorLoader} to manage the query on the provider.
  *
- * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
  *      fragment_cursor}
  */
 public abstract class LoaderManager {
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 383cb6b..0b54396 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -27,7 +27,24 @@
 import java.util.concurrent.CountDownLatch;
 
 /**
- * Abstract Loader that provides an {@link AsyncTask} to do the work.
+ * Abstract Loader that provides an {@link AsyncTask} to do the work.  See
+ * {@link Loader} and {@link android.app.LoaderManager} for more details.
+ *
+ * <p>Here is an example implementation of an AsyncTaskLoader subclass that
+ * loads the currently installed applications from the package manager.  This
+ * implementation takes care of retrieving the application labels and sorting
+ * its result set from them, monitoring for changes to the installed
+ * applications, and rebuilding the list when a change in configuration requires
+ * this (such as a locale change).
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+ *      loader}
+ *
+ * <p>An example implementation of a fragment that uses the above loader to show
+ * the currently installed applications in a list is below.
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+ *      fragment}
  *
  * @param <D> the data type to be loaded.
  */
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index a9d6117..4e70b74 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -26,7 +26,7 @@
 /**
  * An abstract class that performs asynchronous loading of data. While Loaders are active
  * they should monitor the source of their data and deliver new results when the contents
- * change.
+ * change.  See {@link android.app.LoaderManager} for more detail.
  *
  * <p><b>Note on threading:</b> Clients of loaders should as a rule perform
  * any calls on to a Loader from the main thread of their process (that is,
@@ -36,7 +36,10 @@
  * be done on the main thread.</p>
  *
  * <p>Subclasses generally must implement at least {@link #onStartLoading()},
- * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.
+ * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.</p>
+ *
+ * <p>Most implementations should not derive directly from this class, but
+ * instead inherit from {@link AsyncTaskLoader}.</p>
  *
  * @param <D> The result returned when the load is complete
  */
@@ -76,8 +79,12 @@
     }
 
     /**
-     * Stores away the application context associated with context. Since Loaders can be used
-     * across multiple activities it's dangerous to store the context directly.
+     * Stores away the application context associated with context.
+     * Since Loaders can be used across multiple activities it's dangerous to
+     * store the context directly; always use {@link #getContext()} to retrieve
+     * the Loader's Context, don't use the constructor argument directly.
+     * The Context returned by {@link #getContext} is safe to use across
+     * Activity instances.
      *
      * @param context used to retrieve the application context.
      */
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 57fdb0c..c830f7a 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -131,14 +131,25 @@
     /**
      * Wake lock that ensures that the screen and keyboard are on at
      * full brightness.
+     *
+     * <p class="note">Most applications should strongly consider using
+     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}.
+     * This window flag will be correctly managed by the platform
+     * as the user moves between applications and doesn't require a special permission.</p>
      */
     public static final int FULL_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT 
                                             | WAKE_BIT_KEYBOARD_BRIGHT;
 
     /**
+     * @deprecated Most applications should use
+     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
+     * of this type of wake lock, as it will be correctly managed by the platform
+     * as the user moves between applications and doesn't require a special permission.
+     *
      * Wake lock that ensures that the screen is on at full brightness;
      * the keyboard backlight will be allowed to go off.
      */
+    @Deprecated
     public static final int SCREEN_BRIGHT_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT;
 
     /**
@@ -384,6 +395,11 @@
      *wl.release();
      * }
      *
+     * <p class="note">If using this to keep the screen on, you should strongly consider using
+     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
+     * This window flag will be correctly managed by the platform
+     * as the user moves between applications and doesn't require a special permission.</p>
+     *
      * @param flags Combination of flag values defining the requested behavior of the WakeLock.
      * @param tag Your class name (or other tag) for debugging purposes.
      *
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index f284f51..1ccc66f 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -590,8 +590,14 @@
             mHandler.removeMessages(SHOW_PRESS);
             mHandler.removeMessages(LONG_PRESS);
             break;
+
         case MotionEvent.ACTION_CANCEL:
             cancel();
+            break;
+        }
+
+        if (!handled && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
         }
         return handled;
     }
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 6618f07..b5ca2c2 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -54,6 +54,7 @@
 
     // Copy of the most recent events.
     private InputEvent[] mRecentEvents;
+    private boolean[] mRecentEventsUnhandled;
     private int mMostRecentEventIndex;
 
     // Current event and its type.
@@ -65,6 +66,7 @@
 
     // Current state of the trackball.
     private boolean mTrackballDown;
+    private boolean mTrackballUnhandled;
 
     // Bitfield of pointer ids that are currently down.
     // Assumes that the largest possible pointer id is 31, which is potentially subject to change.
@@ -79,6 +81,9 @@
     // Reset on down or cancel.
     private boolean mTouchEventStreamIsTainted;
 
+    // Set to true if the touch event stream is partially unhandled.
+    private boolean mTouchEventStreamUnhandled;
+
     // Set to true if we received hover enter.
     private boolean mHoverEntered;
 
@@ -117,9 +122,17 @@
         mLastEvent = null;
         mLastNestingLevel = 0;
         mTrackballDown = false;
+        mTrackballUnhandled = false;
         mTouchEventStreamPointers = 0;
         mTouchEventStreamIsTainted = false;
+        mTouchEventStreamUnhandled = false;
         mHoverEntered = false;
+
+        while (mKeyStateList != null) {
+            final KeyState state = mKeyStateList;
+            mKeyStateList = state.next;
+            state.recycle();
+        }
     }
 
     /**
@@ -176,7 +189,9 @@
                         // We don't perform this check when processing raw device input
                         // because the input dispatcher itself is responsible for setting
                         // the key repeat count before it delivers input events.
-                        if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
+                        if (state.unhandled) {
+                            state.unhandled = false;
+                        } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
                                 && event.getRepeatCount() == 0) {
                             problem("ACTION_DOWN but key is already down and this event "
                                     + "is not a key repeat.");
@@ -229,10 +244,11 @@
             if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                 switch (action) {
                     case MotionEvent.ACTION_DOWN:
-                        if (mTrackballDown) {
+                        if (mTrackballDown && !mTrackballUnhandled) {
                             problem("ACTION_DOWN but trackball is already down.");
                         } else {
                             mTrackballDown = true;
+                            mTrackballUnhandled = false;
                         }
                         ensureHistorySizeIsZeroForThisAction(event);
                         ensurePointerCountIsOneForThisAction(event);
@@ -242,6 +258,7 @@
                             problem("ACTION_UP but trackball is not down.");
                         } else {
                             mTrackballDown = false;
+                            mTrackballUnhandled = false;
                         }
                         ensureHistorySizeIsZeroForThisAction(event);
                         ensurePointerCountIsOneForThisAction(event);
@@ -285,11 +302,13 @@
         final int action = event.getAction();
         final boolean newStream = action == MotionEvent.ACTION_DOWN
                 || action == MotionEvent.ACTION_CANCEL;
-        if (mTouchEventStreamIsTainted) {
+        if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) {
             if (newStream) {
                 mTouchEventStreamIsTainted = false;
+                mTouchEventStreamUnhandled = false;
+                mTouchEventStreamPointers = 0;
             } else {
-                finishEvent(true);
+                finishEvent(mTouchEventStreamIsTainted);
                 return;
             }
         }
@@ -467,6 +486,48 @@
         }
     }
 
+    /**
+     * Notifies the verifier that a given event was unhandled and the rest of the
+     * trace for the event should be ignored.
+     * This method should only be called if the event was previously checked by
+     * the consistency verifier using {@link #onInputEvent} and other methods.
+     * @param event The event.
+     * @param nestingLevel The nesting level: 0 if called from the base class,
+     * or 1 from a subclass.  If the event was already checked by this consistency verifier
+     * at a higher nesting level, it will not be checked again.  Used to handle the situation
+     * where a subclass dispatching method delegates to its superclass's dispatching method
+     * and both dispatching methods call into the consistency verifier.
+     */
+    public void onUnhandledEvent(InputEvent event, int nestingLevel) {
+        if (nestingLevel != mLastNestingLevel) {
+            return;
+        }
+
+        if (mRecentEventsUnhandled != null) {
+            mRecentEventsUnhandled[mMostRecentEventIndex] = true;
+        }
+
+        if (event instanceof KeyEvent) {
+            final KeyEvent keyEvent = (KeyEvent)event;
+            final int deviceId = keyEvent.getDeviceId();
+            final int source = keyEvent.getSource();
+            final int keyCode = keyEvent.getKeyCode();
+            final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+            if (state != null) {
+                state.unhandled = true;
+            }
+        } else {
+            final MotionEvent motionEvent = (MotionEvent)event;
+            if (motionEvent.isTouchEvent()) {
+                mTouchEventStreamUnhandled = true;
+            } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+                if (mTrackballDown) {
+                    mTrackballUnhandled = true;
+                }
+            }
+        }
+    }
+
     private void ensureMetaStateIsNormalized(int metaState) {
         final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
         if (normalizedMetaState != metaState) {
@@ -518,7 +579,8 @@
     private void finishEvent(boolean tainted) {
         if (mViolationMessage != null && mViolationMessage.length() != 0) {
             mViolationMessage.append("\n  in ").append(mCaller);
-            mViolationMessage.append("\n  ").append(mCurrentEvent);
+            mViolationMessage.append("\n  ");
+            appendEvent(mViolationMessage, 0, mCurrentEvent, false);
 
             if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
                 mViolationMessage.append("\n  -- recent events --");
@@ -529,7 +591,8 @@
                     if (event == null) {
                         break;
                     }
-                    mViolationMessage.append("\n  ").append(i + 1).append(": ").append(event);
+                    mViolationMessage.append("\n  ");
+                    appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
                 }
             }
 
@@ -547,6 +610,7 @@
         if (RECENT_EVENTS_TO_LOG != 0) {
             if (mRecentEvents == null) {
                 mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
+                mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
             }
             final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
             mMostRecentEventIndex = index;
@@ -554,12 +618,23 @@
                 mRecentEvents[index].recycle();
             }
             mRecentEvents[index] = mCurrentEvent.copy();
+            mRecentEventsUnhandled[index] = false;
         }
 
         mCurrentEvent = null;
         mCurrentEventType = null;
     }
 
+    private static void appendEvent(StringBuilder message, int index,
+            InputEvent event, boolean unhandled) {
+        message.append(index).append(": sent at ").append(event.getEventTimeNano());
+        message.append(", ");
+        if (unhandled) {
+            message.append("(unhandled) ");
+        }
+        message.append(event);
+    }
+
     private void problem(String message) {
         if (mViolationMessage == null) {
             mViolationMessage = new StringBuilder();
@@ -608,6 +683,7 @@
         public int deviceId;
         public int source;
         public int keyCode;
+        public boolean unhandled;
 
         private KeyState() {
         }
@@ -625,6 +701,7 @@
             state.deviceId = deviceId;
             state.source = source;
             state.keyCode = keyCode;
+            state.unhandled = false;
             return state;
         }
 
diff --git a/core/java/android/view/PointerIcon.aidl b/core/java/android/view/PointerIcon.aidl
new file mode 100644
index 0000000..b09340b
--- /dev/null
+++ b/core/java/android/view/PointerIcon.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+parcelable PointerIcon;
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
new file mode 100644
index 0000000..bb7ed41
--- /dev/null
+++ b/core/java/android/view/PointerIcon.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import com.android.internal.util.XmlUtils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * <p>
+ * Pointer icons can be provided either by the system using system styles,
+ * or by applications using bitmaps or application resources.
+ * </p>
+ *
+ * @hide
+ */
+public final class PointerIcon implements Parcelable {
+    private static final String TAG = "PointerIcon";
+
+    /** Style constant: Custom icon with a user-supplied bitmap. */
+    public static final int STYLE_CUSTOM = -1;
+
+    /** Style constant: Null icon.  It has no bitmap. */
+    public static final int STYLE_NULL = 0;
+
+    /** Style constant: Arrow icon.  (Default mouse pointer) */
+    public static final int STYLE_ARROW = 1000;
+
+    /** {@hide} Style constant: Spot hover icon for touchpads. */
+    public static final int STYLE_SPOT_HOVER = 2000;
+
+    /** {@hide} Style constant: Spot touch icon for touchpads. */
+    public static final int STYLE_SPOT_TOUCH = 2001;
+
+    /** {@hide} Style constant: Spot anchor icon for touchpads. */
+    public static final int STYLE_SPOT_ANCHOR = 2002;
+
+    // OEM private styles should be defined starting at this range to avoid
+    // conflicts with any system styles that may be defined in the future.
+    private static final int STYLE_OEM_FIRST = 10000;
+
+    // The default pointer icon.
+    private static final int STYLE_DEFAULT = STYLE_ARROW;
+
+    private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL);
+
+    private final int mStyle;
+    private int mSystemIconResourceId;
+    private Bitmap mBitmap;
+    private float mHotSpotX;
+    private float mHotSpotY;
+
+    private PointerIcon(int style) {
+        mStyle = style;
+    }
+
+    /**
+     * Gets a special pointer icon that has no bitmap.
+     *
+     * @return The null pointer icon.
+     *
+     * @see #STYLE_NULL
+     */
+    public static PointerIcon getNullIcon() {
+        return gNullIcon;
+    }
+
+    /**
+     * Gets the default pointer icon.
+     *
+     * @param context The context.
+     * @return The default pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     */
+    public static PointerIcon getDefaultIcon(Context context) {
+        return getSystemIcon(context, STYLE_DEFAULT);
+    }
+
+    /**
+     * Gets a system pointer icon for the given style.
+     * If style is not recognized, returns the default pointer icon.
+     *
+     * @param context The context.
+     * @param style The pointer icon style.
+     * @return The pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     */
+    public static PointerIcon getSystemIcon(Context context, int style) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        if (style == STYLE_NULL) {
+            return gNullIcon;
+        }
+
+        int styleIndex = getSystemIconStyleIndex(style);
+        if (styleIndex == 0) {
+            styleIndex = getSystemIconStyleIndex(STYLE_DEFAULT);
+        }
+
+        TypedArray a = context.obtainStyledAttributes(null,
+                com.android.internal.R.styleable.Pointer,
+                com.android.internal.R.attr.pointerStyle, 0);
+        int resourceId = a.getResourceId(styleIndex, -1);
+        a.recycle();
+
+        if (resourceId == -1) {
+            Log.w(TAG, "Missing theme resources for pointer icon style " + style);
+            return style == STYLE_DEFAULT ? gNullIcon : getSystemIcon(context, STYLE_DEFAULT);
+        }
+
+        PointerIcon icon = new PointerIcon(style);
+        if ((resourceId & 0xff000000) == 0x01000000) {
+            icon.mSystemIconResourceId = resourceId;
+        } else {
+            icon.loadResource(context.getResources(), resourceId);
+        }
+        return icon;
+    }
+
+    /**
+     * Creates a custom pointer from the given bitmap and hotspot information.
+     *
+     * @param bitmap The bitmap for the icon.
+     * @param hotspotX The X offset of the pointer icon hotspot in the bitmap.
+     *        Must be within the [0, bitmap.getWidth()) range.
+     * @param hotspotY The Y offset of the pointer icon hotspot in the bitmap.
+     *        Must be within the [0, bitmap.getHeight()) range.
+     * @return A pointer icon for this bitmap.
+     *
+     * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+     *         parameters are invalid.
+     */
+    public static PointerIcon createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+        if (bitmap == null) {
+            throw new IllegalArgumentException("bitmap must not be null");
+        }
+        validateHotSpot(bitmap, hotSpotX, hotSpotY);
+
+        PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+        icon.mBitmap = bitmap;
+        icon.mHotSpotX = hotSpotX;
+        icon.mHotSpotY = hotSpotY;
+        return icon;
+    }
+
+    /**
+     * Loads a custom pointer icon from an XML resource.
+     * <p>
+     * The XML resource should have the following form:
+     * <code>
+     * &lt;?xml version="1.0" encoding="utf-8"?&gt;
+     * &lt;pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+     *   android:bitmap="@drawable/my_pointer_bitmap"
+     *   android:hotSpotX="24"
+     *   android:hotSpotY="24" /&gt;
+     * </code>
+     * </p>
+     *
+     * @param resources The resources object.
+     * @param resourceId The resource id.
+     * @return The pointer icon.
+     *
+     * @throws IllegalArgumentException if resources is null.
+     * @throws Resources.NotFoundException if the resource was not found or the drawable
+     * linked in the resource was not found.
+     */
+    public static PointerIcon loadCustomIcon(Resources resources, int resourceId) {
+        if (resources == null) {
+            throw new IllegalArgumentException("resources must not be null");
+        }
+
+        PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+        icon.loadResource(resources, resourceId);
+        return icon;
+    }
+
+    /**
+     * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
+     * Returns a pointer icon (not necessarily the same instance) with the information filled in.
+     *
+     * @param context The context.
+     * @return The loaded pointer icon.
+     *
+     * @throws IllegalArgumentException if context is null.
+     * @see #isLoaded()
+     * @hide
+     */
+    public PointerIcon load(Context context) {
+        if (context == null) {
+            throw new IllegalArgumentException("context must not be null");
+        }
+
+        if (mSystemIconResourceId == 0 || mBitmap != null) {
+            return this;
+        }
+
+        PointerIcon result = new PointerIcon(mStyle);
+        result.mSystemIconResourceId = mSystemIconResourceId;
+        result.loadResource(context.getResources(), mSystemIconResourceId);
+        return result;
+    }
+
+    /**
+     * Returns true if the pointer icon style is {@link #STYLE_NULL}.
+     *
+     * @return True if the pointer icon style is {@link #STYLE_NULL}.
+     */
+    public boolean isNullIcon() {
+        return mStyle == STYLE_NULL;
+    }
+
+    /**
+     * Returns true if the pointer icon has been loaded and its bitmap and hotspot
+     * information are available.
+     *
+     * @return True if the pointer icon is loaded.
+     * @see #load(Context)
+     */
+    public boolean isLoaded() {
+        return mBitmap != null || mStyle == STYLE_NULL;
+    }
+
+    /**
+     * Gets the style of the pointer icon.
+     *
+     * @return The pointer icon style.
+     */
+    public int getStyle() {
+        return mStyle;
+    }
+
+    /**
+     * Gets the bitmap of the pointer icon.
+     *
+     * @return The pointer icon bitmap, or null if the style is {@link #STYLE_NULL}.
+     *
+     * @throws IllegalStateException if the bitmap is not loaded.
+     * @see #isLoaded()
+     * @see #load(Context)
+     */
+    public Bitmap getBitmap() {
+        throwIfIconIsNotLoaded();
+        return mBitmap;
+    }
+
+    /**
+     * Gets the X offset of the pointer icon hotspot.
+     *
+     * @return The hotspot X offset.
+     *
+     * @throws IllegalStateException if the bitmap is not loaded.
+     * @see #isLoaded()
+     * @see #load(Context)
+     */
+    public float getHotSpotX() {
+        throwIfIconIsNotLoaded();
+        return mHotSpotX;
+    }
+
+    /**
+     * Gets the Y offset of the pointer icon hotspot.
+     *
+     * @return The hotspot Y offset.
+     *
+     * @throws IllegalStateException if the bitmap is not loaded.
+     * @see #isLoaded()
+     * @see #load(Context)
+     */
+    public float getHotSpotY() {
+        throwIfIconIsNotLoaded();
+        return mHotSpotY;
+    }
+
+    private void throwIfIconIsNotLoaded() {
+        if (!isLoaded()) {
+            throw new IllegalStateException("The icon is not loaded.");
+        }
+    }
+
+    public static final Parcelable.Creator<PointerIcon> CREATOR
+            = new Parcelable.Creator<PointerIcon>() {
+        public PointerIcon createFromParcel(Parcel in) {
+            int style = in.readInt();
+            if (style == STYLE_NULL) {
+                return getNullIcon();
+            }
+
+            int systemIconResourceId = in.readInt();
+            if (systemIconResourceId != 0) {
+                PointerIcon icon = new PointerIcon(style);
+                icon.mSystemIconResourceId = systemIconResourceId;
+                return icon;
+            }
+
+            Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
+            float hotSpotX = in.readFloat();
+            float hotSpotY = in.readFloat();
+            return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+        }
+
+        public PointerIcon[] newArray(int size) {
+            return new PointerIcon[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mStyle);
+
+        if (mStyle != STYLE_NULL) {
+            out.writeInt(mSystemIconResourceId);
+            if (mSystemIconResourceId == 0) {
+                mBitmap.writeToParcel(out, flags);
+                out.writeFloat(mHotSpotX);
+                out.writeFloat(mHotSpotY);
+            }
+        }
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null || !(other instanceof PointerIcon)) {
+            return false;
+        }
+
+        PointerIcon otherIcon = (PointerIcon) other;
+        if (mStyle != otherIcon.mStyle
+                || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
+            return false;
+        }
+
+        if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
+                || mHotSpotX != otherIcon.mHotSpotX
+                || mHotSpotY != otherIcon.mHotSpotY)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void loadResource(Resources resources, int resourceId) {
+        XmlResourceParser parser = resources.getXml(resourceId);
+        final int bitmapRes;
+        final float hotSpotX;
+        final float hotSpotY;
+        try {
+            XmlUtils.beginDocument(parser, "pointer-icon");
+
+            TypedArray a = resources.obtainAttributes(
+                    parser, com.android.internal.R.styleable.PointerIcon);
+            bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
+            hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+            hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+            a.recycle();
+        } catch (Exception ex) {
+            throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
+        } finally {
+            parser.close();
+        }
+
+        if (bitmapRes == 0) {
+            throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
+        }
+
+        Drawable drawable = resources.getDrawable(bitmapRes);
+        if (!(drawable instanceof BitmapDrawable)) {
+            throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
+                    + "refer to a bitmap drawable.");
+        }
+
+        // Set the properties now that we have successfully loaded the icon.
+        mBitmap = ((BitmapDrawable)drawable).getBitmap();
+        mHotSpotX = hotSpotX;
+        mHotSpotY = hotSpotY;
+    }
+
+    private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+        if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
+            throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
+        }
+        if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
+            throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
+        }
+    }
+
+    private static int getSystemIconStyleIndex(int style) {
+        switch (style) {
+            case STYLE_ARROW:
+                return com.android.internal.R.styleable.Pointer_pointerIconArrow;
+            case STYLE_SPOT_HOVER:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
+            case STYLE_SPOT_TOUCH:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
+            case STYLE_SPOT_ANCHOR:
+                return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
+            default:
+                return 0;
+        }
+    }
+}
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 456857a..5e07e1a 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -183,15 +183,15 @@
         }
 
         final int action = event.getActionMasked();
-        boolean handled = true;
 
         if (action == MotionEvent.ACTION_DOWN) {
             reset(); // Start fresh
         }
 
-        if (mInvalidGesture) return false;
-
-        if (!mGestureInProgress) {
+        boolean handled = true;
+        if (mInvalidGesture) {
+            handled = false;
+        } else if (!mGestureInProgress) {
             switch (action) {
             case MotionEvent.ACTION_DOWN: {
                 mActiveId0 = event.getPointerId(0);
@@ -467,6 +467,10 @@
                 break;
             }
         }
+
+        if (!handled && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
         return handled;
     }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index dc8e52f..fe8af19 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4614,8 +4614,15 @@
             return true;
         }
 
-        return event.dispatch(this, mAttachInfo != null
-                ? mAttachInfo.mKeyDispatchState : null, this);
+        if (event.dispatch(this, mAttachInfo != null
+                ? mAttachInfo.mKeyDispatchState : null, this)) {
+            return true;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
     }
 
     /**
@@ -4640,16 +4647,22 @@
             mInputEventConsistencyVerifier.onTouchEvent(event, 0);
         }
 
-        if (!onFilterTouchEventForSecurity(event)) {
-            return false;
+        if (onFilterTouchEventForSecurity(event)) {
+            //noinspection SimplifiableIfStatement
+            if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
+                    mOnTouchListener.onTouch(this, event)) {
+                return true;
+            }
+
+            if (onTouchEvent(event)) {
+                return true;
+            }
         }
 
-        //noinspection SimplifiableIfStatement
-        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
-                mOnTouchListener.onTouch(this, event)) {
-            return true;
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
         }
-        return onTouchEvent(event);
+        return false;
     }
 
     /**
@@ -4682,7 +4695,14 @@
         }
 
         //Log.i("view", "view=" + this + ", " + event.toString());
-        return onTrackballEvent(event);
+        if (onTrackballEvent(event)) {
+            return true;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
     }
 
     /**
@@ -4723,7 +4743,15 @@
                 && mOnGenericMotionListener.onGenericMotion(this, event)) {
             return true;
         }
-        return onGenericMotionEvent(event);
+
+        if (onGenericMotionEvent(event)) {
+            return true;
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+        }
+        return false;
     }
 
     /**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0d4f3d0..08daa28 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1131,9 +1131,17 @@
         }
 
         if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
-            return super.dispatchKeyEvent(event);
+            if (super.dispatchKeyEvent(event)) {
+                return true;
+            }
         } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
-            return mFocused.dispatchKeyEvent(event);
+            if (mFocused.dispatchKeyEvent(event)) {
+                return true;
+            }
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
         }
         return false;
     }
@@ -1161,9 +1169,17 @@
         }
 
         if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
-            return super.dispatchTrackballEvent(event);
+            if (super.dispatchTrackballEvent(event)) {
+                return true;
+            }
         } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
-            return mFocused.dispatchTrackballEvent(event);
+            if (mFocused.dispatchTrackballEvent(event)) {
+                return true;
+            }
+        }
+
+        if (mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
         }
         return false;
     }
@@ -1344,155 +1360,158 @@
             mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
         }
 
-        if (!onFilterTouchEventForSecurity(ev)) {
-            return false;
-        }
+        boolean handled = false;
+        if (onFilterTouchEventForSecurity(ev)) {
+            final int action = ev.getAction();
+            final int actionMasked = action & MotionEvent.ACTION_MASK;
 
-        final int action = ev.getAction();
-        final int actionMasked = action & MotionEvent.ACTION_MASK;
-
-        // Handle an initial down.
-        if (actionMasked == MotionEvent.ACTION_DOWN) {
-            // Throw away all previous state when starting a new touch gesture.
-            // The framework may have dropped the up or cancel event for the previous gesture
-            // due to an app switch, ANR, or some other state change.
-            cancelAndClearTouchTargets(ev);
-            resetTouchState();
-        }
-
-        // Check for interception.
-        final boolean intercepted;
-        if (actionMasked == MotionEvent.ACTION_DOWN
-                || mFirstTouchTarget != null) {
-            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
-            if (!disallowIntercept) {
-                intercepted = onInterceptTouchEvent(ev);
-                ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
-            } else {
-                intercepted = false;
+            // Handle an initial down.
+            if (actionMasked == MotionEvent.ACTION_DOWN) {
+                // Throw away all previous state when starting a new touch gesture.
+                // The framework may have dropped the up or cancel event for the previous gesture
+                // due to an app switch, ANR, or some other state change.
+                cancelAndClearTouchTargets(ev);
+                resetTouchState();
             }
-        } else {
-            // There are no touch targets and this action is not an initial down
-            // so this view group continues to intercept touches.
-            intercepted = true;
-        }
 
-        // Check for cancelation.
-        final boolean canceled = resetCancelNextUpFlag(this)
-                || actionMasked == MotionEvent.ACTION_CANCEL;
-
-        // Update list of touch targets for pointer down, if needed.
-        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
-        TouchTarget newTouchTarget = null;
-        boolean alreadyDispatchedToNewTouchTarget = false;
-        if (!canceled && !intercepted) {
+            // Check for interception.
+            final boolean intercepted;
             if (actionMasked == MotionEvent.ACTION_DOWN
-                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
-                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
-                final int actionIndex = ev.getActionIndex(); // always 0 for down
-                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
-                        : TouchTarget.ALL_POINTER_IDS;
+                    || mFirstTouchTarget != null) {
+                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+                if (!disallowIntercept) {
+                    intercepted = onInterceptTouchEvent(ev);
+                    ev.setAction(action); // restore action in case it was changed
+                } else {
+                    intercepted = false;
+                }
+            } else {
+                // There are no touch targets and this action is not an initial down
+                // so this view group continues to intercept touches.
+                intercepted = true;
+            }
 
-                // Clean up earlier touch targets for this pointer id in case they
-                // have become out of sync.
-                removePointersFromTouchTargets(idBitsToAssign);
+            // Check for cancelation.
+            final boolean canceled = resetCancelNextUpFlag(this)
+                    || actionMasked == MotionEvent.ACTION_CANCEL;
 
-                final int childrenCount = mChildrenCount;
-                if (childrenCount != 0) {
-                    // Find a child that can receive the event.  Scan children from front to back.
-                    final View[] children = mChildren;
-                    final float x = ev.getX(actionIndex);
-                    final float y = ev.getY(actionIndex);
+            // Update list of touch targets for pointer down, if needed.
+            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+            TouchTarget newTouchTarget = null;
+            boolean alreadyDispatchedToNewTouchTarget = false;
+            if (!canceled && !intercepted) {
+                if (actionMasked == MotionEvent.ACTION_DOWN
+                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
+                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+                    final int actionIndex = ev.getActionIndex(); // always 0 for down
+                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+                            : TouchTarget.ALL_POINTER_IDS;
 
-                    for (int i = childrenCount - 1; i >= 0; i--) {
-                        final View child = children[i];
-                        if (!canViewReceivePointerEvents(child)
-                                || !isTransformedTouchPointInView(x, y, child, null)) {
+                    // Clean up earlier touch targets for this pointer id in case they
+                    // have become out of sync.
+                    removePointersFromTouchTargets(idBitsToAssign);
+
+                    final int childrenCount = mChildrenCount;
+                    if (childrenCount != 0) {
+                        // Find a child that can receive the event.
+                        // Scan children from front to back.
+                        final View[] children = mChildren;
+                        final float x = ev.getX(actionIndex);
+                        final float y = ev.getY(actionIndex);
+
+                        for (int i = childrenCount - 1; i >= 0; i--) {
+                            final View child = children[i];
+                            if (!canViewReceivePointerEvents(child)
+                                    || !isTransformedTouchPointInView(x, y, child, null)) {
+                                continue;
+                            }
+
+                            newTouchTarget = getTouchTarget(child);
+                            if (newTouchTarget != null) {
+                                // Child is already receiving touch within its bounds.
+                                // Give it the new pointer in addition to the ones it is handling.
+                                newTouchTarget.pointerIdBits |= idBitsToAssign;
+                                break;
+                            }
+
+                            resetCancelNextUpFlag(child);
+                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+                                // Child wants to receive touch within its bounds.
+                                mLastTouchDownTime = ev.getDownTime();
+                                mLastTouchDownIndex = i;
+                                mLastTouchDownX = ev.getX();
+                                mLastTouchDownY = ev.getY();
+                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
+                                alreadyDispatchedToNewTouchTarget = true;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (newTouchTarget == null && mFirstTouchTarget != null) {
+                        // Did not find a child to receive the event.
+                        // Assign the pointer to the least recently added target.
+                        newTouchTarget = mFirstTouchTarget;
+                        while (newTouchTarget.next != null) {
+                            newTouchTarget = newTouchTarget.next;
+                        }
+                        newTouchTarget.pointerIdBits |= idBitsToAssign;
+                    }
+                }
+            }
+
+            // Dispatch to touch targets.
+            if (mFirstTouchTarget == null) {
+                // No touch targets so treat this as an ordinary view.
+                handled = dispatchTransformedTouchEvent(ev, canceled, null,
+                        TouchTarget.ALL_POINTER_IDS);
+            } else {
+                // Dispatch to touch targets, excluding the new touch target if we already
+                // dispatched to it.  Cancel touch targets if necessary.
+                TouchTarget predecessor = null;
+                TouchTarget target = mFirstTouchTarget;
+                while (target != null) {
+                    final TouchTarget next = target.next;
+                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+                        handled = true;
+                    } else {
+                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
+                        || intercepted;
+                        if (dispatchTransformedTouchEvent(ev, cancelChild,
+                                target.child, target.pointerIdBits)) {
+                            handled = true;
+                        }
+                        if (cancelChild) {
+                            if (predecessor == null) {
+                                mFirstTouchTarget = next;
+                            } else {
+                                predecessor.next = next;
+                            }
+                            target.recycle();
+                            target = next;
                             continue;
                         }
-
-                        newTouchTarget = getTouchTarget(child);
-                        if (newTouchTarget != null) {
-                            // Child is already receiving touch within its bounds.
-                            // Give it the new pointer in addition to the ones it is handling.
-                            newTouchTarget.pointerIdBits |= idBitsToAssign;
-                            break;
-                        }
-
-                        resetCancelNextUpFlag(child);
-                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
-                            // Child wants to receive touch within its bounds.
-                            mLastTouchDownTime = ev.getDownTime();
-                            mLastTouchDownIndex = i;
-                            mLastTouchDownX = ev.getX();
-                            mLastTouchDownY = ev.getY();
-                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
-                            alreadyDispatchedToNewTouchTarget = true;
-                            break;
-                        }
                     }
+                    predecessor = target;
+                    target = next;
                 }
+            }
 
-                if (newTouchTarget == null && mFirstTouchTarget != null) {
-                    // Did not find a child to receive the event.
-                    // Assign the pointer to the least recently added target.
-                    newTouchTarget = mFirstTouchTarget;
-                    while (newTouchTarget.next != null) {
-                        newTouchTarget = newTouchTarget.next;
-                    }
-                    newTouchTarget.pointerIdBits |= idBitsToAssign;
-                }
+            // Update list of touch targets for pointer up or cancel, if needed.
+            if (canceled
+                    || actionMasked == MotionEvent.ACTION_UP
+                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+                resetTouchState();
+            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+                final int actionIndex = ev.getActionIndex();
+                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+                removePointersFromTouchTargets(idBitsToRemove);
             }
         }
 
-        // Dispatch to touch targets.
-        boolean handled = false;
-        if (mFirstTouchTarget == null) {
-            // No touch targets so treat this as an ordinary view.
-            handled = dispatchTransformedTouchEvent(ev, canceled, null,
-                    TouchTarget.ALL_POINTER_IDS);
-        } else {
-            // Dispatch to touch targets, excluding the new touch target if we already
-            // dispatched to it.  Cancel touch targets if necessary.
-            TouchTarget predecessor = null;
-            TouchTarget target = mFirstTouchTarget;
-            while (target != null) {
-                final TouchTarget next = target.next;
-                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
-                    handled = true;
-                } else {
-                    final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
-                    if (dispatchTransformedTouchEvent(ev, cancelChild,
-                            target.child, target.pointerIdBits)) {
-                        handled = true;
-                    }
-                    if (cancelChild) {
-                        if (predecessor == null) {
-                            mFirstTouchTarget = next;
-                        } else {
-                            predecessor.next = next;
-                        }
-                        target.recycle();
-                        target = next;
-                        continue;
-                    }
-                }
-                predecessor = target;
-                target = next;
-            }
+        if (!handled && mInputEventConsistencyVerifier != null) {
+            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
         }
-
-        // Update list of touch targets for pointer up or cancel, if needed.
-        if (canceled
-                || actionMasked == MotionEvent.ACTION_UP
-                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
-            resetTouchState();
-        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
-            final int actionIndex = ev.getActionIndex();
-            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
-            removePointersFromTouchTargets(idBitsToRemove);
-        }
-
         return handled;
     }
 
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index ea9e402..a6639d1 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -26,8 +26,9 @@
  * is used to perform such things as reading text around the cursor,
  * committing text to the text box, and sending raw key events to the application.
  * 
- * <p>Implementations of this interface should generally be done by
- * subclassing {@link BaseInputConnection}.
+ * <p>Applications should never directly implement this interface, but instead
+ * subclass from {@link BaseInputConnection}.  This will ensure that the
+ * application does not break when new methods are added to the interface.
  */
 public interface InputConnection {
     /**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index e07e26e..b5d0492 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -871,8 +871,9 @@
          */
         public static final int UNKNOWN_TYPE = 0;
         /**
-         * HitTestResult for hitting a HTML::a tag
+         * @deprecated This type is no longer used.
          */
+        @Deprecated
         public static final int ANCHOR_TYPE = 1;
         /**
          * HitTestResult for hitting a phone number
@@ -891,8 +892,9 @@
          */
         public static final int IMAGE_TYPE = 5;
         /**
-         * HitTestResult for hitting a HTML::a tag which contains HTML::img
+         * @deprecated This type is no longer used.
          */
+        @Deprecated
         public static final int IMAGE_ANCHOR_TYPE = 6;
         /**
          * HitTestResult for hitting a HTML::a tag with src=http
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index fd0e53d..dbe9288 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -230,6 +230,30 @@
         requestLayout();
     }
 
+    /**
+     * Set padding displayed on both ends of dividers.
+     *
+     * @param padding Padding value in pixels that will be applied to each end
+     *
+     * @see #setShowDividers(int)
+     * @see #setDividerDrawable(Drawable)
+     * @see #getDividerPadding()
+     */
+    public void setDividerPadding(int padding) {
+        mDividerPadding = padding;
+    }
+
+    /**
+     * Get the padding size used to inset dividers in pixels
+     *
+     * @see #setShowDividers(int)
+     * @see #setDividerDrawable(Drawable)
+     * @see #setDividerPadding(int)
+     */
+    public int getDividerPadding() {
+        return mDividerPadding;
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         if (mDivider == null) {
@@ -244,29 +268,15 @@
     }
 
     void drawDividersVertical(Canvas canvas) {
-        final boolean showDividerBeginning =
-            (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
-        final boolean showDividerMiddle =
-                (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-        final boolean showDividerEnd =
-                (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END;
-
         final int count = getVirtualChildCount();
         int top = getPaddingTop();
-        boolean firstVisible = true;
         for (int i = 0; i < count; i++) {
             final View child = getVirtualChildAt(i);
 
             if (child == null) {
                 top += measureNullChild(i);
             } else if (child.getVisibility() != GONE) {
-                if (firstVisible) {
-                    firstVisible = false;
-                    if (showDividerBeginning) {
-                        drawHorizontalDivider(canvas, top);
-                        top += mDividerHeight;
-                    }
-                } else if (showDividerMiddle) {
+                if (hasDividerBeforeChildAt(i)) {
                     drawHorizontalDivider(canvas, top);
                     top += mDividerHeight;
                 }
@@ -276,35 +286,21 @@
             }
         }
 
-        if (showDividerEnd) {
+        if (hasDividerBeforeChildAt(count)) {
             drawHorizontalDivider(canvas, top);
         }
     }
 
     void drawDividersHorizontal(Canvas canvas) {
-        final boolean showDividerBeginning =
-            (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
-        final boolean showDividerMiddle =
-                (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-        final boolean showDividerEnd =
-                (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END;
-
         final int count = getVirtualChildCount();
         int left = getPaddingLeft();
-        boolean firstVisible = true;
         for (int i = 0; i < count; i++) {
             final View child = getVirtualChildAt(i);
 
             if (child == null) {
                 left += measureNullChild(i);
             } else if (child.getVisibility() != GONE) {
-                if (firstVisible) {
-                    firstVisible = false;
-                    if (showDividerBeginning) {
-                        drawVerticalDivider(canvas, left);
-                        left += mDividerWidth;
-                    }
-                } else if (showDividerMiddle) {
+                if (hasDividerBeforeChildAt(i)) {
                     drawVerticalDivider(canvas, left);
                     left += mDividerWidth;
                 }
@@ -314,7 +310,7 @@
             }
         }
 
-        if (showDividerEnd) {
+        if (hasDividerBeforeChildAt(count)) {
             drawVerticalDivider(canvas, left);
         }
     }
@@ -523,6 +519,23 @@
     }
 
     /**
+     * Determines where to position dividers between children.
+     *
+     * @param childIndex Index of child to check for preceding divider
+     * @return true if there should be a divider before the child at childIndex
+     * @hide Pending API consideration. Currently only used internally by the system.
+     */
+    protected boolean hasDividerBeforeChildAt(int childIndex) {
+        if (childIndex == 0) {
+            return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
+        } else if (childIndex == getChildCount()) {
+            return (mShowDividers & SHOW_DIVIDER_END) != 0;
+        } else {
+            return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0;
+        }
+    }
+
+    /**
      * Measures the children when the orientation of this LinearLayout is set
      * to {@link #VERTICAL}.
      *
@@ -554,14 +567,7 @@
 
         int largestChildHeight = Integer.MIN_VALUE;
 
-        // A divider at the end will change how much space views can consume.
-        final boolean showDividerBeginning =
-                (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
-        final boolean showDividerMiddle =
-            (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
         // See how tall everyone is. Also remember max width.
-        boolean firstVisible = true;
         for (int i = 0; i < count; ++i) {
             final View child = getVirtualChildAt(i);
 
@@ -575,12 +581,7 @@
                continue;
             }
 
-            if (firstVisible) {
-                firstVisible = false;
-                if (showDividerBeginning) {
-                    mTotalLength += mDividerHeight;
-                }
-            } else if (showDividerMiddle) {
+            if (hasDividerBeforeChildAt(i)) {
                 mTotalLength += mDividerHeight;
             }
 
@@ -677,7 +678,7 @@
             i += getChildrenSkipCount(child, i);
         }
 
-        if (mTotalLength > 0 && (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END) {
+        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
             mTotalLength += mDividerHeight;
         }
 
@@ -881,14 +882,7 @@
 
         int largestChildWidth = Integer.MIN_VALUE;
 
-        // A divider at the end will change how much space views can consume.
-        final boolean showDividerBeginning =
-                (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
-        final boolean showDividerMiddle =
-            (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
         // See how wide everyone is. Also remember max height.
-        boolean firstVisible = true;
         for (int i = 0; i < count; ++i) {
             final View child = getVirtualChildAt(i);
 
@@ -902,12 +896,7 @@
                 continue;
             }
 
-            if (firstVisible) {
-                firstVisible = false;
-                if (showDividerBeginning) {
-                    mTotalLength += mDividerWidth;
-                }
-            } else if (showDividerMiddle) {
+            if (hasDividerBeforeChildAt(i)) {
                 mTotalLength += mDividerWidth;
             }
 
@@ -1022,7 +1011,7 @@
             i += getChildrenSkipCount(child, i);
         }
 
-        if (mTotalLength > 0 && (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END) {
+        if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
             mTotalLength += mDividerWidth;
         }
 
@@ -1358,13 +1347,6 @@
            
         }
 
-        final boolean showDividerMiddle =
-                (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
-        if ((mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING) {
-            childTop += mDividerHeight;
-        }
-
         for (int i = 0; i < count; i++) {
             final View child = getVirtualChildAt(i);
             if (child == null) {
@@ -1399,15 +1381,15 @@
                         break;
                 }
                 
+                if (hasDividerBeforeChildAt(i)) {
+                    childTop += mDividerHeight;
+                }
+
                 childTop += lp.topMargin;
                 setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                         childWidth, childHeight);
                 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
 
-                if (showDividerMiddle) {
-                    childTop += mDividerHeight;
-                }
-
                 i += getChildrenSkipCount(child, i);
             }
         }
@@ -1458,13 +1440,6 @@
             }
         }
 
-        final boolean showDividerMiddle =
-                (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
-        if ((mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING) {
-            childLeft += mDividerWidth;
-        }
-
         for (int i = 0; i < count; i++) {
             final View child = getVirtualChildAt(i);
 
@@ -1523,16 +1498,16 @@
                         break;
                 }
 
+                if (hasDividerBeforeChildAt(i)) {
+                    childLeft += mDividerWidth;
+                }
+
                 childLeft += lp.leftMargin;
                 setChildFrame(child, childLeft + getLocationOffset(child), childTop,
                         childWidth, childHeight);
                 childLeft += childWidth + lp.rightMargin +
                         getNextLocationOffset(child);
 
-                if (showDividerMiddle) {
-                    childLeft += mDividerWidth;
-                }
-
                 i += getChildrenSkipCount(child, i);
             }
         }
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index af954c9..9d29a60 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -251,7 +251,7 @@
      */
     public void addHeaderView(View v, Object data, boolean isSelectable) {
 
-        if (mAdapter != null) {
+        if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
             throw new IllegalStateException(
                     "Cannot add header view to list -- setAdapter has already been called.");
         }
@@ -261,6 +261,12 @@
         info.data = data;
         info.isSelectable = isSelectable;
         mHeaderViewInfos.add(info);
+
+        // in the case of re-adding a header view, or adding one later on,
+        // we need to notify the observer
+        if (mDataSetObserver != null) {
+            mDataSetObserver.onChanged();
+        }
     }
 
     /**
@@ -294,7 +300,9 @@
         if (mHeaderViewInfos.size() > 0) {
             boolean result = false;
             if (((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
-                mDataSetObserver.onChanged();
+                if (mDataSetObserver != null) {
+                    mDataSetObserver.onChanged();
+                }
                 result = true;
             }
             removeFixedViewInfo(v, mHeaderViewInfos);
@@ -328,6 +336,12 @@
      * @param isSelectable true if the footer view can be selected
      */
     public void addFooterView(View v, Object data, boolean isSelectable) {
+
+        if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
+            throw new IllegalStateException(
+                    "Cannot add footer view to list -- setAdapter has already been called.");
+        }
+
         FixedViewInfo info = new FixedViewInfo();
         info.view = v;
         info.data = data;
@@ -371,7 +385,9 @@
         if (mFooterViewInfos.size() > 0) {
             boolean result = false;
             if (((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
-                mDataSetObserver.onChanged();
+                if (mDataSetObserver != null) {
+                    mDataSetObserver.onChanged();
+                }
                 result = true;
             }
             removeFixedViewInfo(v, mFooterViewInfos);
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 11b594c..16d5539 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -687,7 +687,7 @@
                 return;
             }
             invalidate();
-            mUpperContextView.openOverflowMenu();
+            mUpperContextView.showOverflowMenu();
         }
     }
 
diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
index 2d067da..b54daba 100644
--- a/core/java/com/android/internal/view/StandaloneActionMode.java
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -135,6 +135,6 @@
 
     public void onMenuModeChange(MenuBuilder menu) {
         invalidate();
-        mContextView.openOverflowMenu();
+        mContextView.showOverflowMenu();
     }
 }
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index ca1aa0b..beacf75 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -28,7 +28,7 @@
  * @hide
  */
 public class ActionMenuItemView extends LinearLayout
-        implements MenuView.ItemView, View.OnClickListener {
+        implements MenuView.ItemView, View.OnClickListener, ActionMenuView.ActionMenuChildView {
     private static final String TAG = "ActionMenuItemView";
 
     private MenuItemImpl mItemData;
@@ -137,4 +137,12 @@
     public boolean showsIcon() {
         return true;
     }
+
+    public boolean needsDividerBefore() {
+        return hasText() && mItemData.getIcon() == null;
+    }
+
+    public boolean needsDividerAfter() {
+        return hasText();
+    }
 }
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
new file mode 100644
index 0000000..a05fa53
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.MenuItem;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for building action menus as seen in the action bar and action modes.
+ */
+public class ActionMenuPresenter extends BaseMenuPresenter {
+    private View mOverflowButton;
+    private boolean mReserveOverflow;
+    private int mWidthLimit;
+    private int mActionItemWidthLimit;
+    private int mMaxItems;
+
+    // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+    private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+    private View mScrapActionButtonView;
+
+    private OverflowPopup mOverflowPopup;
+    private ActionButtonSubmenu mActionButtonPopup;
+
+    private OpenOverflowRunnable mPostedOpenRunnable;
+
+    public ActionMenuPresenter() {
+        super(com.android.internal.R.layout.action_menu_layout,
+                com.android.internal.R.layout.action_menu_item_layout);
+    }
+
+    @Override
+    public void initForMenu(Context context, MenuBuilder menu) {
+        super.initForMenu(context, menu);
+
+        final Resources res = context.getResources();
+        final int screen = res.getConfiguration().screenLayout;
+        // TODO Use the no-buttons specifier instead here
+        mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
+                Configuration.SCREENLAYOUT_SIZE_XLARGE;
+        mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
+
+        // Measure for initial configuration
+        mMaxItems = res.getInteger(com.android.internal.R.integer.max_action_buttons);
+
+        int width = mWidthLimit;
+        if (mReserveOverflow) {
+            OverflowMenuButton button = new OverflowMenuButton(mContext);
+            mOverflowButton = button;
+            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+            mOverflowButton.measure(spec, spec);
+            width -= mOverflowButton.getMeasuredWidth();
+        } else {
+            mOverflowButton = null;
+        }
+
+        mActionItemWidthLimit = width;
+
+        // Drop a scrap view as it may no longer reflect the proper context/config.
+        mScrapActionButtonView = null;
+    }
+
+    @Override
+    public MenuView getMenuView(ViewGroup root) {
+        MenuView result = super.getMenuView(root);
+        ((ActionMenuView) result).setPresenter(this);
+        return result;
+    }
+
+    @Override
+    public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+        final View actionView = item.getActionView();
+        return actionView != null ? actionView : super.getItemView(item, convertView, parent);
+    }
+
+    @Override
+    public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
+        itemView.initialize(item, 0);
+        ((ActionMenuItemView) itemView).setItemInvoker((ActionMenuView) mMenuView);
+    }
+
+    @Override
+    public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+        return item.isActionButton();
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        super.updateMenuView(cleared);
+
+        if (mReserveOverflow && mMenu.getNonActionItems().size() > 0) {
+            if (mOverflowButton == null) {
+                mOverflowButton = new OverflowMenuButton(mContext);
+            }
+            ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
+            if (parent != mMenuView) {
+                if (parent != null) {
+                    parent.removeView(mOverflowButton);
+                }
+                ((ViewGroup) mMenuView).addView(mOverflowButton);
+            }
+        } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
+            ((ViewGroup) mMenuView).removeView(mOverflowButton);
+        }
+    }
+
+    @Override
+    public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+        if (parent.getChildAt(childIndex) == mOverflowButton) return false;
+        return super.filterLeftoverView(parent, childIndex);
+    }
+
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (!subMenu.hasVisibleItems()) return false;
+
+        SubMenuBuilder topSubMenu = subMenu;
+        while (topSubMenu.getParentMenu() != mMenu) {
+            topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+        }
+        View anchor = findViewForItem(topSubMenu.getItem());
+        if (anchor == null) return false;
+
+        mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
+        mActionButtonPopup.setAnchorView(anchor);
+        mActionButtonPopup.show();
+        super.onSubMenuSelected(subMenu);
+        return true;
+    }
+
+    private View findViewForItem(MenuItem item) {
+        final ViewGroup parent = (ViewGroup) mMenuView;
+        if (parent == null) return null;
+
+        final int count = parent.getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = parent.getChildAt(i);
+            if (child instanceof MenuView.ItemView &&
+                    ((MenuView.ItemView) child).getItemData() == item) {
+                return child;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Display the overflow menu if one is present.
+     * @return true if the overflow menu was shown, false otherwise.
+     */
+    public boolean showOverflowMenu() {
+        if (mReserveOverflow && !isOverflowMenuShowing() && mMenuView != null &&
+                mPostedOpenRunnable == null) {
+            Log.d("ActionMenuPresenter", "showOverflowMenu");
+            OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
+            mPostedOpenRunnable = new OpenOverflowRunnable(popup);
+            // Post this for later; we might still need a layout for the anchor to be right.
+            ((View) mMenuView).post(mPostedOpenRunnable);
+
+            // ActionMenuPresenter uses null as a callback argument here
+            // to indicate overflow is opening.
+            super.onSubMenuSelected(null);
+
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Hide the overflow menu if it is currently showing.
+     *
+     * @return true if the overflow menu was hidden, false otherwise.
+     */
+    public boolean hideOverflowMenu() {
+        if (mPostedOpenRunnable != null && mMenuView != null) {
+            ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
+            return true;
+        }
+
+        MenuPopupHelper popup = mOverflowPopup;
+        if (popup != null) {
+            popup.dismiss();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Dismiss all popup menus - overflow and submenus.
+     * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+     */
+    public boolean dismissPopupMenus() {
+        boolean result = hideOverflowMenu();
+        result |= hideSubMenus();
+        return result;
+    }
+
+    /**
+     * Dismiss all submenu popups.
+     *
+     * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+     */
+    public boolean hideSubMenus() {
+        if (mActionButtonPopup != null) {
+            mActionButtonPopup.dismiss();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return true if the overflow menu is currently showing
+     */
+    public boolean isOverflowMenuShowing() {
+        return mOverflowPopup != null && mOverflowPopup.isShowing();
+    }
+
+    /**
+     * @return true if space has been reserved in the action menu for an overflow item.
+     */
+    public boolean isOverflowReserved() {
+        return mReserveOverflow;
+    }
+
+    public boolean flagActionItems() {
+        final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+        final int itemsSize = visibleItems.size();
+        int maxActions = mMaxItems;
+        int widthLimit = mActionItemWidthLimit;
+        final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+        final ViewGroup parent = (ViewGroup) mMenuView;
+
+        int requiredItems = 0;
+        int requestedItems = 0;
+        int firstActionWidth = 0;
+        boolean hasOverflow = false;
+        for (int i = 0; i < itemsSize; i++) {
+            MenuItemImpl item = visibleItems.get(i);
+            if (item.requiresActionButton()) {
+                requiredItems++;
+            } else if (item.requestsActionButton()) {
+                requestedItems++;
+            } else {
+                hasOverflow = true;
+            }
+        }
+
+        // Reserve a spot for the overflow item if needed.
+        if (mReserveOverflow &&
+                (hasOverflow || requiredItems + requestedItems > maxActions)) {
+            maxActions--;
+        }
+        maxActions -= requiredItems;
+
+        final SparseBooleanArray seenGroups = mActionButtonGroups;
+        seenGroups.clear();
+
+        // Flag as many more requested items as will fit.
+        for (int i = 0; i < itemsSize; i++) {
+            MenuItemImpl item = visibleItems.get(i);
+
+            if (item.requiresActionButton()) {
+                View v = item.getActionView();
+                if (v == null) {
+                    v = getItemView(item, mScrapActionButtonView, parent);
+                    if (mScrapActionButtonView == null) {
+                        mScrapActionButtonView = v;
+                    }
+                }
+                v.measure(querySpec, querySpec);
+                final int measuredWidth = v.getMeasuredWidth();
+                widthLimit -= measuredWidth;
+                if (firstActionWidth == 0) {
+                    firstActionWidth = measuredWidth;
+                }
+                final int groupId = item.getGroupId();
+                if (groupId != 0) {
+                    seenGroups.put(groupId, true);
+                }
+            } else if (item.requestsActionButton()) {
+                // Items in a group with other items that already have an action slot
+                // can break the max actions rule, but not the width limit.
+                final int groupId = item.getGroupId();
+                final boolean inGroup = seenGroups.get(groupId);
+                boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
+                maxActions--;
+
+                if (isAction) {
+                    View v = item.getActionView();
+                    if (v == null) {
+                        v = getItemView(item, mScrapActionButtonView, parent);
+                        if (mScrapActionButtonView == null) {
+                            mScrapActionButtonView = v;
+                        }
+                    }
+                    v.measure(querySpec, querySpec);
+                    final int measuredWidth = v.getMeasuredWidth();
+                    widthLimit -= measuredWidth;
+                    if (firstActionWidth == 0) {
+                        firstActionWidth = measuredWidth;
+                    }
+
+                    // Did this push the entire first item past halfway?
+                    if (widthLimit + firstActionWidth <= 0) {
+                        isAction = false;
+                    }
+                }
+
+                if (isAction && groupId != 0) {
+                    seenGroups.put(groupId, true);
+                } else if (inGroup) {
+                    // We broke the width limit. Demote the whole group, they all overflow now.
+                    seenGroups.put(groupId, false);
+                    for (int j = 0; j < i; j++) {
+                        MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+                        if (areYouMyGroupie.getGroupId() == groupId) {
+                            areYouMyGroupie.setIsActionButton(false);
+                        }
+                    }
+                }
+
+                item.setIsActionButton(isAction);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        dismissPopupMenus();
+        super.onCloseMenu(menu, allMenusAreClosing);
+    }
+
+    private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
+        public OverflowMenuButton(Context context) {
+            super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
+
+            setClickable(true);
+            setFocusable(true);
+            setVisibility(VISIBLE);
+            setEnabled(true);
+        }
+
+        @Override
+        public boolean performClick() {
+            if (super.performClick()) {
+                return true;
+            }
+
+            playSoundEffect(SoundEffectConstants.CLICK);
+            showOverflowMenu();
+            return true;
+        }
+
+        public boolean needsDividerBefore() {
+            return true;
+        }
+
+        public boolean needsDividerAfter() {
+            return false;
+        }
+    }
+
+    private class OverflowPopup extends MenuPopupHelper {
+        public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
+                boolean overflowOnly) {
+            super(context, menu, anchorView, overflowOnly);
+        }
+
+        @Override
+        public void onDismiss() {
+            super.onDismiss();
+            mMenu.close();
+            mOverflowPopup = null;
+        }
+    }
+
+    private class ActionButtonSubmenu extends MenuPopupHelper {
+        private SubMenuBuilder mSubMenu;
+
+        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
+            super(context, subMenu);
+            mSubMenu = subMenu;
+
+            MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
+            if (!item.isActionButton()) {
+                // Give a reasonable anchor to nested submenus.
+                setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
+            }
+        }
+
+        @Override
+        public void onDismiss() {
+            super.onDismiss();
+            mSubMenu.close();
+            mActionButtonPopup = null;
+        }
+    }
+
+    private class OpenOverflowRunnable implements Runnable {
+        private OverflowPopup mPopup;
+
+        public OpenOverflowRunnable(OverflowPopup popup) {
+            mPopup = popup;
+        }
+
+        public void run() {
+            mMenu.changeMenuMode();
+            if (mPopup.tryShow()) {
+                mOverflowPopup = mPopup;
+                mPostedOpenRunnable = null;
+            }
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index 7775f00..0ea9c89 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -17,63 +17,22 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.Gravity;
-import android.view.SoundEffectConstants;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 
-import java.util.ArrayList;
-
 /**
  * @hide
  */
 public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
     private static final String TAG = "ActionMenuView";
-
-    // TODO Theme/style this.
-    private static final int DIVIDER_PADDING = 12; // dips
     
     private MenuBuilder mMenu;
 
-    private int mMaxItems;
-    private int mWidthLimit;
     private boolean mReserveOverflow;
-    private OverflowMenuButton mOverflowButton;
-    private MenuPopupHelper mOverflowPopup;
-
-    private float mDividerPadding;
-    
-    private Drawable mDivider;
-
-    private final Runnable mShowOverflow = new Runnable() {
-        public void run() {
-            showOverflowMenu();
-        }
-    };
-    
-    private class OpenOverflowRunnable implements Runnable {
-        private MenuPopupHelper mPopup;
-
-        public OpenOverflowRunnable(MenuPopupHelper popup) {
-            mPopup = popup;
-        }
-
-        public void run() {
-            if (mPopup.tryShow()) {
-                mOverflowPopup = mPopup;
-                mPostedOpenRunnable = null;
-            }
-        }
-    }
-
-    private OpenOverflowRunnable mPostedOpenRunnable;
+    private ActionMenuPresenter mPresenter;
 
     public ActionMenuView(Context context) {
         this(context, null);
@@ -81,60 +40,28 @@
     
     public ActionMenuView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        
-        final Resources res = getResources();
-
-        // Measure for initial configuration
-        mMaxItems = getMaxActionButtons();
-
-        // TODO There has to be a better way to indicate that we don't have a hard menu key.
-        final int screen = res.getConfiguration().screenLayout;
-        mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
-                Configuration.SCREENLAYOUT_SIZE_XLARGE;
-        mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
-        
-        TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
-        mDivider = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
-        a.recycle();
-        
-        mDividerPadding = DIVIDER_PADDING * res.getDisplayMetrics().density;
-
         setBaselineAligned(false);
     }
 
+    public void setPresenter(ActionMenuPresenter presenter) {
+        mPresenter = presenter;
+    }
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        final int screen = newConfig.screenLayout;
-        mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
-                Configuration.SCREENLAYOUT_SIZE_XLARGE;
-        mMaxItems = getMaxActionButtons();
-        mWidthLimit = getResources().getDisplayMetrics().widthPixels / 2;
-        if (mMenu != null) {
-            mMenu.setMaxActionItems(mMaxItems);
-            updateChildren(false);
-        }
+        mPresenter.updateMenuView(false);
 
-        if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
-            mOverflowPopup.dismiss();
-            post(mShowOverflow);
+        if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
+            mPresenter.hideOverflowMenu();
+            mPresenter.showOverflowMenu();
         }
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
-            mOverflowPopup.dismiss();
-        }
-        removeCallbacks(mShowOverflow);
-        if (mPostedOpenRunnable != null) {
-            removeCallbacks(mPostedOpenRunnable);
-        }
-    }
-
-    private int getMaxActionButtons() {
-        return getResources().getInteger(com.android.internal.R.integer.max_action_buttons);
+        mPresenter.dismissPopupMenus();
     }
 
     public boolean isOverflowReserved() {
@@ -144,10 +71,6 @@
     public void setOverflowReserved(boolean reserveOverflow) {
         mReserveOverflow = reserveOverflow;
     }
-    
-    public View getOverflowButton() {
-        return mOverflowButton;
-    }
 
     @Override
     protected LayoutParams generateDefaultLayoutParams() {
@@ -169,6 +92,11 @@
         return generateDefaultLayoutParams();
     }
 
+    @Override
+    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+        return p instanceof LayoutParams;
+    }
+
     public boolean invokeItem(MenuItemImpl item) {
         return mMenu.performItemAction(item, 0);
     }
@@ -177,243 +105,26 @@
         return 0;
     }
 
-    public void initialize(MenuBuilder menu, int menuType) {
-        int width = mWidthLimit;
-        if (mReserveOverflow) {
-            if (mOverflowButton == null) {
-                OverflowMenuButton button = new OverflowMenuButton(mContext);
-                mOverflowButton = button;
-            }
-            final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-            mOverflowButton.measure(spec, spec);
-            width -= mOverflowButton.getMeasuredWidth();
-        }
-
-        menu.setActionWidthLimit(width);
-
-        menu.setMaxActionItems(mMaxItems);
-        final boolean cleared = mMenu != menu;
+    public void initialize(MenuBuilder menu) {
         mMenu = menu;
-        updateChildren(cleared);
     }
 
-    public void updateChildren(boolean cleared) {
-        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(mReserveOverflow);
-        final int itemCount = itemsToShow.size();
-        
-        boolean needsDivider = false;
-        int childIndex = 0;
-        for (int i = 0; i < itemCount; i++) {
-            final MenuItemImpl itemData = itemsToShow.get(i);
-            boolean hasDivider = false;
-
-            if (needsDivider) {
-                if (!isDivider(getChildAt(childIndex))) {
-                    addView(makeDividerView(), childIndex, makeDividerLayoutParams());
-                }
-                hasDivider = true;
-                childIndex++;
-            }
-
-            View childToAdd = itemData.getActionView();
-            boolean needsPreDivider = false;
-            if (childToAdd != null) {
-                childToAdd.setLayoutParams(makeActionViewLayoutParams(childToAdd));
-            } else {
-                ActionMenuItemView view = (ActionMenuItemView) itemData.getItemView(
-                        MenuBuilder.TYPE_ACTION_BUTTON, this);
-                view.setItemInvoker(this);
-                needsPreDivider = i > 0 && !hasDivider && view.hasText() &&
-                        itemData.getIcon() == null;
-                needsDivider = view.hasText();
-                childToAdd = view;
-            }
-
-            boolean addPreDivider = removeChildrenUntil(childIndex, childToAdd, needsPreDivider);
-
-            if (addPreDivider) addView(makeDividerView(), childIndex, makeDividerLayoutParams());
-            if (needsPreDivider) childIndex++;
-
-            if (getChildAt(childIndex) != childToAdd) {
-                addView(childToAdd, childIndex);
-            }
-            childIndex++;
+    @Override
+    protected boolean hasDividerBeforeChildAt(int childIndex) {
+        final View childBefore = getChildAt(childIndex - 1);
+        final View child = getChildAt(childIndex);
+        boolean result = false;
+        if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+            result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
         }
-
-        final boolean hasOverflow = mOverflowButton != null && mOverflowButton.getParent() == this;
-        final boolean needsOverflow = mReserveOverflow && mMenu.getNonActionItems(true).size() > 0;
-
-        if (hasOverflow != needsOverflow) {
-            if (needsOverflow) {
-                if (mOverflowButton == null) {
-                    OverflowMenuButton button = new OverflowMenuButton(mContext);
-                    mOverflowButton = button;
-                }
-                boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton, true);
-                if (addDivider && itemCount > 0) {
-                    addView(makeDividerView(), childIndex, makeDividerLayoutParams());
-                    childIndex++;
-                }
-                addView(mOverflowButton, childIndex);
-                childIndex++;
-            } else {
-                removeView(mOverflowButton);
-            }
-        } else {
-            if (needsOverflow) {
-                boolean overflowDivider = itemCount > 0;
-                boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton,
-                        overflowDivider);
-                if (addDivider && itemCount > 0) {
-                    addView(makeDividerView(), childIndex, makeDividerLayoutParams());
-                }
-                if (overflowDivider) {
-                    childIndex += 2;
-                } else {
-                    childIndex++;
-                }
-            }
+        if (childIndex > 0 && child instanceof ActionMenuChildView) {
+            result |= ((ActionMenuChildView) child).needsDividerBefore();
         }
-
-        while (getChildCount() > childIndex) {
-            removeViewAt(childIndex);
-        }
-    }
-
-    private boolean removeChildrenUntil(int start, View targetChild, boolean needsPreDivider) {
-        final int childCount = getChildCount();
-        boolean found = false;
-        for (int i = start; i < childCount; i++) {
-            final View child = getChildAt(i);
-            if (child == targetChild) {
-                found = true;
-                break;
-            }
-        }
-
-        if (!found) {
-            return needsPreDivider;
-        }
-
-        for (int i = start; i < getChildCount(); ) {
-            final View child = getChildAt(i);
-            if (needsPreDivider && isDivider(child)) {
-                needsPreDivider = false;
-                i++;
-                continue;
-            }
-            if (child == targetChild) break;
-            removeViewAt(i);
-        }
-
-        return needsPreDivider;
-    }
-
-    private static boolean isDivider(View v) {
-        return v != null && v.getId() == com.android.internal.R.id.action_menu_divider;
-    }
-
-    public boolean showOverflowMenu() {
-        if (mOverflowButton != null && !isOverflowMenuShowing()) {
-            mMenu.getCallback().onMenuModeChange(mMenu);
-            return true;
-        }
-        return false;
-    }
-
-    public void openOverflowMenu() {
-        OverflowPopup popup = new OverflowPopup(getContext(), mMenu, mOverflowButton, true);
-        mPostedOpenRunnable = new OpenOverflowRunnable(popup);
-        // Post this for later; we might still need a layout for the anchor to be right.
-        post(mPostedOpenRunnable);
-    }
-
-    public boolean isOverflowMenuShowing() {
-        return mOverflowPopup != null && mOverflowPopup.isShowing();
-    }
-
-    public boolean isOverflowMenuOpen() {
-        return mOverflowPopup != null;
-    }
-
-    public boolean hideOverflowMenu() {
-        if (mPostedOpenRunnable != null) {
-            removeCallbacks(mPostedOpenRunnable);
-            return true;
-        }
-
-        MenuPopupHelper popup = mOverflowPopup;
-        if (popup != null) {
-            popup.dismiss();
-            return true;
-        }
-        return false;
-    }
-
-    private boolean addItemView(boolean needsDivider, ActionMenuItemView view) {
-        view.setItemInvoker(this);
-        boolean hasText = view.hasText();
-        
-        if (hasText && needsDivider) {
-            addView(makeDividerView(), makeDividerLayoutParams());
-        }
-        addView(view);
-        return hasText;
-    }
-
-    private ImageView makeDividerView() {
-        ImageView result = new ImageView(mContext);
-        result.setImageDrawable(mDivider);
-        result.setScaleType(ImageView.ScaleType.FIT_XY);
-        result.setId(com.android.internal.R.id.action_menu_divider);
         return result;
     }
 
-    private LayoutParams makeDividerLayoutParams() {
-        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
-                LayoutParams.MATCH_PARENT);
-        params.topMargin = (int) mDividerPadding;
-        params.bottomMargin = (int) mDividerPadding;
-        return params;
-    }
-
-    private LayoutParams makeActionViewLayoutParams(View view) {
-        return generateLayoutParams(view.getLayoutParams());
-    }
-
-    private class OverflowMenuButton extends ImageButton {
-        public OverflowMenuButton(Context context) {
-            super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
-
-            setClickable(true);
-            setFocusable(true);
-            setVisibility(VISIBLE);
-            setEnabled(true);
-        }
-
-        @Override
-        public boolean performClick() {
-            if (super.performClick()) {
-                return true;
-            }
-
-            playSoundEffect(SoundEffectConstants.CLICK);
-            showOverflowMenu();
-            return true;
-        }
-    }
-
-    private class OverflowPopup extends MenuPopupHelper {
-        public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
-                boolean overflowOnly) {
-            super(context, menu, anchorView, overflowOnly);
-        }
-
-        @Override
-        public void onDismiss() {
-            super.onDismiss();
-            mMenu.getCallback().onCloseMenu(mMenu, true);
-            mOverflowPopup = null;
-        }
+    public interface ActionMenuChildView {
+        public boolean needsDividerBefore();
+        public boolean needsDividerAfter();
     }
 }
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
new file mode 100644
index 0000000..71511c6
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Base class for MenuPresenters that have a consistent container view and item
+ * views. Behaves similarly to an AdapterView in that existing item views will
+ * be reused if possible when items change.
+ */
+public abstract class BaseMenuPresenter implements MenuPresenter {
+    protected Context mContext;
+    protected MenuBuilder mMenu;
+    protected LayoutInflater mInflater;
+    private Callback mCallback;
+
+    private int mMenuLayoutRes;
+    private int mItemLayoutRes;
+
+    protected MenuView mMenuView;
+
+    /**
+     * Construct a new BaseMenuPresenter.
+     *
+     * @param menuLayoutRes Layout resource ID for the menu container view
+     * @param itemLayoutRes Layout resource ID for a single item view
+     */
+    public BaseMenuPresenter(int menuLayoutRes, int itemLayoutRes) {
+        mMenuLayoutRes = menuLayoutRes;
+        mItemLayoutRes = itemLayoutRes;
+    }
+
+    @Override
+    public void initForMenu(Context context, MenuBuilder menu) {
+        mContext = context;
+        mInflater = LayoutInflater.from(mContext);
+        mMenu = menu;
+    }
+
+    @Override
+    public MenuView getMenuView(ViewGroup root) {
+        if (mMenuView == null) {
+            mMenuView = (MenuView) mInflater.inflate(mMenuLayoutRes, root, false);
+            mMenuView.initialize(mMenu);
+            updateMenuView(true);
+        }
+
+        return mMenuView;
+    }
+
+    /**
+     * Reuses item views when it can
+     */
+    public void updateMenuView(boolean cleared) {
+        mMenu.flagActionItems();
+        ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+        final int itemCount = visibleItems.size();
+        final ViewGroup parent = (ViewGroup) mMenuView;
+        int childIndex = 0;
+        for (int i = 0; i < itemCount; i++) {
+            MenuItemImpl item = visibleItems.get(i);
+            if (shouldIncludeItem(childIndex, item)) {
+                final View convertView = parent.getChildAt(childIndex);
+                final View itemView = getItemView(item, convertView, parent);
+                if (itemView != convertView) {
+                    addItemView(itemView, childIndex);
+                }
+                childIndex++;
+            }
+        }
+
+        // Remove leftover views.
+        while (childIndex < parent.getChildCount()) {
+            if (!filterLeftoverView(parent, childIndex)) {
+                childIndex++;
+            }
+        }
+    }
+
+    /**
+     * Add an item view at the given index.
+     *
+     * @param itemView View to add
+     * @param childIndex Index within the parent to insert at
+     */
+    protected void addItemView(View itemView, int childIndex) {
+        ((ViewGroup) mMenuView).addView(itemView, childIndex);
+    }
+
+    /**
+     * Filter the child view at index and remove it if appropriate.
+     * @param parent Parent to filter from
+     * @param childIndex Index to filter
+     * @return true if the child view at index was removed
+     */
+    protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+        parent.removeViewAt(childIndex);
+        return true;
+    }
+
+    public void setCallback(Callback cb) {
+        mCallback = cb;
+    }
+
+    /**
+     * Create a new item view that can be re-bound to other item data later.
+     *
+     * @return The new item view
+     */
+    public MenuView.ItemView createItemView(ViewGroup parent) {
+        return (MenuView.ItemView) mInflater.inflate(mItemLayoutRes, parent, false);
+    }
+
+    /**
+     * Prepare an item view for use. See AdapterView for the basic idea at work here.
+     * This may require creating a new item view, but well-behaved implementations will
+     * re-use the view passed as convertView if present. The returned view will be populated
+     * with data from the item parameter.
+     *
+     * @param item Item to present
+     * @param convertView Existing view to reuse
+     * @param parent Intended parent view - use for inflation.
+     * @return View that presents the requested menu item
+     */
+    public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+        MenuView.ItemView itemView;
+        if (convertView instanceof MenuView.ItemView) {
+            itemView = (MenuView.ItemView) convertView;
+        } else {
+            itemView = createItemView(parent);
+        }
+        bindItemView(item, itemView);
+        return (View) itemView;
+    }
+
+    /**
+     * Bind item data to an existing item view.
+     *
+     * @param item Item to bind
+     * @param itemView View to populate with item data
+     */
+    public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
+
+    /**
+     * Filter item by child index and item data.
+     *
+     * @param childIndex Indended presentation index of this item
+     * @param item Item to present
+     * @return true if this item should be included in this menu presentation; false otherwise
+     */
+    public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+        return true;
+    }
+
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        if (mCallback != null) {
+            mCallback.onCloseMenu(menu, allMenusAreClosing);
+        }
+    }
+
+    public boolean onSubMenuSelected(SubMenuBuilder menu) {
+        if (mCallback != null) {
+            return mCallback.onOpenSubMenu(menu);
+        }
+        return false;
+    }
+
+    public boolean flagActionItems() {
+        return false;
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
index 9e4b4ce..723ece4 100644
--- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -17,17 +17,15 @@
 package com.android.internal.view.menu;
 
 
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.view.View;
 import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.ListView;
 import android.widget.AdapterView.OnItemClickListener;
-
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+import android.widget.ListView;
 
 /**
  * The expanded menu view is a list-like menu with all of the available menu items.  It is opened
@@ -53,23 +51,8 @@
         setOnItemClickListener(this);
     }
 
-    public void initialize(MenuBuilder menu, int menuType) {
+    public void initialize(MenuBuilder menu) {
         mMenu = menu;
-
-        setAdapter(menu.new MenuAdapter(menuType));
-    }
-
-    public void updateChildren(boolean cleared) {
-        ListAdapter adapter = getAdapter();
-        // Tell adapter of the change, it will notify the mListView
-        if (adapter != null) {
-            if (cleared) {
-                ((BaseAdapter)adapter).notifyDataSetInvalidated();
-            }
-            else {
-                ((BaseAdapter)adapter).notifyDataSetChanged();
-            }
-        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 3c5b422..afa8a01 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -112,6 +112,10 @@
         setEnabled(itemData.isEnabled());
     }
 
+    public void setItemData(MenuItemImpl data) {
+        mItemData = data;
+    }
+
     @Override
     public boolean performClick() {
         // Let the view's click listener have top priority (the More button relies on this)
diff --git a/core/java/com/android/internal/view/menu/IconMenuPresenter.java b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
new file mode 100644
index 0000000..f717904
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.view.menu;
+
+import com.android.internal.view.menu.MenuView.ItemView;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for the classic "six-pack" icon menu.
+ */
+public class IconMenuPresenter extends BaseMenuPresenter {
+    private IconMenuItemView mMoreView;
+    private int mMaxItems = -1;
+
+    private static final String VIEWS_TAG = "android:menu:icon";
+
+    public IconMenuPresenter() {
+        super(com.android.internal.R.layout.icon_menu_layout,
+                com.android.internal.R.layout.icon_menu_item_layout);
+    }
+
+    @Override
+    public void initForMenu(Context context, MenuBuilder menu) {
+        mContext = new ContextThemeWrapper(context, com.android.internal.R.style.Theme_IconMenu);
+        mInflater = LayoutInflater.from(mContext);
+        mMenu = menu;
+        mMaxItems = -1;
+    }
+
+    @Override
+    public void bindItemView(MenuItemImpl item, ItemView itemView) {
+        final IconMenuItemView view = (IconMenuItemView) itemView;
+        view.setItemData(item);
+
+        view.initialize(item.getTitleForItemView(view), item.getIcon());
+
+        view.setVisibility(item.isVisible() ? View.VISIBLE : View.GONE);
+        view.setEnabled(view.isEnabled());
+        view.setLayoutParams(view.getTextAppropriateLayoutParams());
+    }
+
+    @Override
+    public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
+        boolean fits = (itemsToShow.size() == mMaxItems && childIndex < mMaxItems) ||
+                childIndex < mMaxItems - 1;
+        return fits && !item.isActionButton();
+    }
+
+    @Override
+    protected void addItemView(View itemView, int childIndex) {
+        final IconMenuItemView v = (IconMenuItemView) itemView;
+        final IconMenuView parent = (IconMenuView) mMenuView;
+
+        v.setIconMenuView(parent);
+        v.setItemInvoker(parent);
+        v.setBackgroundDrawable(parent.getItemBackgroundDrawable());
+        super.addItemView(itemView, childIndex);
+    }
+
+    @Override
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (!subMenu.hasVisibleItems()) return false;
+
+        // The window manager will give us a token.
+        new MenuDialogHelper(subMenu).show(null);
+        super.onSubMenuSelected(subMenu);
+        return true;
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        final IconMenuView menuView = (IconMenuView) mMenuView;
+        if (mMaxItems < 0) mMaxItems = menuView.getMaxItems();
+        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
+        final boolean needsMore = itemsToShow.size() > mMaxItems;
+        super.updateMenuView(cleared);
+
+        if (needsMore && (mMoreView == null || mMoreView.getParent() != menuView)) {
+            if (mMoreView == null) {
+                mMoreView = menuView.createMoreItemView();
+                mMoreView.setBackgroundDrawable(menuView.getItemBackgroundDrawable());
+            }
+            menuView.addView(mMoreView);
+        } else if (!needsMore && mMoreView != null) {
+            menuView.removeView(mMoreView);
+        }
+
+        menuView.setNumActualItemsShown(needsMore ? mMaxItems - 1 : itemsToShow.size());
+    }
+
+    @Override
+    protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+        if (parent.getChildAt(childIndex) != mMoreView) {
+            return super.filterLeftoverView(parent, childIndex);
+        }
+        return false;
+    }
+
+    public int getNumActualItemsShown() {
+        return ((IconMenuView) mMenuView).getNumActualItemsShown();
+    }
+
+    public void saveHierarchyState(Bundle outState) {
+        SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+        if (mMenuView != null) {
+            ((View) mMenuView).saveHierarchyState(viewStates);
+        }
+        outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+    }
+
+    public void restoreHierarchyState(Bundle inState) {
+        SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
+        if (viewStates != null) {
+            ((View) mMenuView).restoreHierarchyState(viewStates);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index d18c9727..dab43eb 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -80,10 +80,7 @@
     
     /** Icon for the 'More' button */
     private Drawable mMoreIcon;
-    
-    /** Item view for the 'More' button */
-    private IconMenuItemView mMoreItemView;
-    
+
     /** Background of each item (should contain the selected and focused states) */
     private Drawable mItemBackground;
 
@@ -172,6 +169,10 @@
         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
     }
 
+    int getMaxItems() {
+        return mMaxItems;
+    }
+
     /**
      * Figures out the layout for the menu items.
      * 
@@ -277,23 +278,8 @@
         return true;
     }
 
-    /**
-     * Adds an IconMenuItemView to this icon menu view.
-     * @param itemView The item's view to add
-     */
-    private void addItemView(IconMenuItemView itemView) {   
-        // Set ourselves on the item view
-        itemView.setIconMenuView(this);
-        
-        // Apply the background to the item view
-        itemView.setBackgroundDrawable(
-                mItemBackground.getConstantState().newDrawable(
-                        getContext().getResources()));
-
-        // This class is the invoker for all its item views 
-        itemView.setItemInvoker(this);
-        
-        addView(itemView, itemView.getTextAppropriateLayoutParams());
+    Drawable getItemBackgroundDrawable() {
+        return mItemBackground.getConstantState().newDrawable(getContext().getResources());
     }
 
     /**
@@ -302,25 +288,23 @@
      * have a MenuItemData backing it.
      * @return The IconMenuItemView for the 'More' button
      */
-    private IconMenuItemView createMoreItemView() {
-        LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater();
+    IconMenuItemView createMoreItemView() {
+        Context context = getContext();
+        LayoutInflater inflater = LayoutInflater.from(context);
         
         final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
                 com.android.internal.R.layout.icon_menu_item_layout, null);
         
-        Resources r = getContext().getResources();
+        Resources r = context.getResources();
         itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
         
         // Set up a click listener on the view since there will be no invocation sequence
         // due to the lack of a MenuItemData this view
         itemView.setOnClickListener(new OnClickListener() {
             public void onClick(View v) {
-                // Switches the menu to expanded mode
-                MenuBuilder.Callback cb = mMenu.getCallback();
-                if (cb != null) {
-                    // Call callback
-                    cb.onMenuModeChange(mMenu);
-                }
+                // Switches the menu to expanded mode. Requires support from
+                // the menu's active callback.
+                mMenu.changeMenuMode();
             }
         });
         
@@ -328,51 +312,8 @@
     }
     
     
-    public void initialize(MenuBuilder menu, int menuType) {
+    public void initialize(MenuBuilder menu) {
         mMenu = menu;
-        updateChildren(true);
-    }
-
-    public void updateChildren(boolean cleared) {
-        // This method does a clear refresh of children
-        removeAllViews();
-        
-        // IconMenuView never wants content sorted for an overflow action button, since
-        // it is never used in the presence of an overflow button.
-        final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(false);
-        final int numItems = itemsToShow.size();
-        final int numItemsThatCanFit = mMaxItems;
-        // Minimum of the num that can fit and the num that we have
-        final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
-        
-        MenuItemImpl itemData;
-        // Traverse through all but the last item that can fit since that last item can either
-        // be a 'More' button or a sixth item
-        for (int i = 0; i < minFitMinus1AndNumItems; i++) {
-            itemData = itemsToShow.get(i);
-            addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this));
-        }
-
-        if (numItems > numItemsThatCanFit) {
-            // If there are more items than we can fit, show the 'More' button to
-            // switch to expanded mode
-            if (mMoreItemView == null) {
-                mMoreItemView = createMoreItemView();
-            }
-            
-            addItemView(mMoreItemView);
-            
-            // The last view is the more button, so the actual number of items is one less than
-            // the number that can fit
-            mNumActualItemsShown = numItemsThatCanFit - 1;
-        } else if (numItems == numItemsThatCanFit) {
-            // There are exactly the number we can show, so show the last item 
-            final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1);
-            addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this));
-            
-            // The items shown fit exactly
-            mNumActualItemsShown = numItemsThatCanFit;
-        }
     }
 
     /**
@@ -463,13 +404,6 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (mHasStaleChildren) {
-            mHasStaleChildren = false;
-
-            // If we have stale data, resync with the menu
-            updateChildren(false);
-        }
-        
         int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
         calculateItemFittingMetadata(measuredWidth);
         layoutItems(measuredWidth);
@@ -564,6 +498,9 @@
         return mNumActualItemsShown;
     }
     
+    void setNumActualItemsShown(int count) {
+        mNumActualItemsShown = count;
+    }
     
     public int getWindowAnimations() {
         return mAnimations;
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 02584b6..0c3c605 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -48,6 +48,8 @@
     
     private int mMenuType;
     
+    private LayoutInflater mInflater;
+
     public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs);
     
@@ -187,7 +189,7 @@
     }
     
     public void setIcon(Drawable icon) {
-        final boolean showIcon = mItemData.shouldShowIcon(mMenuType);
+        final boolean showIcon = mItemData.shouldShowIcon();
         if (!showIcon && !mPreserveIconSpacing) {
             return;
         }
@@ -212,14 +214,14 @@
     }
     
     private void insertIconView() {
-        LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+        LayoutInflater inflater = getInflater();
         mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
                 this, false);
         addView(mIconView, 0);
     }
     
     private void insertRadioButton() {
-        LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+        LayoutInflater inflater = getInflater();
         mRadioButton =
                 (RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio,
                 this, false);
@@ -227,7 +229,7 @@
     }
     
     private void insertCheckBox() {
-        LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+        LayoutInflater inflater = getInflater();
         mCheckBox =
                 (CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox,
                 this, false);
@@ -242,4 +244,10 @@
         return false;
     }
     
+    private LayoutInflater getInflater() {
+        if (mInflater == null) {
+            mInflater = LayoutInflater.from(mContext);
+        }
+        return mInflater;
+    }
 }
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
new file mode 100644
index 0000000..2cb2a10
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for list-style menus.
+ */
+public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
+    Context mContext;
+    LayoutInflater mInflater;
+    MenuBuilder mMenu;
+
+    ExpandedMenuView mMenuView;
+
+    private int mItemIndexOffset;
+    int mThemeRes;
+    int mItemLayoutRes;
+
+    private Callback mCallback;
+    private MenuAdapter mAdapter;
+
+    public static final String VIEWS_TAG = "android:menu:list";
+
+    /**
+     * Construct a new ListMenuPresenter.
+     * @param context Context to use for theming. This will supersede the context provided
+     *                to initForMenu when this presenter is added.
+     * @param itemLayoutRes Layout resource for individual item views.
+     */
+    public ListMenuPresenter(Context context, int itemLayoutRes) {
+        this(itemLayoutRes, 0);
+        mContext = context;
+    }
+
+    /**
+     * Construct a new ListMenuPresenter.
+     * @param itemLayoutRes Layout resource for individual item views.
+     * @param themeRes Resource ID of a theme to use for views.
+     */
+    public ListMenuPresenter(int itemLayoutRes, int themeRes) {
+        mItemLayoutRes = itemLayoutRes;
+        mThemeRes = themeRes;
+    }
+
+    @Override
+    public void initForMenu(Context context, MenuBuilder menu) {
+        if (mThemeRes != 0) {
+            mContext = new ContextThemeWrapper(context, mThemeRes);
+        } else if (mContext == null) {
+            mContext = context;
+        }
+        mInflater = LayoutInflater.from(mContext);
+        mMenu = menu;
+    }
+
+    @Override
+    public MenuView getMenuView(ViewGroup root) {
+        if (mMenuView == null) {
+            mMenuView = (ExpandedMenuView) mInflater.inflate(
+                    com.android.internal.R.layout.expanded_menu_layout, root, false);
+            if (mAdapter == null) {
+                mAdapter = new MenuAdapter();
+            }
+            mMenuView.setAdapter(mAdapter);
+            mMenuView.setOnItemClickListener(this);
+        }
+        return mMenuView;
+    }
+
+    /**
+     * Call this instead of getMenuView if you want to manage your own ListView.
+     * For proper operation, the ListView hosting this adapter should add
+     * this presenter as an OnItemClickListener.
+     *
+     * @return A ListAdapter containing the items in the menu.
+     */
+    public ListAdapter getAdapter() {
+        if (mAdapter == null) {
+            mAdapter = new MenuAdapter();
+        }
+        return mAdapter;
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        if (mAdapter != null) mAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void setCallback(Callback cb) {
+        mCallback = cb;
+    }
+
+    @Override
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (!subMenu.hasVisibleItems()) return false;
+
+        // The window manager will give us a token.
+        new MenuDialogHelper(subMenu).show(null);
+        if (mCallback != null) {
+            mCallback.onOpenSubMenu(subMenu);
+        }
+        return true;
+    }
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        if (mCallback != null) {
+            mCallback.onCloseMenu(menu, allMenusAreClosing);
+        }
+    }
+
+    int getItemIndexOffset() {
+        return mItemIndexOffset;
+    }
+
+    public void setItemIndexOffset(int offset) {
+        mItemIndexOffset = offset;
+        if (mMenuView != null) {
+            updateMenuView(false);
+        }
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        mMenu.performItemAction(mAdapter.getItem(position), 0);
+    }
+
+    @Override
+    public boolean flagActionItems() {
+        return false;
+    }
+
+    public void saveHierarchyState(Bundle outState) {
+        SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+        if (mMenuView != null) {
+            ((View) mMenuView).saveHierarchyState(viewStates);
+        }
+        outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+    }
+
+    public void restoreHierarchyState(Bundle inState) {
+        SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
+        ((View) mMenuView).restoreHierarchyState(viewStates);
+    }
+
+    private class MenuAdapter extends BaseAdapter {
+        public int getCount() {
+            ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+            return items.size() - mItemIndexOffset;
+        }
+
+        public MenuItemImpl getItem(int position) {
+            ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+            return items.get(position + mItemIndexOffset);
+        }
+
+        public long getItemId(int position) {
+            // Since a menu item's ID is optional, we'll use the position as an
+            // ID for the item in the AdapterView
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = mInflater.inflate(mItemLayoutRes, parent, false);
+            }
+
+            MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+            itemView.initialize(getItem(position), 0);
+            return convertView;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 14d0ac5..b348142 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -25,29 +25,22 @@
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.os.Parcelable;
+import android.util.Log;
 import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TypedValue;
 import android.view.ContextMenu.ContextMenuInfo;
-import android.view.ContextThemeWrapper;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
-import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Vector;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Implementation of the {@link android.view.Menu} interface for creating a
@@ -55,60 +48,6 @@
  */
 public class MenuBuilder implements Menu {
     private static final String LOGTAG = "MenuBuilder";
-    
-    /** The number of different menu types */
-    public static final int NUM_TYPES = 5;
-    /** The menu type that represents the icon menu view */
-    public static final int TYPE_ICON = 0;
-    /** The menu type that represents the expanded menu view */
-    public static final int TYPE_EXPANDED = 1;
-    /**
-     * The menu type that represents a menu dialog. Examples are context and sub
-     * menus. This menu type will not have a corresponding MenuView, but it will
-     * have an ItemView.
-     */
-    public static final int TYPE_DIALOG = 2;
-    /**
-     * The menu type that represents a button in the application's action bar.
-     */
-    public static final int TYPE_ACTION_BUTTON = 3;
-    /**
-     * The menu type that represents a menu popup.
-     */
-    public static final int TYPE_POPUP = 4;
-
-    private static final String VIEWS_TAG = "android:views";
-
-    private static final int THEME_SYSTEM_DEFAULT = 0;
-    private static final int THEME_APPLICATION = -1;
-    private static final int THEME_ALERT_DIALOG = -2;
-
-    // Order must be the same order as the TYPE_*
-    static final int THEME_RES_FOR_TYPE[] = new int[] {
-        com.android.internal.R.style.Theme_IconMenu,
-        com.android.internal.R.style.Theme_ExpandedMenu,
-        THEME_ALERT_DIALOG,
-        THEME_APPLICATION,
-        THEME_APPLICATION,
-    };
-    
-    // Order must be the same order as the TYPE_*
-    static final int LAYOUT_RES_FOR_TYPE[] = new int[] {
-        com.android.internal.R.layout.icon_menu_layout,
-        com.android.internal.R.layout.expanded_menu_layout,
-        0,
-        com.android.internal.R.layout.action_menu_layout,
-        0,
-    };
-
-    // Order must be the same order as the TYPE_*
-    static final int ITEM_LAYOUT_RES_FOR_TYPE[] = new int[] {
-        com.android.internal.R.layout.icon_menu_item_layout,
-        com.android.internal.R.layout.list_menu_item_layout,
-        com.android.internal.R.layout.list_menu_item_layout,
-        com.android.internal.R.layout.action_menu_item_layout,
-        com.android.internal.R.layout.popup_menu_item_layout,
-    };
 
     private static final int[]  sCategoryToOrder = new int[] {
         1, /* No category */
@@ -160,14 +99,7 @@
      * Contains items that should NOT appear in the Action Bar, if present.
      */
     private ArrayList<MenuItemImpl> mNonActionItems;
-    /**
-     * The number of visible action buttons permitted in this menu
-     */
-    private int mMaxActionItems;
-    /**
-     * The total width limit in pixels for all action items within a menu
-     */
-    private int mActionWidthLimit;
+
     /**
      * Whether or not the items (or any one item's action state) has changed since it was
      * last fetched.
@@ -175,12 +107,6 @@
     private boolean mIsActionItemsStale;
 
     /**
-     * Whether the process of granting space as action items should reserve a space for
-     * an overflow option in the action list.
-     */
-    private boolean mReserveActionOverflow;
-
-    /**
      * Default value for how added items should show in the action list.
      */
     private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
@@ -210,100 +136,19 @@
      * that may individually call onItemsChanged.
      */
     private boolean mPreventDispatchingItemsChanged = false;
+    private boolean mItemsChangedWhileDispatchPrevented = false;
     
     private boolean mOptionalIconsVisible = false;
 
-    private ViewGroup mMeasureActionButtonParent;
+    private boolean mIsClosing = false;
 
-    private final WeakReference<MenuAdapter>[] mAdapterCache =
-            new WeakReference[NUM_TYPES];
-    private final WeakReference<OverflowMenuAdapter>[] mOverflowAdapterCache =
-            new WeakReference[NUM_TYPES];
+    private ArrayList<MenuItemImpl> mTempShortcutItemList = new ArrayList<MenuItemImpl>();
 
-    // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
-    private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
-
-    private static int getAlertDialogTheme(Context context) {
-        TypedValue outValue = new TypedValue();
-        context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
-                outValue, true);
-        return outValue.resourceId;
-    }
-
-    private MenuType[] mMenuTypes;
-    class MenuType {
-        private int mMenuType;
-        
-        /** The layout inflater that uses the menu type's theme */
-        private LayoutInflater mInflater;
-
-        /** The lazily loaded {@link MenuView} */
-        private WeakReference<MenuView> mMenuView;
-
-        MenuType(int menuType) {
-            mMenuType = menuType;
-        }
-        
-        LayoutInflater getInflater() {
-            // Create an inflater that uses the given theme for the Views it inflates
-            if (mInflater == null) {
-                Context wrappedContext;
-                int themeResForType = THEME_RES_FOR_TYPE[mMenuType];
-                switch (themeResForType) {
-                    case THEME_APPLICATION:
-                        wrappedContext = mContext;
-                        break;
-                    case THEME_ALERT_DIALOG:
-                        wrappedContext = new ContextThemeWrapper(mContext,
-                                getAlertDialogTheme(mContext));
-                        break;
-                    default:
-                        wrappedContext = new ContextThemeWrapper(mContext, themeResForType);
-                        break;
-                }
-                mInflater = (LayoutInflater) wrappedContext
-                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-            }
-            
-            return mInflater;
-        }
-        
-        MenuView getMenuView(ViewGroup parent) {
-            if (LAYOUT_RES_FOR_TYPE[mMenuType] == 0) {
-                return null;
-            }
-
-            synchronized (this) {
-                MenuView menuView = mMenuView != null ? mMenuView.get() : null;
-                
-                if (menuView == null) {
-                    menuView = (MenuView) getInflater().inflate(
-                            LAYOUT_RES_FOR_TYPE[mMenuType], parent, false);
-                    menuView.initialize(MenuBuilder.this, mMenuType);
-
-                    // Cache the view
-                    mMenuView = new WeakReference<MenuView>(menuView);
-                    
-                    if (mFrozenViewStates != null) {
-                        View view = (View) menuView;
-                        view.restoreHierarchyState(mFrozenViewStates);
-
-                        // Clear this menu type's frozen state, since we just restored it
-                        mFrozenViewStates.remove(view.getId());
-                    }
-                }
-            
-                return menuView;
-            }
-        }
-        
-        boolean hasMenuView() {
-            return mMenuView != null && mMenuView.get() != null;
-        }
-    }
+    private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
+            new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
     
     /**
-     * Called by menu to notify of close and selection changes
+     * Called by menu to notify of close and selection changes.
      */
     public interface Callback {
         /**
@@ -315,30 +160,6 @@
         public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
         
         /**
-         * Called when a menu is closed.
-         * @param menu The menu that was closed.
-         * @param allMenusAreClosing Whether the menus are completely closing (true),
-         *            or whether there is another menu opening shortly
-         *            (false). For example, if the menu is closing because a
-         *            sub menu is about to be shown, <var>allMenusAreClosing</var>
-         *            is false.
-         */
-        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
-        
-        /**
-         * Called when a sub menu is selected.  This is a cue to open the given sub menu's decor.
-         * @param subMenu the sub menu that is being opened
-         * @return whether the sub menu selection was handled by the callback
-         */
-        public boolean onSubMenuSelected(SubMenuBuilder subMenu);
-
-        /**
-         * Called when a sub menu is closed
-         * @param menu the sub menu that was closed
-         */
-        public void onCloseSubMenu(SubMenuBuilder menu);
-        
-        /**
          * Called when the mode of the menu changes (for example, from icon to expanded).
          * 
          * @param menu the menu that has changed modes
@@ -354,8 +175,6 @@
     }
 
     public MenuBuilder(Context context) {
-        mMenuTypes = new MenuType[NUM_TYPES];
-        
         mContext = context;
         mResources = context.getResources();
         
@@ -375,82 +194,66 @@
         mDefaultShowAsAction = defaultShowAsAction;
         return this;
     }
-    
-    public void setCallback(Callback callback) {
-        mCallback = callback;
+
+    /**
+     * Add a presenter to this menu. This will only hold a WeakReference;
+     * you do not need to explicitly remove a presenter, but you can using
+     * {@link #removeMenuPresenter(MenuPresenter)}.
+     *
+     * @param presenter The presenter to add
+     */
+    public void addMenuPresenter(MenuPresenter presenter) {
+        mPresenters.add(new WeakReference<MenuPresenter>(presenter));
+        presenter.initForMenu(mContext, this);
+        mIsActionItemsStale = true;
     }
 
-    MenuType getMenuType(int menuType) {
-        if (mMenuTypes[menuType] == null) {
-            mMenuTypes[menuType] = new MenuType(menuType);
-        }
-        
-        return mMenuTypes[menuType];
-    }
-    
     /**
-     * Gets a menu View that contains this menu's items.
-     * 
-     * @param menuType The type of menu to get a View for (must be one of
-     *            {@link #TYPE_ICON}, {@link #TYPE_EXPANDED},
-     *            {@link #TYPE_DIALOG}).
-     * @param parent The ViewGroup that provides a set of LayoutParams values
-     *            for this menu view
-     * @return A View for the menu of type <var>menuType</var>
+     * Remove a presenter from this menu. That presenter will no longer
+     * receive notifications of updates to this menu's data.
+     *
+     * @param presenter The presenter to remove
      */
-    public View getMenuView(int menuType, ViewGroup parent) {
-        // The expanded menu depends on the number if items shown in the icon menu (which
-        // is adjustable as setters/XML attributes on IconMenuView [imagine a larger LCD
-        // wanting to show more icons]). If, for example, the activity goes through
-        // an orientation change while the expanded menu is open, the icon menu's view
-        // won't have an instance anymore; so here we make sure we have an icon menu view (matching
-        // the same parent so the layout parameters from the XML are used). This
-        // will create the icon menu view and cache it (if it doesn't already exist). 
-        if (menuType == TYPE_EXPANDED
-                && (mMenuTypes[TYPE_ICON] == null || !mMenuTypes[TYPE_ICON].hasMenuView())) {
-            getMenuType(TYPE_ICON).getMenuView(parent);
+    public void removeMenuPresenter(MenuPresenter presenter) {
+        for (WeakReference<MenuPresenter> ref : mPresenters) {
+            final MenuPresenter item = ref.get();
+            if (item == null || item == presenter) {
+                mPresenters.remove(ref);
+            }
         }
-        
-        return (View) getMenuType(menuType).getMenuView(parent);
     }
     
-    private int getNumIconMenuItemsShown() {
-        ViewGroup parent = null;
-        
-        if (!mMenuTypes[TYPE_ICON].hasMenuView()) {
-            /*
-             * There isn't an icon menu view instantiated, so when we get it
-             * below, it will lazily instantiate it. We should pass a proper
-             * parent so it uses the layout_ attributes present in the XML
-             * layout file.
-             */
-            if (mMenuTypes[TYPE_EXPANDED].hasMenuView()) {
-                View expandedMenuView = (View) mMenuTypes[TYPE_EXPANDED].getMenuView(null);
-                parent = (ViewGroup) expandedMenuView.getParent();
+    private void dispatchPresenterUpdate(boolean cleared) {
+        if (mPresenters.isEmpty()) return;
+
+        for (WeakReference<MenuPresenter> ref : mPresenters) {
+            final MenuPresenter presenter = ref.get();
+            if (presenter == null) {
+                mPresenters.remove(ref);
+            } else {
+                presenter.updateMenuView(cleared);
             }
         }
-        
-        return ((IconMenuView) getMenuView(TYPE_ICON, parent)).getNumActualItemsShown(); 
     }
     
-    /**
-     * Clears the cached menu views. Call this if the menu views need to another
-     * layout (for example, if the screen size has changed).
-     */
-    public void clearMenuViews() {
-        for (int i = NUM_TYPES - 1; i >= 0; i--) {
-            if (mMenuTypes[i] != null) {
-                mMenuTypes[i].mMenuView = null;
+    private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
+        if (mPresenters.isEmpty()) return false;
+
+        boolean result = false;
+
+        for (WeakReference<MenuPresenter> ref : mPresenters) {
+            final MenuPresenter presenter = ref.get();
+            if (presenter == null) {
+                mPresenters.remove(ref);
+            } else if (!result) {
+                result = presenter.onSubMenuSelected(subMenu);
             }
         }
-        
-        for (int i = mItems.size() - 1; i >= 0; i--) {
-            MenuItemImpl item = mItems.get(i);
-            if (item.hasSubMenu()) {
-                ((SubMenuBuilder) item.getSubMenu()).clearMenuViews();
-            }
-            item.clearItemViews();
-        }
+        return result;
+    }
+
+    public void setCallback(Callback cb) {
+        mCallback = cb;
     }
     
     /**
@@ -468,7 +271,7 @@
         }
         
         mItems.add(findInsertIndex(mItems, ordering), item);
-        onItemsChanged(false);
+        onItemsChanged(true);
         
         return item;
     }
@@ -554,7 +357,7 @@
             }
             
             // Notify menu views
-            onItemsChanged(false);
+            onItemsChanged(true);
         }
     }
 
@@ -573,7 +376,7 @@
 
         mItems.remove(index);
         
-        if (updateChildrenOnMenuViews) onItemsChanged(false);
+        if (updateChildrenOnMenuViews) onItemsChanged(true);
     }
     
     public void removeItemAt(int index) {
@@ -585,6 +388,7 @@
         clear();
         clearHeader();
         mPreventDispatchingItemsChanged = false;
+        mItemsChangedWhileDispatchPrevented = false;
         onItemsChanged(true);
     }
     
@@ -725,19 +529,14 @@
         return mItems.get(index);
     }
 
-    public MenuItem getOverflowItem(int index) {
-        flagActionItems(true);
-        return mNonActionItems.get(index);
-    }
-
     public boolean isShortcutKey(int keyCode, KeyEvent event) {
         return findItemWithShortcutForKey(keyCode, event) != null;
     }
 
     public void setQwertyMode(boolean isQwerty) {
         mQwertyMode = isQwerty;
-        
-        refreshShortcuts(isShortcutsVisible(), isQwerty);
+
+        onItemsChanged(false);
     }
 
     /**
@@ -751,8 +550,7 @@
      * @return An ordering integer that can be used to order this item across
      *         all the items (even from other categories).
      */
-    private static int getOrdering(int categoryOrder)
-    {
+    private static int getOrdering(int categoryOrder) {
         final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
         
         if (index < 0 || index >= sCategoryToOrder.length) {
@@ -770,23 +568,6 @@
     }
 
     /**
-     * Refreshes the shortcut labels on each of the displayed items.  Passes the arguments
-     * so submenus don't need to call their parent menu for the same values.
-     */
-    private void refreshShortcuts(boolean shortcutsVisible, boolean qwertyMode) {
-        MenuItemImpl item;
-        for (int i = mItems.size() - 1; i >= 0; i--) {
-            item = mItems.get(i);
-            
-            if (item.hasSubMenu()) {
-                ((MenuBuilder) item.getSubMenu()).refreshShortcuts(shortcutsVisible, qwertyMode);
-            }
-            
-            item.refreshShortcutOnItemViews(shortcutsVisible, qwertyMode);
-        }
-    }
-
-    /**
      * Sets whether the shortcuts should be visible on menus.  Devices without hardware
      * key input will never make shortcuts visible even if this method is passed 'true'.
      * 
@@ -798,7 +579,7 @@
         if (mShortcutsVisible == shortcutsVisible) return;
 
         setShortcutsVisibleInner(shortcutsVisible);
-        refreshShortcuts(mShortcutsVisible, isQwertyMode());
+        onItemsChanged(false);
     }
 
     private void setShortcutsVisibleInner(boolean shortcutsVisible) {
@@ -818,15 +599,24 @@
     Resources getResources() {
         return mResources;
     }
-
-    public Callback getCallback() {
-        return mCallback;
-    }
     
     public Context getContext() {
         return mContext;
     }
     
+    boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+        return mCallback != null && mCallback.onMenuItemSelected(menu, item);
+    }
+
+    /**
+     * Dispatch a mode change event to this menu's callback.
+     */
+    public void changeMenuMode() {
+        if (mCallback != null) {
+            mCallback.onMenuModeChange(this);
+        }
+    }
+
     private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
         for (int i = items.size() - 1; i >= 0; i--) {
             MenuItemImpl item = items.get(i);
@@ -860,7 +650,7 @@
      * (the ALT-enabled char corresponds to the shortcut) associated
      * with the keyCode.
      */
-    List<MenuItemImpl> findItemsWithShortcutForKey(int keyCode, KeyEvent event) {
+    void findItemsWithShortcutForKey(List<MenuItemImpl> items, int keyCode, KeyEvent event) {
         final boolean qwerty = isQwertyMode();
         final int metaState = event.getMetaState();
         final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
@@ -868,18 +658,15 @@
         final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
         // The delete key is not mapped to '\b' so we treat it specially
         if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
-            return null;
+            return;
         }
 
-        Vector<MenuItemImpl> items = new Vector();
         // Look for an item whose shortcut is this key.
         final int N = mItems.size();
         for (int i = 0; i < N; i++) {
             MenuItemImpl item = mItems.get(i);
             if (item.hasSubMenu()) {
-                List<MenuItemImpl> subMenuItems = ((MenuBuilder)item.getSubMenu())
-                    .findItemsWithShortcutForKey(keyCode, event);
-                items.addAll(subMenuItems);
+                ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
             }
             final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
             if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
@@ -892,7 +679,6 @@
                 items.add(item);
             }
         }
-        return items;
     }
 
     /*
@@ -908,9 +694,11 @@
      */
     MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
         // Get all items that can be associated directly or indirectly with the keyCode
-        List<MenuItemImpl> items = findItemsWithShortcutForKey(keyCode, event);
+        ArrayList<MenuItemImpl> items = mTempShortcutItemList;
+        items.clear();
+        findItemsWithShortcutForKey(items, keyCode, event);
 
-        if (items == null) {
+        if (items.isEmpty()) {
             return null;
         }
 
@@ -920,15 +708,18 @@
         event.getKeyData(possibleChars);
 
         // If we have only one element, we can safely returns it
-        if (items.size() == 1) {
+        final int size = items.size();
+        if (size == 1) {
             return items.get(0);
         }
 
         final boolean qwerty = isQwertyMode();
         // If we found more than one item associated with the key,
         // we have to return the exact match
-        for (MenuItemImpl item : items) {
-            final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
+        for (int i = 0; i < size; i++) {
+            final MenuItemImpl item = items.get(i);
+            final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
+                    item.getNumericShortcut();
             if ((shortcutChar == possibleChars.meta[0] &&
                     (metaState & KeyEvent.META_ALT_ON) == 0)
                 || (shortcutChar == possibleChars.meta[2] &&
@@ -958,11 +749,8 @@
         if (item.hasSubMenu()) {
             close(false);
 
-            if (mCallback != null) {
-                // Return true if the sub menu was invoked or the item was invoked previously
-                invoked = mCallback.onSubMenuSelected((SubMenuBuilder) item.getSubMenu())
-                        || invoked;
-            }
+            invoked |= dispatchSubMenuSelected((SubMenuBuilder) item.getSubMenu());
+            if (!invoked) close(true);
         } else {
             if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
                 close(true);
@@ -982,10 +770,18 @@
      *            is false.
      */
     final void close(boolean allMenusAreClosing) {
-        Callback callback = getCallback();
-        if (callback != null) {
-            callback.onCloseMenu(this, allMenusAreClosing);
+        if (mIsClosing) return;
+
+        mIsClosing = true;
+        for (WeakReference<MenuPresenter> ref : mPresenters) {
+            final MenuPresenter presenter = ref.get();
+            if (presenter == null) {
+                mPresenters.remove(ref);
+            } else {
+                presenter.onCloseMenu(this, allMenusAreClosing);
+            }
         }
+        mIsClosing = false;
     }
 
     /** {@inheritDoc} */
@@ -996,26 +792,38 @@
     /**
      * Called when an item is added or removed.
      * 
-     * @param cleared Whether the items were cleared or just changed.
+     * @param structureChanged true if the menu structure changed,
+     *                         false if only item properties changed.
      */
-    private void onItemsChanged(boolean cleared) {
+    void onItemsChanged(boolean structureChanged) {
         if (!mPreventDispatchingItemsChanged) {
-            if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
-            if (mIsActionItemsStale == false) mIsActionItemsStale = true;
-            
-            MenuType[] menuTypes = mMenuTypes;
-            for (int i = 0; i < NUM_TYPES; i++) {
-                if ((menuTypes[i] != null) && (menuTypes[i].hasMenuView())) {
-                    MenuView menuView = menuTypes[i].mMenuView.get();
-                    menuView.updateChildren(cleared);
-                }
-
-                MenuAdapter adapter = mAdapterCache[i] == null ? null : mAdapterCache[i].get();
-                if (adapter != null) adapter.notifyDataSetChanged();
-
-                adapter = mOverflowAdapterCache[i] == null ? null : mOverflowAdapterCache[i].get();
-                if (adapter != null) adapter.notifyDataSetChanged();
+            if (structureChanged) {
+                mIsVisibleItemsStale = true;
+                mIsActionItemsStale = true;
             }
+
+            dispatchPresenterUpdate(structureChanged);
+        } else {
+            mItemsChangedWhileDispatchPrevented = true;
+        }
+    }
+
+    /**
+     * Stop dispatching item changed events to presenters until
+     * {@link #startDispatchingItemsChanged()} is called. Useful when
+     * many menu operations are going to be performed as a batch.
+     */
+    public void stopDispatchingItemsChanged() {
+        mPreventDispatchingItemsChanged = true;
+        mItemsChangedWhileDispatchPrevented = false;
+    }
+
+    public void startDispatchingItemsChanged() {
+        mPreventDispatchingItemsChanged = false;
+
+        if (mItemsChangedWhileDispatchPrevented) {
+            mItemsChangedWhileDispatchPrevented = false;
+            onItemsChanged(true);
         }
     }
 
@@ -1025,6 +833,7 @@
      */
     void onItemVisibleChanged(MenuItemImpl item) {
         // Notify of items being changed
+        mIsVisibleItemsStale = true;
         onItemsChanged(false);
     }
     
@@ -1034,6 +843,7 @@
      */
     void onItemActionRequestChanged(MenuItemImpl item) {
         // Notify of items being changed
+        mIsActionItemsStale = true;
         onItemsChanged(false);
     }
     
@@ -1055,17 +865,6 @@
         
         return mVisibleItems;
     }
-    
-    /**
-     * @return A fake action button parent view for obtaining child views.
-     */
-    private ViewGroup getMeasureActionButtonParent() {
-        if (mMeasureActionButtonParent == null) {
-            mMeasureActionButtonParent = (ViewGroup) getMenuType(TYPE_ACTION_BUTTON).getInflater()
-                    .inflate(LAYOUT_RES_FOR_TYPE[TYPE_ACTION_BUTTON], null, false);
-        }
-        return mMeasureActionButtonParent;
-    }
 
     /**
      * This method determines which menu items get to be 'action items' that will appear
@@ -1090,147 +889,56 @@
      * <p>The space freed by demoting a full group cannot be consumed by future menu items.
      * Once items begin to overflow, all future items become overflow items as well. This is
      * to avoid inadvertent reordering that may break the app's intended design.
-     *
-     * @param reserveActionOverflow true if an overflow button should consume one space
-     *                              in the available item count
      */
-    private void flagActionItems(boolean reserveActionOverflow) {
-        if (reserveActionOverflow != mReserveActionOverflow) {
-            mReserveActionOverflow = reserveActionOverflow;
-            mIsActionItemsStale = true;
-        }
-
+    public void flagActionItems() {
         if (!mIsActionItemsStale) {
             return;
         }
 
-        final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
-        final int itemsSize = visibleItems.size();
-        int maxActions = mMaxActionItems;
-        int widthLimit = mActionWidthLimit;
-        final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
-        final ViewGroup parent = getMeasureActionButtonParent();
-
-        int requiredItems = 0;
-        int requestedItems = 0;
-        int firstActionWidth = 0;
-        boolean hasOverflow = false;
-        for (int i = 0; i < itemsSize; i++) {
-            MenuItemImpl item = visibleItems.get(i);
-            if (item.requiresActionButton()) {
-                requiredItems++;
-            } else if (item.requestsActionButton()) {
-                requestedItems++;
+        // Presenters flag action items as needed.
+        boolean flagged = false;
+        for (WeakReference<MenuPresenter> ref : mPresenters) {
+            final MenuPresenter presenter = ref.get();
+            if (presenter == null) {
+                mPresenters.remove(ref);
             } else {
-                hasOverflow = true;
+                flagged |= presenter.flagActionItems();
             }
         }
 
-        // Reserve a spot for the overflow item if needed.
-        if (reserveActionOverflow &&
-                (hasOverflow || requiredItems + requestedItems > maxActions)) {
-            maxActions--;
-        }
-        maxActions -= requiredItems;
-
-        final SparseBooleanArray seenGroups = mActionButtonGroups;
-        seenGroups.clear();
-
-        // Flag as many more requested items as will fit.
-        for (int i = 0; i < itemsSize; i++) {
-            MenuItemImpl item = visibleItems.get(i);
-
-            if (item.requiresActionButton()) {
-                View v = item.getActionView();
-                if (v == null) {
-                    v = item.getItemView(TYPE_ACTION_BUTTON, parent);
+        if (flagged) {
+            mActionItems.clear();
+            mNonActionItems.clear();
+            ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
+            final int itemsSize = visibleItems.size();
+            for (int i = 0; i < itemsSize; i++) {
+                MenuItemImpl item = visibleItems.get(i);
+                if (item.isActionButton()) {
+                    mActionItems.add(item);
+                } else {
+                    mNonActionItems.add(item);
                 }
-                v.measure(querySpec, querySpec);
-                final int measuredWidth = v.getMeasuredWidth();
-                widthLimit -= measuredWidth;
-                if (firstActionWidth == 0) {
-                    firstActionWidth = measuredWidth;
-                }
-                final int groupId = item.getGroupId();
-                if (groupId != 0) {
-                    seenGroups.put(groupId, true);
-                }
-            } else if (item.requestsActionButton()) {
-                // Items in a group with other items that already have an action slot
-                // can break the max actions rule, but not the width limit.
-                final int groupId = item.getGroupId();
-                final boolean inGroup = seenGroups.get(groupId);
-                boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
-                maxActions--;
-
-                if (isAction) {
-                    View v = item.getActionView();
-                    if (v == null) {
-                        v = item.getItemView(TYPE_ACTION_BUTTON, parent);
-                    }
-                    v.measure(querySpec, querySpec);
-                    final int measuredWidth = v.getMeasuredWidth();
-                    widthLimit -= measuredWidth;
-                    if (firstActionWidth == 0) {
-                        firstActionWidth = measuredWidth;
-                    }
-
-                    // Did this push the entire first item past halfway?
-                    if (widthLimit + firstActionWidth <= 0) {
-                        isAction = false;
-                    }
-                }
-
-                if (isAction && groupId != 0) {
-                    seenGroups.put(groupId, true);
-                } else if (inGroup) {
-                    // We broke the width limit. Demote the whole group, they all overflow now.
-                    seenGroups.put(groupId, false);
-                    for (int j = 0; j < i; j++) {
-                        MenuItemImpl areYouMyGroupie = visibleItems.get(j);
-                        if (areYouMyGroupie.getGroupId() == groupId) {
-                            areYouMyGroupie.setIsActionButton(false);
-                        }
-                    }
-                }
-
-                item.setIsActionButton(isAction);
             }
+        } else if (mActionItems.size() + mNonActionItems.size() != getVisibleItems().size()) {
+            // Nobody flagged anything, but if something doesn't add up then treat everything
+            // as non-action items.
+            // (This happens during a first pass with no action-item presenters.)
+            mActionItems.clear();
+            mNonActionItems.clear();
+            mNonActionItems.addAll(getVisibleItems());
         }
-
-        mActionItems.clear();
-        mNonActionItems.clear();
-        for (int i = 0; i < itemsSize; i++) {
-            MenuItemImpl item = visibleItems.get(i);
-            if (item.isActionButton()) {
-                mActionItems.add(item);
-            } else {
-                mNonActionItems.add(item);
-            }
-        }
-
         mIsActionItemsStale = false;
     }
     
-    ArrayList<MenuItemImpl> getActionItems(boolean reserveActionOverflow) {
-        flagActionItems(reserveActionOverflow);
+    ArrayList<MenuItemImpl> getActionItems() {
+        flagActionItems();
         return mActionItems;
     }
     
-    ArrayList<MenuItemImpl> getNonActionItems(boolean reserveActionOverflow) {
-        flagActionItems(reserveActionOverflow);
+    ArrayList<MenuItemImpl> getNonActionItems() {
+        flagActionItems();
         return mNonActionItems;
     }
-    
-    void setMaxActionItems(int maxActionItems) {
-        mMaxActionItems = maxActionItems;
-        mIsActionItemsStale = true;
-    }
-
-    void setActionWidthLimit(int widthLimit) {
-        mActionWidthLimit = widthLimit;
-        mIsActionItemsStale = true;
-    }
 
     public void clearHeader() {
         mHeaderIcon = null;
@@ -1362,38 +1070,6 @@
         mCurrentMenuInfo = menuInfo;
     }
 
-    /**
-     * Gets an adapter for providing items and their views.
-     * 
-     * @param menuType The type of menu to get an adapter for.
-     * @return A {@link MenuAdapter} for this menu with the given menu type.
-     */
-    public MenuAdapter getMenuAdapter(int menuType) {
-        MenuAdapter adapter = mAdapterCache[menuType] == null ?
-                null : mAdapterCache[menuType].get();
-        if (adapter != null) return adapter;
-
-        adapter = new MenuAdapter(menuType);
-        mAdapterCache[menuType] = new WeakReference<MenuAdapter>(adapter);
-        return adapter;
-    }
-
-    /**
-     * Gets an adapter for providing overflow (non-action) items and their views.
-     *
-     * @param menuType The type of menu to get an adapter for.
-     * @return A {@link MenuAdapter} for this menu with the given menu type.
-     */
-    public MenuAdapter getOverflowMenuAdapter(int menuType) {
-        OverflowMenuAdapter adapter = mOverflowAdapterCache[menuType] == null ?
-                null : mOverflowAdapterCache[menuType].get();
-        if (adapter != null) return adapter;
-
-        adapter = new OverflowMenuAdapter(menuType);
-        mOverflowAdapterCache[menuType] = new WeakReference<OverflowMenuAdapter>(adapter);
-        return adapter;
-    }
-
     void setOptionalIconsVisible(boolean visible) {
         mOptionalIconsVisible = visible;
     }
@@ -1401,109 +1077,4 @@
     boolean getOptionalIconsVisible() {
         return mOptionalIconsVisible;
     }
-
-    public void saveHierarchyState(Bundle outState) {
-        SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
-        
-        MenuType[] menuTypes = mMenuTypes;
-        for (int i = NUM_TYPES - 1; i >= 0; i--) {
-            if (menuTypes[i] == null) {
-                continue;
-            }
-
-            if (menuTypes[i].hasMenuView()) {
-                ((View) menuTypes[i].getMenuView(null)).saveHierarchyState(viewStates);
-            }
-        }
-        
-        outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
-    }
-
-    public void restoreHierarchyState(Bundle inState) {
-        // Save this for menu views opened later
-        SparseArray<Parcelable> viewStates = mFrozenViewStates = inState
-                .getSparseParcelableArray(VIEWS_TAG);
-        
-        // Thaw those menu views already open
-        MenuType[] menuTypes = mMenuTypes;
-        for (int i = NUM_TYPES - 1; i >= 0; i--) {
-            if (menuTypes[i] == null) {
-                continue;
-            }
-            
-            if (menuTypes[i].hasMenuView()) {
-                ((View) menuTypes[i].getMenuView(null)).restoreHierarchyState(viewStates);
-            }
-        }
-    }
-    
-    /**
-     * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
-     * source.  This adapter will use only the visible/shown items from the menu.
-     */
-    public class MenuAdapter extends BaseAdapter {
-        private int mMenuType;
-        
-        public MenuAdapter(int menuType) {
-            mMenuType = menuType;
-        }
-
-        public int getOffset() {
-            if (mMenuType == TYPE_EXPANDED) {
-                return getNumIconMenuItemsShown(); 
-            } else {
-                return 0;
-            }
-        }
-        
-        public int getCount() {
-            return getVisibleItems().size() - getOffset();
-        }
-
-        public MenuItemImpl getItem(int position) {
-            return getVisibleItems().get(position + getOffset());
-        }
-
-        public long getItemId(int position) {
-            // Since a menu item's ID is optional, we'll use the position as an
-            // ID for the item in the AdapterView
-            return position;
-        }
-
-        public View getView(int position, View convertView, ViewGroup parent) {
-            if (convertView != null) {
-                MenuView.ItemView itemView = (MenuView.ItemView) convertView;
-                itemView.getItemData().setItemView(mMenuType, null);
-
-                MenuItemImpl item = (MenuItemImpl) getItem(position);
-                itemView.initialize(item, mMenuType);
-                item.setItemView(mMenuType, itemView);
-                return convertView;
-            } else {
-                MenuItemImpl item = (MenuItemImpl) getItem(position);
-                item.setItemView(mMenuType, null);
-                return item.getItemView(mMenuType, parent);
-            }
-        }
-    }
-
-    /**
-     * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
-     * source for overflow menu items that do not fit in the list of action items.
-     */
-    private class OverflowMenuAdapter extends MenuAdapter {
-        public OverflowMenuAdapter(int menuType) {
-            super(menuType);
-        }
-
-        @Override
-        public MenuItemImpl getItem(int position) {
-            return getNonActionItems(true).get(position);
-        }
-
-        @Override
-        public int getCount() {
-            return getNonActionItems(true).size();
-        }
-    }
 }
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index d7438d6..6387c9b 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -24,17 +24,19 @@
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
-import android.widget.ListAdapter;
 
 /**
  * Helper for menus that appear as Dialogs (context and submenus).
  * 
  * @hide
  */
-public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogInterface.OnClickListener {
+public class MenuDialogHelper implements DialogInterface.OnKeyListener,
+        DialogInterface.OnClickListener,
+        DialogInterface.OnDismissListener,
+        MenuPresenter.Callback {
     private MenuBuilder mMenu;
-    private ListAdapter mAdapter;
     private AlertDialog mDialog;
+    ListMenuPresenter mPresenter;
     
     public MenuDialogHelper(MenuBuilder menu) {
         mMenu = menu;
@@ -49,12 +51,15 @@
         // Many references to mMenu, create local reference
         final MenuBuilder menu = mMenu;
         
-        // Get an adapter for the menu item views
-        mAdapter = menu.getMenuAdapter(MenuBuilder.TYPE_DIALOG);
-        
         // Get the builder for the dialog
-        final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext())
-                .setAdapter(mAdapter, this); 
+        final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext());
+
+        mPresenter = new ListMenuPresenter(builder.getContext(),
+                com.android.internal.R.layout.list_menu_item_layout);
+
+        mPresenter.setCallback(this);
+        mMenu.addMenuPresenter(mPresenter);
+        builder.setAdapter(mPresenter.getAdapter(), this);
 
         // Set the title
         final View headerView = menu.getHeaderView();
@@ -68,13 +73,10 @@
         
         // Set the key listener
         builder.setOnKeyListener(this);
-
-        // Since this is for a menu, disable the recycling of views
-        // This is done by the menu framework anyway
-        builder.setRecycleOnMeasureEnabled(false);
         
         // Show the menu
         mDialog = builder.create();
+        mDialog.setOnDismissListener(this);
         
         WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
         lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -132,9 +134,25 @@
             mDialog.dismiss();
         }
     }
-    
-    public void onClick(DialogInterface dialog, int which) {
-        mMenu.performItemAction((MenuItemImpl) mAdapter.getItem(which), 0);
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        mPresenter.onCloseMenu(mMenu, true);
     }
-    
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        if (allMenusAreClosing || menu == mMenu) {
+            dismiss();
+        }
+    }
+
+    @Override
+    public boolean onOpenSubMenu(MenuBuilder subMenu) {
+        return false;
+    }
+
+    public void onClick(DialogInterface dialog, int which) {
+        mMenu.performItemAction((MenuItemImpl) mPresenter.getAdapter().getItem(which), 0);
+    }
 }
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 305115f..42ef916 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -16,21 +16,18 @@
 
 package com.android.internal.view.menu;
 
-import java.lang.ref.WeakReference;
+import com.android.internal.view.menu.MenuView.ItemView;
 
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ContextMenu.ContextMenuInfo;
-
-import com.android.internal.view.menu.MenuView.ItemView;
 
 /**
  * @hide
@@ -60,9 +57,6 @@
      * needed).
      */ 
     private int mIconResId = NO_ICON;
-
-    /** The (cached) menu item views for this item */  
-    private WeakReference<ItemView> mItemViews[];
     
     /** The menu to which this item belongs */
     private MenuBuilder mMenu;
@@ -128,7 +122,6 @@
                     com.android.internal.R.string.menu_space_shortcut_label);
         }
         
-        mItemViews = new WeakReference[MenuBuilder.NUM_TYPES];
         mMenu = menu;
         mId = id;
         mGroup = group;
@@ -149,9 +142,7 @@
             return true;
         }
 
-        MenuBuilder.Callback callback = mMenu.getCallback(); 
-        if (callback != null &&
-            callback.onMenuItemSelected(mMenu.getRootMenu(), this)) {
+        if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
             return true;
         }
 
@@ -172,10 +163,6 @@
         return false;
     }
     
-    private boolean hasItemView(int menuType) {
-        return mItemViews[menuType] != null && mItemViews[menuType].get() != null;
-    }
-    
     public boolean isEnabled() {
         return (mFlags & ENABLED) != 0;
     }
@@ -187,13 +174,7 @@
             mFlags &= ~ENABLED;
         }
 
-        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
-            // If the item view prefers a condensed title, only set this title if there
-            // is no condensed title for this item
-            if (hasItemView(i)) {
-                mItemViews[i].get().setEnabled(enabled);
-            }
-        }
+        mMenu.onItemsChanged(false);
         
         return this;
     }
@@ -242,7 +223,7 @@
         
         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
         
-        refreshShortcutOnItemViews();
+        mMenu.onItemsChanged(false);
         
         return this;
     }
@@ -256,7 +237,7 @@
         
         mShortcutNumericChar = numericChar;
         
-        refreshShortcutOnItemViews();
+        mMenu.onItemsChanged(false);
         
         return this;
     }
@@ -265,7 +246,7 @@
         mShortcutNumericChar = numericChar;
         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
         
-        refreshShortcutOnItemViews();
+        mMenu.onItemsChanged(false);
         
         return this;
     }
@@ -322,38 +303,6 @@
         return mMenu.isShortcutsVisible() && (getShortcut() != 0);
     }
     
-    /**
-     * Refreshes the shortcut shown on the ItemViews.  This method retrieves current
-     * shortcut state (mode and shown) from the menu that contains this item.
-     */
-    private void refreshShortcutOnItemViews() {
-        refreshShortcutOnItemViews(mMenu.isShortcutsVisible(), mMenu.isQwertyMode());
-    }
-
-    /**
-     * Refreshes the shortcut shown on the ItemViews. This is usually called by
-     * the {@link MenuBuilder} when it is refreshing the shortcuts on all item
-     * views, so it passes arguments rather than each item calling a method on the menu to get
-     * the same values.
-     * 
-     * @param menuShortcutShown The menu's shortcut shown mode. In addition,
-     *            this method will ensure this item has a shortcut before it
-     *            displays the shortcut.
-     * @param isQwertyMode Whether the shortcut mode is qwerty mode
-     */
-    void refreshShortcutOnItemViews(boolean menuShortcutShown, boolean isQwertyMode) {
-        final char shortcutKey = (isQwertyMode) ? mShortcutAlphabeticChar : mShortcutNumericChar;
-
-        // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
-        final boolean showShortcut = menuShortcutShown && (shortcutKey != 0);
-        
-        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
-            if (hasItemView(i)) {
-                mItemViews[i].get().setShortcut(showShortcut, shortcutKey);
-            }
-        }
-    }
-    
     public SubMenu getSubMenu() {
         return mSubMenu;
     }
@@ -394,18 +343,7 @@
     public MenuItem setTitle(CharSequence title) {
         mTitle = title;
 
-        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
-            // If the item view prefers a condensed title, only set this title if there
-            // is no condensed title for this item
-            if (!hasItemView(i)) {
-                continue;
-            }
-            
-            ItemView itemView = mItemViews[i].get(); 
-            if (!itemView.prefersCondensedTitle() || mTitleCondensed == null) {
-                itemView.setTitle(title);
-            }
-        }
+        mMenu.onItemsChanged(false);
         
         if (mSubMenu != null) {
             mSubMenu.setHeaderTitle(title);
@@ -430,18 +368,12 @@
             title = mTitle;
         }
         
-        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
-            // Refresh those item views that prefer a condensed title
-            if (hasItemView(i) && (mItemViews[i].get().prefersCondensedTitle())) {
-                mItemViews[i].get().setTitle(title);
-            }
-        }
+        mMenu.onItemsChanged(false);
         
         return this;
     }
 
     public Drawable getIcon() {
-        
         if (mIconDrawable != null) {
             return mIconDrawable;
         }
@@ -456,7 +388,7 @@
     public MenuItem setIcon(Drawable icon) {
         mIconResId = NO_ICON;
         mIconDrawable = icon;
-        setIconOnViews(icon);
+        mMenu.onItemsChanged(false);
         
         return this;
     }
@@ -466,33 +398,10 @@
         mIconResId = iconResId;
 
         // If we have a view, we need to push the Drawable to them
-        if (haveAnyOpenedIconCapableItemViews()) {
-            Drawable drawable = iconResId != NO_ICON ? mMenu.getResources().getDrawable(iconResId)
-                    : null;
-            setIconOnViews(drawable);
-        }
+        mMenu.onItemsChanged(false);
         
         return this;
     }
-
-    private void setIconOnViews(Drawable icon) {
-        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
-            // Refresh those item views that are able to display an icon
-            if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
-                mItemViews[i].get().setIcon(icon);
-            }
-        }
-    }
-    
-    private boolean haveAnyOpenedIconCapableItemViews() {
-        for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
-            if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
-                return true;
-            }
-        }
-        
-        return false;
-    }
     
     public boolean isCheckable() {
         return (mFlags & CHECKABLE) == CHECKABLE;
@@ -502,19 +411,14 @@
         final int oldFlags = mFlags;
         mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
         if (oldFlags != mFlags) {
-            for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
-                if (hasItemView(i)) {
-                    mItemViews[i].get().setCheckable(checkable);
-                }
-            }
+            mMenu.onItemsChanged(false);
         }
         
         return this;
     }
 
-    public void setExclusiveCheckable(boolean exclusive)
-    {
-        mFlags = (mFlags&~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+    public void setExclusiveCheckable(boolean exclusive) {
+        mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
     }
 
     public boolean isExclusiveCheckable() {
@@ -541,11 +445,7 @@
         final int oldFlags = mFlags;
         mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
         if (oldFlags != mFlags) {
-            for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
-                if (hasItemView(i)) {
-                    mItemViews[i].get().setChecked(checked);
-                }
-            }
+            mMenu.onItemsChanged(false);
         }
     }
     
@@ -581,39 +481,6 @@
         mClickListener = clickListener;
         return this;
     }
-
-    View getItemView(int menuType, ViewGroup parent) {
-        if (!hasItemView(menuType)) {
-            mItemViews[menuType] = new WeakReference<ItemView>(createItemView(menuType, parent));
-        }
-        
-        return (View) mItemViews[menuType].get();
-    }
-
-    void setItemView(int menuType, ItemView view) {
-        mItemViews[menuType] = new WeakReference<ItemView>(view);
-    }
-
-    /**
-     * Create and initializes a menu item view that implements {@link MenuView.ItemView}.
-     * @param menuType The type of menu to get a View for (must be one of
-     *            {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
-     *            {@link MenuBuilder#TYPE_SUB}, {@link MenuBuilder#TYPE_CONTEXT}).
-     * @return The inflated {@link MenuView.ItemView} that is ready for use
-     */
-    private MenuView.ItemView createItemView(int menuType, ViewGroup parent) {
-        // Create the MenuView
-        MenuView.ItemView itemView = (MenuView.ItemView) getLayoutInflater(menuType)
-                .inflate(MenuBuilder.ITEM_LAYOUT_RES_FOR_TYPE[menuType], parent, false);
-        itemView.initialize(this, menuType);
-        return itemView;
-    }
-    
-    void clearItemViews() {
-        for (int i = mItemViews.length - 1; i >= 0; i--) {
-            mItemViews[i] = null;
-        }
-    }
     
     @Override
     public String toString() {
@@ -627,24 +494,12 @@
     public ContextMenuInfo getMenuInfo() {
         return mMenuInfo;
     }
-    
-    /**
-     * Returns a LayoutInflater that is themed for the given menu type.
-     * 
-     * @param menuType The type of menu.
-     * @return A LayoutInflater.
-     */
-    public LayoutInflater getLayoutInflater(int menuType) {
-        return mMenu.getMenuType(menuType).getInflater();
-    }
 
     /**
-     * @return Whether the given menu type should show icons for menu items.
+     * @return Whether the menu should show icons for menu items.
      */
-    public boolean shouldShowIcon(int menuType) {
-        return menuType == MenuBuilder.TYPE_ICON ||
-                menuType == MenuBuilder.TYPE_ACTION_BUTTON ||
-                mMenu.getOptionalIconsVisible();
+    public boolean shouldShowIcon() {
+        return mMenu.getOptionalIconsVisible();
     }
     
     public boolean isActionButton() {
@@ -696,8 +551,8 @@
 
     public MenuItem setActionView(int resId) {
         LayoutInflater inflater = LayoutInflater.from(mMenu.getContext());
-        ViewGroup parent = (ViewGroup) mMenu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, null);
-        setActionView(inflater.inflate(resId, parent, false));
+        // TODO - Fix for proper parent. Lazily inflate in the presenter.
+        setActionView(inflater.inflate(resId, null));
         return this;
     }
 
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 04a059e..38cec29 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -16,31 +16,35 @@
 
 package com.android.internal.view.menu;
 
-import com.android.internal.view.menu.MenuBuilder.MenuAdapter;
-
 import android.content.Context;
-import android.os.Handler;
 import android.util.DisplayMetrics;
 import android.view.KeyEvent;
-import android.view.MenuItem;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
 import android.widget.ListPopupWindow;
 import android.widget.PopupWindow;
 
-import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 
 /**
+ * Presents a menu as a small, simple popup anchored to another view.
  * @hide
  */
 public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
         ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
-        View.OnAttachStateChangeListener {
+        View.OnAttachStateChangeListener, MenuPresenter {
     private static final String TAG = "MenuPopupHelper";
 
+    static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
+
     private Context mContext;
+    private LayoutInflater mInflater;
     private ListPopupWindow mPopup;
     private MenuBuilder mMenu;
     private int mPopupMaxWidth;
@@ -48,7 +52,9 @@
     private boolean mOverflowOnly;
     private ViewTreeObserver mTreeObserver;
 
-    private final Handler mHandler = new Handler();
+    private MenuAdapter mAdapter;
+
+    private Callback mPresenterCallback;
 
     public MenuPopupHelper(Context context, MenuBuilder menu) {
         this(context, menu, null, false);
@@ -61,6 +67,7 @@
     public MenuPopupHelper(Context context, MenuBuilder menu,
             View anchorView, boolean overflowOnly) {
         mContext = context;
+        mInflater = LayoutInflater.from(context);
         mMenu = menu;
         mOverflowOnly = overflowOnly;
 
@@ -68,6 +75,8 @@
         mPopupMaxWidth = metrics.widthPixels / 2;
 
         mAnchorView = anchorView;
+
+        menu.addMenuPresenter(this);
     }
 
     public void setAnchorView(View anchor) {
@@ -82,23 +91,14 @@
 
     public boolean tryShow() {
         mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
-        mPopup.setOnItemClickListener(this);
         mPopup.setOnDismissListener(this);
+        mPopup.setOnItemClickListener(this);
 
-        final MenuAdapter adapter = mOverflowOnly ?
-                mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) :
-                mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP);
-        mPopup.setAdapter(adapter);
+        mAdapter = new MenuAdapter(mMenu);
+        mPopup.setAdapter(mAdapter);
         mPopup.setModal(true);
 
         View anchor = mAnchorView;
-        if (anchor == null && mMenu instanceof SubMenuBuilder) {
-            SubMenuBuilder subMenu = (SubMenuBuilder) mMenu;
-            final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem();
-            anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null);
-            mAnchorView = anchor;
-        }
-
         if (anchor != null) {
             final boolean addGlobalListener = mTreeObserver == null;
             mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
@@ -109,7 +109,7 @@
             return false;
         }
 
-        mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth));
+        mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth));
         mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
         mPopup.show();
         mPopup.getListView().setOnKeyListener(this);
@@ -136,23 +136,10 @@
         return mPopup != null && mPopup.isShowing();
     }
 
+    @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-        if (!isShowing()) return;
-
-        MenuItem item = null;
-        if (mOverflowOnly) {
-            item = mMenu.getOverflowItem(position);
-        } else {
-            item = mMenu.getVisibleItems().get(position);
-        }
-        dismiss();
-
-        final MenuItem performItem = item;
-        mHandler.post(new Runnable() {
-            public void run() {
-                mMenu.performItemAction(performItem, 0);
-            }
-        });
+        MenuAdapter adapter = mAdapter;
+        adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
     }
 
     public boolean onKey(View v, int keyCode, KeyEvent event) {
@@ -163,7 +150,7 @@
         return false;
     }
 
-    private int measureContentWidth(MenuAdapter adapter) {
+    private int measureContentWidth(ListAdapter adapter) {
         // Menus don't tend to be long, so this is more sane than it looks.
         int width = 0;
         View itemView = null;
@@ -211,4 +198,91 @@
         }
         v.removeOnAttachStateChangeListener(this);
     }
+
+    @Override
+    public void initForMenu(Context context, MenuBuilder menu) {
+        // Don't need to do anything; we added as a presenter in the constructor.
+    }
+
+    @Override
+    public MenuView getMenuView(ViewGroup root) {
+        throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
+    }
+
+    @Override
+    public void updateMenuView(boolean cleared) {
+        if (mAdapter != null) mAdapter.notifyDataSetChanged();
+    }
+
+    @Override
+    public void setCallback(Callback cb) {
+        mPresenterCallback = cb;
+    }
+
+    @Override
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+        if (subMenu.hasVisibleItems()) {
+            MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
+            subPopup.setCallback(mPresenterCallback);
+            if (subPopup.tryShow()) {
+                if (mPresenterCallback != null) {
+                    mPresenterCallback.onOpenSubMenu(subMenu);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+        // Only care about the (sub)menu we're presenting.
+        if (menu != mMenu) return;
+
+        dismiss();
+        if (mPresenterCallback != null) {
+            mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+        }
+    }
+
+    @Override
+    public boolean flagActionItems() {
+        return false;
+    }
+
+    private class MenuAdapter extends BaseAdapter {
+        private MenuBuilder mAdapterMenu;
+
+        public MenuAdapter(MenuBuilder menu) {
+            mAdapterMenu = menu;
+        }
+
+        public int getCount() {
+            ArrayList<MenuItemImpl> items = mOverflowOnly ?
+                    mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+            return items.size();
+        }
+
+        public MenuItemImpl getItem(int position) {
+            ArrayList<MenuItemImpl> items = mOverflowOnly ?
+                    mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+            return items.get(position);
+        }
+
+        public long getItemId(int position) {
+            // Since a menu item's ID is optional, we'll use the position as an
+            // ID for the item in the AdapterView
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+            }
+
+            MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+            itemView.initialize(getItem(position), 0);
+            return convertView;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
new file mode 100644
index 0000000..5baf419
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view.menu;
+
+import android.content.Context;
+import android.view.Menu;
+import android.view.ViewGroup;
+
+/**
+ * A MenuPresenter is responsible for building views for a Menu object.
+ * It takes over some responsibility from the old style monolithic MenuBuilder class.
+ */
+public interface MenuPresenter {
+    /**
+     * Called by menu implementation to notify another component of open/close events.
+     */
+    public interface Callback {
+        /**
+         * Called when a menu is closing.
+         * @param menu
+         * @param allMenusAreClosing
+         */
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+        /**
+         * Called when a submenu opens. Useful for notifying the application
+         * of menu state so that it does not attempt to hide the action bar
+         * while a submenu is open or similar.
+         *
+         * @param subMenu Submenu currently being opened
+         * @return true if the Callback will handle presenting the submenu, false if
+         *         the presenter should attempt to do so.
+         */
+        public boolean onOpenSubMenu(MenuBuilder subMenu);
+    }
+
+    /**
+     * Initialize this presenter for the given context and menu.
+     * This method is called by MenuBuilder when a presenter is
+     * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+     *
+     * @param context Context for this presenter; used for view creation and resource management
+     * @param menu Menu to host
+     */
+    public void initForMenu(Context context, MenuBuilder menu);
+
+    /**
+     * Retrieve a MenuView to display the menu specified in
+     * {@link #initForMenu(Context, Menu)}.
+     *
+     * @param root Intended parent of the MenuView.
+     * @return A freshly created MenuView.
+     */
+    public MenuView getMenuView(ViewGroup root);
+
+    /**
+     * Update the menu UI in response to a change. Called by
+     * MenuBuilder during the normal course of operation.
+     *
+     * @param cleared true if the menu was entirely cleared
+     */
+    public void updateMenuView(boolean cleared);
+
+    /**
+     * Set a callback object that will be notified of menu events
+     * related to this specific presentation.
+     * @param cb Callback that will be notified of future events
+     */
+    public void setCallback(Callback cb);
+
+    /**
+     * Called by Menu implementations to indicate that a submenu item
+     * has been selected. An active Callback should be notified, and
+     * if applicable the presenter should present the submenu.
+     *
+     * @param subMenu SubMenu being opened
+     * @return true if the the event was handled, false otherwise.
+     */
+    public boolean onSubMenuSelected(SubMenuBuilder subMenu);
+
+    /**
+     * Called by Menu implementations to indicate that a menu or submenu is
+     * closing. Presenter implementations should close the representation
+     * of the menu indicated as necessary and notify a registered callback.
+     *
+     * @param menu Menu or submenu that is closing.
+     * @param allMenusAreClosing True if all associated menus are closing.
+     */
+    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+    /**
+     * Called by Menu implementations to flag items that will be shown as actions.
+     * @return true if this presenter changed the action status of any items.
+     */
+    public boolean flagActionItems();
+}
diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java
index 5090400..407caae 100644
--- a/core/java/com/android/internal/view/menu/MenuView.java
+++ b/core/java/com/android/internal/view/menu/MenuView.java
@@ -22,7 +22,7 @@
 import android.graphics.drawable.Drawable;
 
 /**
- * Minimal interface for a menu view.  {@link #initialize(MenuBuilder, int)} must be called for the
+ * Minimal interface for a menu view.  {@link #initialize(MenuBuilder)} must be called for the
  * menu to be functional.
  * 
  * @hide
@@ -33,18 +33,8 @@
      * view is inflated.
      * 
      * @param menu The menu that this MenuView should display.
-     * @param menuType The type of this menu, one of 
-     *            {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
-     *            {@link MenuBuilder#TYPE_DIALOG}).
      */
-    public void initialize(MenuBuilder menu, int menuType);
-
-    /**
-     * Forces the menu view to update its view to reflect the new state of the menu.
-     * 
-     * @param cleared Whether the menu was cleared or just modified.
-     */
-    public void updateChildren(boolean cleared);
+    public void initialize(MenuBuilder menu);
 
     /**
      * Returns the default animations to be used for this menu when entering/exiting.
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index af1b996..ad773ee 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -67,11 +67,6 @@
     }
 
     @Override
-    public Callback getCallback() {
-        return mParentMenu.getCallback();
-    }
-
-    @Override
     public void setCallback(Callback callback) {
         mParentMenu.setCallback(callback);
     }
@@ -110,5 +105,4 @@
     public SubMenu setHeaderView(View view) {
         return (SubMenu) super.setHeaderViewInt(view);
     }
-    
 }
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 71af115..70fb3b2 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -16,6 +16,7 @@
 package com.android.internal.widget;
 
 import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuPresenter;
 import com.android.internal.view.menu.ActionMenuView;
 import com.android.internal.view.menu.MenuBuilder;
 
@@ -53,6 +54,7 @@
     private int mTitleStyleRes;
     private int mSubtitleStyleRes;
     private ActionMenuView mMenuView;
+    private ActionMenuPresenter mPresenter;
 
     private Animator mCurrentAnimation;
     private boolean mAnimateInOnLayout;
@@ -176,9 +178,9 @@
         });
 
         final MenuBuilder menu = (MenuBuilder) mode.getMenu();
-        mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this);
-        mMenuView.setOverflowReserved(true);
-        mMenuView.updateChildren(false);
+        mPresenter = new ActionMenuPresenter();
+        menu.addMenuPresenter(mPresenter);
+        mMenuView = (ActionMenuView) mPresenter.getMenuView(this);
         addView(mMenuView);
 
         mAnimateInOnLayout = true;
@@ -217,28 +219,22 @@
     }
 
     public boolean showOverflowMenu() {
-        if (mMenuView != null) {
-            return mMenuView.showOverflowMenu();
+        if (mPresenter != null) {
+            return mPresenter.showOverflowMenu();
         }
         return false;
     }
 
-    public void openOverflowMenu() {
-        if (mMenuView != null) {
-            mMenuView.openOverflowMenu();
-        }
-    }
-
     public boolean hideOverflowMenu() {
-        if (mMenuView != null) {
-            return mMenuView.hideOverflowMenu();
+        if (mPresenter != null) {
+            return mPresenter.hideOverflowMenu();
         }
         return false;
     }
 
     public boolean isOverflowMenuShowing() {
-        if (mMenuView != null) {
-            return mMenuView.isOverflowMenuShowing();
+        if (mPresenter != null) {
+            return mPresenter.isOverflowMenuShowing();
         }
         return false;
     }
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 2d9a9f2..74a6ae7 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -18,8 +18,10 @@
 
 import com.android.internal.R;
 import com.android.internal.view.menu.ActionMenuItem;
+import com.android.internal.view.menu.ActionMenuPresenter;
 import com.android.internal.view.menu.ActionMenuView;
 import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPresenter;
 
 import android.app.ActionBar;
 import android.app.ActionBar.OnNavigationListener;
@@ -112,6 +114,7 @@
 
     private MenuBuilder mOptionsMenu;
     private ActionMenuView mMenuView;
+    private ActionMenuPresenter mActionMenuPresenter;
     
     private ActionBarContextView mContextView;
 
@@ -250,16 +253,24 @@
         mCallback = callback;
     }
 
-    public void setMenu(Menu menu) {
+    public void setMenu(Menu menu, MenuPresenter.Callback cb) {
         if (menu == mOptionsMenu) return;
 
+        if (mOptionsMenu != null) {
+            mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
+        }
+
         MenuBuilder builder = (MenuBuilder) menu;
         mOptionsMenu = builder;
         if (mMenuView != null) {
             removeView(mMenuView);
         }
-        final ActionMenuView menuView = (ActionMenuView) builder.getMenuView(
-                MenuBuilder.TYPE_ACTION_BUTTON, null);
+        if (mActionMenuPresenter == null) {
+            mActionMenuPresenter = new ActionMenuPresenter();
+            mActionMenuPresenter.setCallback(cb);
+            builder.addMenuPresenter(mActionMenuPresenter);
+        }
+        final ActionMenuView menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
         final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
                 LayoutParams.MATCH_PARENT);
         menuView.setLayoutParams(layoutParams);
@@ -268,15 +279,15 @@
     }
 
     public boolean showOverflowMenu() {
-        if (mMenuView != null) {
-            return mMenuView.showOverflowMenu();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.showOverflowMenu();
         }
         return false;
     }
 
     public void openOverflowMenu() {
-        if (mMenuView != null) {
-            mMenuView.openOverflowMenu();
+        if (mActionMenuPresenter != null) {
+            showOverflowMenu();
         }
     }
 
@@ -289,28 +300,27 @@
     }
 
     public boolean hideOverflowMenu() {
-        if (mMenuView != null) {
-            return mMenuView.hideOverflowMenu();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.hideOverflowMenu();
         }
         return false;
     }
 
     public boolean isOverflowMenuShowing() {
-        if (mMenuView != null) {
-            return mMenuView.isOverflowMenuShowing();
-        }
-        return false;
-    }
-
-    public boolean isOverflowMenuOpen() {
-        if (mMenuView != null) {
-            return mMenuView.isOverflowMenuOpen();
+        if (mActionMenuPresenter != null) {
+            return mActionMenuPresenter.isOverflowMenuShowing();
         }
         return false;
     }
 
     public boolean isOverflowReserved() {
-        return mMenuView != null && mMenuView.isOverflowReserved();
+        return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
+    }
+
+    public void dismissPopupMenus() {
+        if (mActionMenuPresenter != null) {
+            mActionMenuPresenter.dismissPopupMenus();
+        }
     }
 
     public void setCustomNavigationView(View view) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index f8f8761..290f528 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -54,6 +54,7 @@
 	android_view_KeyCharacterMap.cpp \
 	android_view_GLES20Canvas.cpp \
 	android_view_MotionEvent.cpp \
+	android_view_PointerIcon.cpp \
 	android_view_VelocityTracker.cpp \
 	android_text_AndroidCharacter.cpp \
 	android_text_AndroidBidi.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b628b9dc..a4a229a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -170,6 +170,7 @@
 extern int register_android_view_InputQueue(JNIEnv* env);
 extern int register_android_view_KeyEvent(JNIEnv* env);
 extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_PointerIcon(JNIEnv* env);
 extern int register_android_view_VelocityTracker(JNIEnv* env);
 extern int register_android_content_res_ObbScanner(JNIEnv* env);
 extern int register_android_content_res_Configuration(JNIEnv* env);
@@ -1212,6 +1213,7 @@
     REG_JNI(register_android_view_InputQueue),
     REG_JNI(register_android_view_KeyEvent),
     REG_JNI(register_android_view_MotionEvent),
+    REG_JNI(register_android_view_PointerIcon),
     REG_JNI(register_android_view_VelocityTracker),
 
     REG_JNI(register_android_content_res_ObbScanner),
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
new file mode 100644
index 0000000..091341a
--- /dev/null
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "PointerIcon-JNI"
+
+#include "JNIHelp.h"
+
+#include "android_view_PointerIcon.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <android/graphics/GraphicsJNI.h>
+
+namespace android {
+
+static struct {
+    jclass clazz;
+    jfieldID mStyle;
+    jfieldID mBitmap;
+    jfieldID mHotSpotX;
+    jfieldID mHotSpotY;
+    jmethodID getSystemIcon;
+    jmethodID load;
+} gPointerIconClassInfo;
+
+
+// --- Global Functions ---
+
+jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env, jobject contextObj, int32_t style) {
+    jobject pointerIconObj = env->CallStaticObjectMethod(gPointerIconClassInfo.clazz,
+            gPointerIconClassInfo.getSystemIcon, contextObj, style);
+    if (env->ExceptionCheck()) {
+        LOGW("An exception occurred while getting a pointer icon with style %d.", style);
+        LOGW_EX(env);
+        env->ExceptionClear();
+        return NULL;
+    }
+    return pointerIconObj;
+}
+
+status_t android_view_PointerIcon_load(JNIEnv* env, jobject pointerIconObj, jobject contextObj,
+        PointerIcon* outPointerIcon) {
+    outPointerIcon->reset();
+
+    if (!pointerIconObj) {
+        return OK;
+    }
+
+    jobject loadedPointerIconObj = env->CallObjectMethod(pointerIconObj,
+            gPointerIconClassInfo.load, contextObj);
+    if (env->ExceptionCheck() || !loadedPointerIconObj) {
+        LOGW("An exception occurred while loading a pointer icon.");
+        LOGW_EX(env);
+        env->ExceptionClear();
+        return UNKNOWN_ERROR;
+    }
+
+    outPointerIcon->style = env->GetIntField(loadedPointerIconObj,
+            gPointerIconClassInfo.mStyle);
+    outPointerIcon->hotSpotX = env->GetFloatField(loadedPointerIconObj,
+            gPointerIconClassInfo.mHotSpotX);
+    outPointerIcon->hotSpotY = env->GetFloatField(loadedPointerIconObj,
+            gPointerIconClassInfo.mHotSpotY);
+
+    jobject bitmapObj = env->GetObjectField(loadedPointerIconObj, gPointerIconClassInfo.mBitmap);
+    if (bitmapObj) {
+        SkBitmap* bitmap = GraphicsJNI::getNativeBitmap(env, bitmapObj);
+        if (bitmap) {
+            outPointerIcon->bitmap = *bitmap; // use a shared pixel ref
+        }
+        env->DeleteLocalRef(bitmapObj);
+    }
+
+    env->DeleteLocalRef(loadedPointerIconObj);
+    return OK;
+}
+
+status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env, jobject contextObj,
+        int32_t style, PointerIcon* outPointerIcon) {
+    jobject pointerIconObj = android_view_PointerIcon_getSystemIcon(env, contextObj, style);
+    if (!pointerIconObj) {
+        outPointerIcon->reset();
+        return UNKNOWN_ERROR;
+    }
+
+    status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+            contextObj, outPointerIcon);
+    env->DeleteLocalRef(pointerIconObj);
+    return status;
+}
+
+
+// --- JNI Registration ---
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className); \
+        var = jclass(env->NewGlobalRef(var));
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_view_PointerIcon(JNIEnv* env) {
+    FIND_CLASS(gPointerIconClassInfo.clazz, "android/view/PointerIcon");
+
+    GET_FIELD_ID(gPointerIconClassInfo.mBitmap, gPointerIconClassInfo.clazz,
+            "mBitmap", "Landroid/graphics/Bitmap;");
+
+    GET_FIELD_ID(gPointerIconClassInfo.mStyle, gPointerIconClassInfo.clazz,
+            "mStyle", "I");
+
+    GET_FIELD_ID(gPointerIconClassInfo.mHotSpotX, gPointerIconClassInfo.clazz,
+            "mHotSpotX", "F");
+
+    GET_FIELD_ID(gPointerIconClassInfo.mHotSpotY, gPointerIconClassInfo.clazz,
+            "mHotSpotY", "F");
+
+    GET_STATIC_METHOD_ID(gPointerIconClassInfo.getSystemIcon, gPointerIconClassInfo.clazz,
+            "getSystemIcon", "(Landroid/content/Context;I)Landroid/view/PointerIcon;");
+
+    GET_METHOD_ID(gPointerIconClassInfo.load, gPointerIconClassInfo.clazz,
+            "load", "(Landroid/content/Context;)Landroid/view/PointerIcon;");
+
+    return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
new file mode 100644
index 0000000..3bfd645
--- /dev/null
+++ b/core/jni/android_view_PointerIcon.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _ANDROID_VIEW_POINTER_ICON_H
+#define _ANDROID_VIEW_POINTER_ICON_H
+
+#include "jni.h"
+
+#include <utils/Errors.h>
+#include <SkBitmap.h>
+
+namespace android {
+
+/* Pointer icon styles.
+ * Must match the definition in android.view.PointerIcon.
+ */
+enum {
+    POINTER_ICON_STYLE_CUSTOM = -1,
+    POINTER_ICON_STYLE_NULL = 0,
+    POINTER_ICON_STYLE_ARROW = 1000,
+    POINTER_ICON_STYLE_SPOT_HOVER = 2000,
+    POINTER_ICON_STYLE_SPOT_TOUCH = 2001,
+    POINTER_ICON_STYLE_SPOT_ANCHOR = 2002,
+};
+
+/*
+ * Describes a pointer icon.
+ */
+struct PointerIcon {
+    inline PointerIcon() {
+        reset();
+    }
+
+    int32_t style;
+    SkBitmap bitmap;
+    float hotSpotX;
+    float hotSpotY;
+
+    inline bool isNullIcon() {
+        return style == POINTER_ICON_STYLE_NULL;
+    }
+
+    inline void reset() {
+        style = POINTER_ICON_STYLE_NULL;
+        bitmap.reset();
+        hotSpotX = 0;
+        hotSpotY = 0;
+    }
+};
+
+/* Gets a system pointer icon with the specified style. */
+extern jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env,
+        jobject contextObj, int32_t style);
+
+/* Loads the bitmap associated with a pointer icon.
+ * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
+extern status_t android_view_PointerIcon_load(JNIEnv* env,
+        jobject pointerIconObj, jobject contextObj, PointerIcon* outPointerIcon);
+
+/* Loads the bitmap associated with a pointer icon by style.
+ * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
+extern status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env,
+        jobject contextObj, int32_t style, PointerIcon* outPointerIcon);
+
+} // namespace android
+
+#endif // _ANDROID_OS_POINTER_ICON_H
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor.png b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
new file mode 100644
index 0000000..d7aca36
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml
new file mode 100644
index 0000000..2222b8e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_anchor"
+    android:hotSpotX="33"
+    android:hotSpotY="33" />
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover.png b/core/res/res/drawable-mdpi/pointer_spot_hover.png
new file mode 100644
index 0000000..5041aa3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml
new file mode 100644
index 0000000..dc62a69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_hover"
+    android:hotSpotX="33"
+    android:hotSpotY="33" />
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch.png b/core/res/res/drawable-mdpi/pointer_spot_touch.png
new file mode 100644
index 0000000..64a42a1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml
new file mode 100644
index 0000000..4bffee6
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+    android:bitmap="@drawable/pointer_spot_touch"
+    android:hotSpotX="24"
+    android:hotSpotY="24" />
diff --git a/core/res/res/layout/action_menu_layout.xml b/core/res/res/layout/action_menu_layout.xml
index 18d5531..5696d87 100644
--- a/core/res/res/layout/action_menu_layout.xml
+++ b/core/res/res/layout/action_menu_layout.xml
@@ -17,4 +17,7 @@
 <com.android.internal.view.menu.ActionMenuView
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="wrap_content"
-     android:layout_height="wrap_content" />
+     android:layout_height="wrap_content"
+     android:divider="?android:attr/dividerVertical"
+     android:dividerPadding="12dip"
+     android:gravity="center_vertical" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 819ce58..e8767d8 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -757,6 +757,13 @@
         <!-- Default style for the Switch widget. -->
         <attr name="switchStyle" format="reference" />
 
+        <!-- ============== -->
+        <!-- Pointer styles -->
+        <!-- ============== -->
+        <eat-comment />
+
+        <!-- Reference to the Pointer style -->
+        <attr name="pointerStyle" format="reference" />
     </declare-styleable>
 
     <!-- **************************************************************** -->
@@ -4921,6 +4928,17 @@
         <attr name="switchPadding" format="dimension" />
     </declare-styleable>
 
+    <declare-styleable name="Pointer">
+        <!-- Reference to a pointer icon drawable with STYLE_ARROW -->
+        <attr name="pointerIconArrow" format="reference" />
+        <!-- Reference to a pointer icon drawable with STYLE_SPOT_HOVER -->
+        <attr name="pointerIconSpotHover" format="reference" />
+        <!-- Reference to a pointer icon drawable with STYLE_SPOT_TOUCH -->
+        <attr name="pointerIconSpotTouch" format="reference" />
+        <!-- Reference to a pointer icon drawable with STYLE_SPOT_ANCHOR -->
+        <attr name="pointerIconSpotAnchor" format="reference" />
+    </declare-styleable>
+
     <declare-styleable name="PointerIcon">
         <!-- Drawable to use as the icon bitmap. -->
         <attr name="bitmap" format="reference" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index bf4c6d7..198ff8b 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -602,11 +602,10 @@
         <item name="android:divider">@android:drawable/divider_horizontal_bright_opaque</item>
     </style>
 
-    <style name="Widget.ListView.Menu">
+    <style name="Widget.ListView.Menu" parent="Widget.Holo.ListView">
 		<item name="android:cacheColorHint">@null</item>
         <item name="android:scrollbars">vertical</item>
         <item name="android:fadingEdge">none</item>
-        <item name="listSelector">@android:drawable/menu_selector</item>
         <!-- Light background for the list in menus, so the divider for bright themes -->
         <item name="android:divider">@android:drawable/divider_horizontal_dark</item>
     </style>
@@ -2210,4 +2209,12 @@
         <item name="android:borderLeft">0dip</item>
         <item name="android:borderRight">0dip</item>
     </style>
+
+    <!-- Pointer styles -->
+    <style name="Pointer">
+        <item name="android:pointerIconArrow">@android:drawable/pointer_arrow_icon</item>
+        <item name="android:pointerIconSpotHover">@android:drawable/pointer_spot_hover_icon</item>
+        <item name="android:pointerIconSpotTouch">@android:drawable/pointer_spot_touch_icon</item>
+        <item name="android:pointerIconSpotAnchor">@android:drawable/pointer_spot_anchor_icon</item>
+    </style>
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index b1e4f0f..b9fd6a5 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -323,6 +323,8 @@
         <item name="fastScrollOverlayPosition">floating</item>
         <item name="fastScrollTextColor">@android:color/primary_text_dark</item>
 
+        <!-- Pointer style -->
+        <item name="pointerStyle">@android:style/Pointer</item>
     </style>
 
     <!-- Variant of the default (dark) theme with no title bar -->
@@ -696,10 +698,10 @@
     <!-- Menu Themes -->
     <eat-comment />
 
-    <style name="Theme.IconMenu">
+    <style name="Theme.IconMenu" parent="Theme.Holo">
         <!-- Menu/item attributes -->
         <item name="android:itemTextAppearance">@android:style/TextAppearance.Widget.IconMenu.Item</item>
-        <item name="android:itemBackground">@android:drawable/menu_selector</item>
+        <item name="android:itemBackground">?android:attr/selectableItemBackground</item>
         <item name="android:itemIconDisabledAlpha">?android:attr/disabledAlpha</item>
         <item name="android:horizontalDivider">@android:drawable/divider_horizontal_dark</item>
         <item name="android:verticalDivider">@android:drawable/divider_vertical_dark</item>
@@ -708,7 +710,7 @@
         <item name="android:background">@null</item>
     </style>
 
-    <style name="Theme.ExpandedMenu">
+    <style name="Theme.ExpandedMenu" parent="Theme.Holo">
         <!-- Menu/item attributes -->
         <item name="android:itemTextAppearance">?android:attr/textAppearanceLarge</item>
         <item name="android:listViewStyle">@android:style/Widget.ListView.Menu</item>
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
index d9bf860..9347b27 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
@@ -59,6 +59,8 @@
     private void assertLayout(Integer... expectedLayout) {
         toggleMenu();
         
+        /* TODO These need to be rewritten to account for presenters that an activity
+         * does not have access to.
         IconMenuView iconMenuView = ((IconMenuView) mActivity.getMenuView(MenuBuilder.TYPE_ICON));
         int[] layout = iconMenuView.getLayout();
         int layoutNumRows = iconMenuView.getLayoutNumRows(); 
@@ -70,6 +72,7 @@
             assertEquals("Col mismatch on row " + row, expectedLayout[row].intValue(),
                     layout[row]);
         }
+         */
     }
     
     public void test1ShortItem() {
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
index ad746b07..b053699 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
@@ -58,6 +58,8 @@
     private void assertLayout(Integer... expectedLayout) {
         toggleMenu();
         
+        /* TODO These need to be rewritten to account for presenters that an activity
+         * does not have access to.
         IconMenuView iconMenuView = ((IconMenuView) mActivity.getMenuView(MenuBuilder.TYPE_ICON));
         int[] layout = iconMenuView.getLayout();
         int layoutNumRows = iconMenuView.getLayoutNumRows(); 
@@ -69,6 +71,7 @@
             assertEquals("Col mismatch on row " + row, expectedLayout[row].intValue(),
                     layout[row]);
         }
+         */
     }
     
     public void test1ShortItem() {
diff --git a/core/tests/coretests/src/android/view/menu/MenuScenario.java b/core/tests/coretests/src/android/view/menu/MenuScenario.java
index b0b8802..668aec4 100644
--- a/core/tests/coretests/src/android/view/menu/MenuScenario.java
+++ b/core/tests/coretests/src/android/view/menu/MenuScenario.java
@@ -16,16 +16,12 @@
 
 package android.view.menu;
 
-import android.util.ListScenario;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuBuilder.MenuAdapter;
-
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.ListScenario;
 import android.util.SparseArray;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.view.View;
 
 /**
  * Utility base class for creating various Menu scenarios. Configurable by the
@@ -36,7 +32,6 @@
     private Menu mMenu;
     private MenuItem[] mItems;
     private boolean[] mWasItemClicked;
-    private MenuAdapter[] mMenuAdapters = new MenuAdapter[MenuBuilder.NUM_TYPES];
     
     @Override
     protected void onCreate(Bundle icicle) {
@@ -149,39 +144,6 @@
         return -1;
     }
     
-    /**
-     * @see MenuBuilder#getMenuAdapter(int)
-     */
-    public MenuAdapter getMenuAdapter(int menuType) {
-        if (mMenuAdapters[menuType] == null) {
-            mMenuAdapters[menuType] = ((MenuBuilder) mMenu).getMenuAdapter(menuType);
-        }
-        
-        return mMenuAdapters[menuType];
-    }
-
-    /**
-     * Gets a menu view. Call this after you're sure it has been shown,
-     * otherwise it may not have the proper layout_* attributes set.
-     * 
-     * @param menuType The type of menu.
-     * @return The MenuView for that type.
-     */
-    public View getMenuView(int menuType) {
-        return ((MenuBuilder) mMenu).getMenuView(menuType, null);
-    }
-    
-    /**
-     * Gets the menu item view for a given position.
-     * 
-     * @param menuType The type of menu.
-     * @param position The position of the item.
-     * @return The menu item view for the given item in the given menu type.
-     */
-    public View getItemView(int menuType, int position) {
-        return getMenuAdapter(menuType).getView(position, null, null);
-    }
-    
     public static class Params {
         // Using as data structure, so no m prefix
         private boolean shouldShowMenu = true;
diff --git a/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java b/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java
index 4e71053..82ad858 100644
--- a/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java
@@ -61,6 +61,9 @@
 
     @LargeTest
     public void testTouchModeTransfersRemovesFocus() throws Exception {
+        /* TODO These need to be rewritten to account for presenters that an activity
+         * does not have access to.
+
         // open menu, move around to give it focus
         sendKeys(KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_DPAD_LEFT);
         final View menuItem = mActivity.getItemView(MenuBuilder.TYPE_ICON, 0);
@@ -80,5 +83,6 @@
         sendKeys(KeyEvent.KEYCODE_MENU);
         assertTrue("menuItem.isInTouchMode()", menuItem.isInTouchMode());
         assertFalse("menuItem.isFocused()", menuItem.isFocused());
+         */
     }
 }
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 0dc29c8..9b92c73 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -620,6 +620,11 @@
     // Oldest sample to consider when calculating the velocity.
     static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms
 
+    // When the total duration of the window of samples being averaged is less
+    // than the window size, the resulting velocity is scaled to reduce the impact
+    // of overestimation in short traces.
+    static const nsecs_t MIN_WINDOW = 100 * 1000000; // 100 ms
+
     // The minimum duration between samples when estimating velocity.
     static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
 
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index bbe579e..a95f432 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -832,6 +832,7 @@
         const Position& oldestPosition =
                 oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
         nsecs_t lastDuration = 0;
+
         while (numTouches-- > 1) {
             if (++index == HISTORY_SIZE) {
                 index = 0;
@@ -858,6 +859,14 @@
 
         // Make sure we used at least one sample.
         if (samplesUsed != 0) {
+            // Scale the velocity linearly if the window of samples is small.
+            nsecs_t totalDuration = newestMovement.eventTime - oldestMovement.eventTime;
+            if (totalDuration < MIN_WINDOW) {
+                float scale = float(totalDuration) / float(MIN_WINDOW);
+                accumVx *= scale;
+                accumVy *= scale;
+            }
+
             *outVx = accumVx;
             *outVy = accumVy;
             return true;
diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
index d5dd126..b54fb9d 100644
--- a/libs/utils/Looper.cpp
+++ b/libs/utils/Looper.cpp
@@ -662,7 +662,8 @@
 #endif
 
 void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {
-    sendMessageAtTime(LLONG_MIN, handler, message);
+    nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+    sendMessageAtTime(now, handler, message);
 }
 
 void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index d07ea1b..576a850 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -33,11 +33,25 @@
 
 namespace android {
 
-NuPlayer::HTTPLiveSource::HTTPLiveSource(const char *url, uint32_t flags)
+NuPlayer::HTTPLiveSource::HTTPLiveSource(
+        const char *url,
+        const KeyedVector<String8, String8> *headers)
     : mURL(url),
-      mFlags(flags),
+      mFlags(0),
       mEOS(false),
       mOffset(0) {
+    if (headers) {
+        mExtraHeaders = *headers;
+
+        ssize_t index =
+            mExtraHeaders.indexOfKey(String8("x-hide-urls-from-log"));
+
+        if (index >= 0) {
+            mFlags |= kFlagIncognito;
+
+            mExtraHeaders.removeItemsAt(index);
+        }
+    }
 }
 
 NuPlayer::HTTPLiveSource::~HTTPLiveSource() {
@@ -55,7 +69,8 @@
 
     mLiveLooper->registerHandler(mLiveSession);
 
-    mLiveSession->connect(mURL.c_str());
+    mLiveSession->connect(
+            mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
 
     mTSParser = new ATSParser;
 }
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
index a8ce7f4..7a337e9 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
@@ -27,11 +27,9 @@
 struct LiveSession;
 
 struct NuPlayer::HTTPLiveSource : public NuPlayer::Source {
-    enum Flags {
-        // Don't log any URLs.
-        kFlagIncognito = 1,
-    };
-    HTTPLiveSource(const char *url, uint32_t flags = 0);
+    HTTPLiveSource(
+            const char *url,
+            const KeyedVector<String8, String8> *headers);
 
     virtual void start();
 
@@ -49,7 +47,13 @@
     virtual ~HTTPLiveSource();
 
 private:
+    enum Flags {
+        // Don't log any URLs.
+        kFlagIncognito = 1,
+    };
+
     AString mURL;
+    KeyedVector<String8, String8> mExtraHeaders;
     uint32_t mFlags;
     bool mEOS;
     off64_t mOffset;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index d439f6e..effa703 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -72,17 +72,7 @@
         const char *url, const KeyedVector<String8, String8> *headers) {
     sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());
 
-    uint32_t flags = 0;
-
-    if (headers) {
-        ssize_t index = headers->indexOfKey(String8("x-hide-urls-from-log"));
-
-        if (index >= 0) {
-            flags |= HTTPLiveSource::kFlagIncognito;
-        }
-    }
-
-    msg->setObject("source", new HTTPLiveSource(url, flags));
+    msg->setObject("source", new HTTPLiveSource(url, headers));
     msg->post();
 }
 
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 8e1bdf3..012d9ad 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -67,9 +67,17 @@
     return mDataSource;
 }
 
-void LiveSession::connect(const char *url) {
+void LiveSession::connect(
+        const char *url, const KeyedVector<String8, String8> *headers) {
     sp<AMessage> msg = new AMessage(kWhatConnect, id());
     msg->setString("url", url);
+
+    if (headers != NULL) {
+        msg->setPointer(
+                "headers",
+                new KeyedVector<String8, String8>(*headers));
+    }
+
     msg->post();
 }
 
@@ -144,6 +152,16 @@
     AString url;
     CHECK(msg->findString("url", &url));
 
+    KeyedVector<String8, String8> *headers = NULL;
+    if (!msg->findPointer("headers", (void **)&headers)) {
+        mExtraHeaders.clear();
+    } else {
+        mExtraHeaders = *headers;
+
+        delete headers;
+        headers = NULL;
+    }
+
     if (!(mFlags & kFlagIncognito)) {
         LOGI("onConnect '%s'", url.c_str());
     } else {
@@ -210,7 +228,8 @@
             }
         }
 
-        status_t err = mHTTPDataSource->connect(url);
+        status_t err = mHTTPDataSource->connect(
+                url, mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
 
         if (err != OK) {
             return err;
diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h
index 2b5ea0e..99abe64 100644
--- a/media/libstagefright/include/LiveSession.h
+++ b/media/libstagefright/include/LiveSession.h
@@ -20,6 +20,8 @@
 
 #include <media/stagefright/foundation/AHandler.h>
 
+#include <utils/String8.h>
+
 namespace android {
 
 struct ABuffer;
@@ -37,7 +39,10 @@
 
     sp<DataSource> getDataSource();
 
-    void connect(const char *url);
+    void connect(
+            const char *url,
+            const KeyedVector<String8, String8> *headers = NULL);
+
     void disconnect();
 
     // Blocks until seek is complete.
@@ -78,6 +83,8 @@
     sp<HTTPBase> mHTTPDataSource;
 
     AString mMasterURL;
+    KeyedVector<String8, String8> mExtraHeaders;
+
     Vector<BandwidthItem> mBandwidthItems;
 
     KeyedVector<AString, sp<ABuffer> > mAESKeyForURI;
diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java
index 0935383..27d8976 100644
--- a/obex/javax/obex/ClientSession.java
+++ b/obex/javax/obex/ClientSession.java
@@ -449,8 +449,8 @@
                 maxPacketSize = (mInput.read() << 8) + mInput.read();
 
                 //check with local max size
-                if (maxPacketSize > ObexHelper.MAX_PACKET_SIZE_INT) {
-                    maxPacketSize = ObexHelper.MAX_PACKET_SIZE_INT;
+                if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
+                    maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
                 }
 
                 if (length > 7) {
diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java
index df0e0fb..8c12a20 100644
--- a/obex/javax/obex/ObexHelper.java
+++ b/obex/javax/obex/ObexHelper.java
@@ -70,6 +70,12 @@
      */
     public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
 
+    /**
+     * Temporary workaround to be able to push files to Windows 7.
+     * TODO: Should be removed as soon as Microsoft updates their driver.
+     */
+    public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
+
     public static final int OBEX_OPCODE_CONNECT = 0x80;
 
     public static final int OBEX_OPCODE_DISCONNECT = 0x81;
diff --git a/opengl/libs/GLES2_dbg/Android.mk b/opengl/libs/GLES2_dbg/Android.mk
index 853cce6..9f6e68c 100644
--- a/opengl/libs/GLES2_dbg/Android.mk
+++ b/opengl/libs/GLES2_dbg/Android.mk
@@ -45,3 +45,5 @@
 LOCAL_MODULE_TAGS := optional
 
 include $(BUILD_SHARED_LIBRARY)
+
+include $(LOCAL_PATH)/test/Android.mk
diff --git a/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py b/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
index 914ea24..57e008c 100755
--- a/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
+++ b/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
@@ -142,6 +142,7 @@
         TimeMode = 1; // arg0 = SYSTEM_TIME_* in utils/Timers.h
         ExpectResponse = 2; // arg0 = enum Function, arg1 = true/false
         CaptureSwap = 3; // arg0 = number of eglSwapBuffers to glReadPixels
+        GLConstant = 4; // arg0 = GLenum, arg1 = constant; send GL impl. constants
     };
     optional Prop prop = 21; // used with SETPROP, value in arg0
     optional float clock = 22; // wall clock in seconds
diff --git a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
index fe93874..7f5b27b 100644
--- a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
+++ b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
@@ -25,11 +25,11 @@
 namespace android
 {
 
-static pthread_key_t sEGLThreadLocalStorageKey = -1;
+pthread_key_t dbgEGLThreadLocalStorageKey = -1;
 
 DbgContext * getDbgContextThreadSpecific()
 {
-    tls_t* tls = (tls_t*)pthread_getspecific(sEGLThreadLocalStorageKey);
+    tls_t* tls = (tls_t*)pthread_getspecific(dbgEGLThreadLocalStorageKey);
     return tls->dbg;
 }
 
@@ -63,7 +63,7 @@
 DbgContext * CreateDbgContext(const pthread_key_t EGLThreadLocalStorageKey,
                               const unsigned version, const gl_hooks_t * const hooks)
 {
-    sEGLThreadLocalStorageKey = EGLThreadLocalStorageKey;
+    dbgEGLThreadLocalStorageKey = EGLThreadLocalStorageKey;
     assert(version < 2);
     assert(GL_NO_ERROR == hooks->gl.glGetError());
     GLint MAX_VERTEX_ATTRIBS = 0;
@@ -71,7 +71,24 @@
     GLint readFormat, readType;
     hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
     hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
-    return new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS, readFormat, readType);
+    DbgContext * const dbg = new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS, readFormat, readType);
+
+    glesv2debugger::Message msg, cmd;
+    msg.set_context_id(reinterpret_cast<int>(dbg));
+    msg.set_expect_response(false);
+    msg.set_type(msg.Response);
+    msg.set_function(msg.SETPROP);
+    msg.set_prop(msg.GLConstant);
+    msg.set_arg0(GL_MAX_VERTEX_ATTRIBS);
+    msg.set_arg1(MAX_VERTEX_ATTRIBS);
+    Send(msg, cmd);
+
+    GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0;
+    hooks->gl.glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+    msg.set_arg0(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+    msg.set_arg1(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+    Send(msg, cmd);
+    return dbg;
 }
 
 void DestroyDbgContext(DbgContext * const dbg)
@@ -149,6 +166,37 @@
     }
 }
 
+unsigned char * DbgContext::Decompress(const void * in, const unsigned int inLen,
+                                       unsigned int * const outLen)
+{
+    assert(inLen > 4 * 3);
+    if (inLen < 4 * 3)
+        return NULL;
+    *outLen = *(uint32_t *)in;
+    unsigned char * const out = (unsigned char *)malloc(*outLen);
+    unsigned int outPos = 0;
+    const unsigned char * const end = (const unsigned char *)in + inLen;
+    for (const unsigned char * inData = (const unsigned char *)in + 4; inData < end; ) {
+        const uint32_t chunkOut = *(uint32_t *)inData;
+        inData += 4;
+        const uint32_t chunkIn = *(uint32_t *)inData;
+        inData += 4;
+        if (chunkIn > 0) {
+            assert(inData + chunkIn <= end);
+            assert(outPos + chunkOut <= *outLen);
+            outPos += lzf_decompress(inData, chunkIn, out + outPos, chunkOut);
+            inData += chunkIn;
+        } else {
+            assert(inData + chunkOut <= end);
+            assert(outPos + chunkOut <= *outLen);
+            memcpy(out + outPos, inData, chunkOut);
+            inData += chunkOut;
+            outPos += chunkOut;
+        }
+    }
+    return out;
+}
+
 void * DbgContext::GetReadPixelsBuffer(const unsigned size)
 {
     if (lzf_refBufSize < size + 8) {
diff --git a/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp b/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
index 40478c3..50f70f7 100644
--- a/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
+++ b/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
@@ -477,6 +477,7 @@
     case 1:
     case 2:
     case 3:
+    case 4:
       return true;
     default:
       return false;
@@ -488,6 +489,7 @@
 const Message_Prop Message::TimeMode;
 const Message_Prop Message::ExpectResponse;
 const Message_Prop Message::CaptureSwap;
+const Message_Prop Message::GLConstant;
 const Message_Prop Message::Prop_MIN;
 const Message_Prop Message::Prop_MAX;
 const int Message::Prop_ARRAYSIZE;
@@ -975,7 +977,7 @@
         if (input->ExpectTag(208)) goto parse_image_width;
         break;
       }
-
+      
       // optional int32 image_width = 26;
       case 26: {
         if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
@@ -991,7 +993,7 @@
         if (input->ExpectTag(216)) goto parse_image_height;
         break;
       }
-
+      
       // optional int32 image_height = 27;
       case 27: {
         if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
@@ -1139,12 +1141,12 @@
   if (_has_bit(18)) {
     ::google::protobuf::internal::WireFormatLite::WriteInt32(26, this->image_width(), output);
   }
-
+  
   // optional int32 image_height = 27;
   if (_has_bit(19)) {
     ::google::protobuf::internal::WireFormatLite::WriteInt32(27, this->image_height(), output);
   }
-
+  
 }
 
 int Message::ByteSize() const {
@@ -1282,14 +1284,14 @@
         ::google::protobuf::internal::WireFormatLite::Int32Size(
           this->image_width());
     }
-
+    
     // optional int32 image_height = 27;
     if (has_image_height()) {
       total_size += 2 +
         ::google::protobuf::internal::WireFormatLite::Int32Size(
           this->image_height());
     }
-
+    
     // optional float time = 11;
     if (has_time()) {
       total_size += 1 + 4;
diff --git a/opengl/libs/GLES2_dbg/src/debugger_message.pb.h b/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
index 4ccfebb..5c94664 100644
--- a/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
+++ b/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
@@ -258,11 +258,12 @@
   Message_Prop_CaptureDraw = 0,
   Message_Prop_TimeMode = 1,
   Message_Prop_ExpectResponse = 2,
-  Message_Prop_CaptureSwap = 3
+  Message_Prop_CaptureSwap = 3,
+  Message_Prop_GLConstant = 4
 };
 bool Message_Prop_IsValid(int value);
 const Message_Prop Message_Prop_Prop_MIN = Message_Prop_CaptureDraw;
-const Message_Prop Message_Prop_Prop_MAX = Message_Prop_CaptureSwap;
+const Message_Prop Message_Prop_Prop_MAX = Message_Prop_GLConstant;
 const int Message_Prop_Prop_ARRAYSIZE = Message_Prop_Prop_MAX + 1;
 
 // ===================================================================
@@ -544,6 +545,7 @@
   static const Prop TimeMode = Message_Prop_TimeMode;
   static const Prop ExpectResponse = Message_Prop_ExpectResponse;
   static const Prop CaptureSwap = Message_Prop_CaptureSwap;
+  static const Prop GLConstant = Message_Prop_GLConstant;
   static inline bool Prop_IsValid(int value) {
     return Message_Prop_IsValid(value);
   }
@@ -691,14 +693,14 @@
   static const int kImageWidthFieldNumber = 26;
   inline ::google::protobuf::int32 image_width() const;
   inline void set_image_width(::google::protobuf::int32 value);
-
+  
   // optional int32 image_height = 27;
   inline bool has_image_height() const;
   inline void clear_image_height();
   static const int kImageHeightFieldNumber = 27;
   inline ::google::protobuf::int32 image_height() const;
   inline void set_image_height(::google::protobuf::int32 value);
-
+  
   // optional float time = 11;
   inline bool has_time() const;
   inline void clear_time();
diff --git a/opengl/libs/GLES2_dbg/src/header.h b/opengl/libs/GLES2_dbg/src/header.h
index c9e6c41..f2b1fa6 100644
--- a/opengl/libs/GLES2_dbg/src/header.h
+++ b/opengl/libs/GLES2_dbg/src/header.h
@@ -73,8 +73,9 @@
 };
 
 struct DbgContext {
-private:
     static const unsigned int LZF_CHUNK_SIZE = 256 * 1024;
+
+private:
     char * lzf_buf; // malloc / free; for lzf chunk compression and other uses
 
     // used as buffer and reference frame for ReadPixels; malloc/free
@@ -129,6 +130,8 @@
 
     void Fetch(const unsigned index, std::string * const data) const;
     void Compress(const void * in_data, unsigned in_len, std::string * const outStr);
+    static unsigned char * Decompress(const void * in, const unsigned int inLen,
+                                      unsigned int * const outLen); // malloc/free
     void * GetReadPixelsBuffer(const unsigned size);
     bool IsReadPixelBuffer(const void * const ptr)  {
         return ptr == lzf_ref[lzf_readIndex];
diff --git a/opengl/libs/GLES2_dbg/src/server.cpp b/opengl/libs/GLES2_dbg/src/server.cpp
index f13d6cc..0c711bf 100644
--- a/opengl/libs/GLES2_dbg/src/server.cpp
+++ b/opengl/libs/GLES2_dbg/src/server.cpp
@@ -159,8 +159,17 @@
 
 float Send(const glesv2debugger::Message & msg, glesv2debugger::Message & cmd)
 {
+    // TODO: use per DbgContext send/receive buffer and async socket
+    //  instead of mutex and blocking io; watch out for large messages
     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-    pthread_mutex_lock(&mutex); // TODO: this is just temporary
+    struct Autolock {
+        Autolock() {
+            pthread_mutex_lock(&mutex);
+        }
+        ~Autolock() {
+            pthread_mutex_unlock(&mutex);
+        }
+    } autolock;
 
     if (msg.function() != glesv2debugger::Message_Function_ACK)
         assert(msg.has_context_id() && msg.context_id() != 0);
@@ -176,7 +185,6 @@
                 Die("MAX_FILE_SIZE reached");
             }
         }
-        pthread_mutex_unlock(&mutex);
         return 0;
     }
     int sent = -1;
@@ -186,13 +194,17 @@
         Die("Failed to send message length");
     }
     nsecs_t c0 = systemTime(timeMode);
-    sent = send(clientSock, str.c_str(), str.length(), 0);
+    sent = send(clientSock, str.data(), str.length(), 0);
     float t = (float)ns2ms(systemTime(timeMode) - c0);
     if (sent != str.length()) {
         LOGD("actual sent=%d expected=%d clientSock=%d", sent, str.length(), clientSock);
         Die("Failed to send message");
     }
-
+    // TODO: factor Receive & TryReceive out and into MessageLoop, or add control argument.
+    // mean while, if server is sending a SETPROP then don't try to receive,
+    //  because server will not be processing received command
+    if (msg.function() == msg.SETPROP)
+        return t;
     // try to receive commands even though not expecting response,
     //  since client can send SETPROP and other commands anytime
     if (!msg.expect_response()) {
@@ -204,8 +216,6 @@
         }
     } else
         Receive(cmd);
-
-    pthread_mutex_unlock(&mutex);
     return t;
 }
 
@@ -246,8 +256,9 @@
     msg.set_function(function);
 
     // when not exectResponse, set cmd to CONTINUE then SKIP
+    // cmd will be overwritten by received command
     cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
-    cmd.set_expect_response(false);
+    cmd.set_expect_response(expectResponse);
     glesv2debugger::Message_Function oldCmd = cmd.function();
     Send(msg, cmd);
     expectResponse = cmd.expect_response();
diff --git a/opengl/libs/GLES2_dbg/src/vertex.cpp b/opengl/libs/GLES2_dbg/src/vertex.cpp
index 7edc050..029ee3b 100644
--- a/opengl/libs/GLES2_dbg/src/vertex.cpp
+++ b/opengl/libs/GLES2_dbg/src/vertex.cpp
@@ -43,10 +43,8 @@
 
     void * pixels = NULL;
     int viewport[4] = {};
-    if (!expectResponse) {
-        cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
-        cmd.set_expect_response(false);
-    }
+    cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+    cmd.set_expect_response(expectResponse);
     glesv2debugger::Message_Function oldCmd = cmd.function();
     Send(msg, cmd);
     expectResponse = cmd.expect_response();
@@ -61,8 +59,6 @@
             msg.set_function(glesv2debugger::Message_Function_glDrawArrays);
             msg.set_type(glesv2debugger::Message_Type_AfterCall);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
-                cmd.set_function(glesv2debugger::Message_Function_SKIP);
             if (!expectResponse) {
                 cmd.set_function(glesv2debugger::Message_Function_SKIP);
                 cmd.set_expect_response(false);
@@ -154,10 +150,8 @@
 
     void * pixels = NULL;
     int viewport[4] = {};
-    if (!expectResponse) {
-        cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
-        cmd.set_expect_response(false);
-    }
+    cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+    cmd.set_expect_response(expectResponse);
     glesv2debugger::Message_Function oldCmd = cmd.function();
     Send(msg, cmd);
     expectResponse = cmd.expect_response();
@@ -172,8 +166,6 @@
             msg.set_function(glesv2debugger::Message_Function_glDrawElements);
             msg.set_type(glesv2debugger::Message_Type_AfterCall);
             msg.set_expect_response(expectResponse);
-            if (!expectResponse)
-                cmd.set_function(glesv2debugger::Message_Function_SKIP);
             if (!expectResponse) {
                 cmd.set_function(glesv2debugger::Message_Function_SKIP);
                 cmd.set_expect_response(false);
diff --git a/opengl/libs/GLES2_dbg/test/Android.mk b/opengl/libs/GLES2_dbg/test/Android.mk
new file mode 100644
index 0000000..14a84b4
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/Android.mk
@@ -0,0 +1,39 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := \
+    $(LOCAL_PATH) \
+    $(LOCAL_PATH)/../src \
+    $(LOCAL_PATH)/../../ \
+    external/gtest/include \
+    external/stlport/stlport \
+    external/protobuf/src \
+    bionic \
+    external \
+#
+
+LOCAL_SRC_FILES:= \
+    test_main.cpp \
+    test_server.cpp \
+    test_socket.cpp \
+#
+
+LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2_dbg libstlport
+LOCAL_STATIC_LIBRARIES := libgtest libprotobuf-cpp-2.3.0-lite liblzf
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE:= libGLESv2_dbg_test
+
+ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true)
+    LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
+endif
+ifneq ($(TARGET_SIMULATOR),true)
+    LOCAL_C_INCLUDES += bionic/libc/private
+endif
+
+LOCAL_CFLAGS += -DLOG_TAG=\"libEGL\"
+LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+LOCAL_CFLAGS += -fvisibility=hidden
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/opengl/libs/GLES2_dbg/test/test_main.cpp b/opengl/libs/GLES2_dbg/test/test_main.cpp
new file mode 100644
index 0000000..058bea4
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_main.cpp
@@ -0,0 +1,234 @@
+/*
+ ** Copyright 2011, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "hooks.h"
+
+namespace
+{
+
+// The fixture for testing class Foo.
+class DbgContextTest : public ::testing::Test
+{
+protected:
+    android::DbgContext dbg;
+    gl_hooks_t hooks;
+
+    DbgContextTest()
+            : dbg(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE) {
+        // You can do set-up work for each test here.
+        hooks.gl.glGetError = GetError;
+    }
+
+    static GLenum GetError() {
+        return GL_NO_ERROR;
+    }
+
+    virtual ~DbgContextTest() {
+        // You can do clean-up work that doesn't throw exceptions here.
+    }
+
+    // If the constructor and destructor are not enough for setting up
+    // and cleaning up each test, you can define the following methods:
+
+    virtual void SetUp() {
+        // Code here will be called immediately after the constructor (right
+        // before each test).
+    }
+
+    virtual void TearDown() {
+        // Code here will be called immediately after each test (right
+        // before the destructor).
+    }
+};
+
+TEST_F(DbgContextTest, GetReadPixelBuffer)
+{
+    const unsigned int bufferSize = 512;
+    // test that it's allocating two buffers and swapping them
+    void * const buffer0 = dbg.GetReadPixelsBuffer(bufferSize);
+    ASSERT_NE((void *)NULL, buffer0);
+    for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) {
+        EXPECT_EQ(0, ((unsigned int *)buffer0)[i])
+        << "GetReadPixelsBuffer should allocate and zero";
+        ((unsigned int *)buffer0)[i] = i * 13;
+    }
+
+    void * const buffer1 = dbg.GetReadPixelsBuffer(bufferSize);
+    ASSERT_NE((void *)NULL, buffer1);
+    EXPECT_NE(buffer0, buffer1);
+    for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) {
+        EXPECT_EQ(0, ((unsigned int *)buffer1)[i])
+        << "GetReadPixelsBuffer should allocate and zero";
+        ((unsigned int *)buffer1)[i] = i * 17;
+    }
+
+    void * const buffer2 = dbg.GetReadPixelsBuffer(bufferSize);
+    EXPECT_EQ(buffer2, buffer0);
+    for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++)
+        EXPECT_EQ(i * 13, ((unsigned int *)buffer2)[i])
+        << "GetReadPixelsBuffer should swap buffers";
+
+    void * const buffer3 = dbg.GetReadPixelsBuffer(bufferSize);
+    EXPECT_EQ(buffer3, buffer1);
+    for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++)
+        EXPECT_EQ(i * 17, ((unsigned int *)buffer3)[i])
+        << "GetReadPixelsBuffer should swap buffers";
+
+    void * const buffer4 = dbg.GetReadPixelsBuffer(bufferSize);
+    EXPECT_NE(buffer3, buffer4);
+    EXPECT_EQ(buffer0, buffer2);
+    EXPECT_EQ(buffer1, buffer3);
+    EXPECT_EQ(buffer2, buffer4);
+
+    // it reallocs as necessary; 0 size may result in NULL
+    for (unsigned int i = 0; i < 42; i++) {
+        void * const buffer = dbg.GetReadPixelsBuffer(((i & 7)) << 20);
+        EXPECT_NE((void *)NULL, buffer)
+        << "should be able to get a variety of reasonable sizes";
+        EXPECT_TRUE(dbg.IsReadPixelBuffer(buffer));
+    }
+}
+
+TEST_F(DbgContextTest, CompressReadPixelBuffer)
+{
+    const unsigned int bufferSize = dbg.LZF_CHUNK_SIZE * 4 + 33;
+    std::string out;
+    unsigned char * buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        buffer[i] = i * 13;
+    dbg.CompressReadPixelBuffer(&out);
+    uint32_t decompSize = 0;
+    ASSERT_LT(12, out.length()); // at least written chunk header
+    ASSERT_EQ(bufferSize, *(uint32_t *)out.data())
+    << "total decompressed size should be as requested in GetReadPixelsBuffer";
+    for (unsigned int i = 4; i < out.length();) {
+        const uint32_t outSize = *(uint32_t *)(out.data() + i);
+        i += 4;
+        const uint32_t inSize = *(uint32_t *)(out.data() + i);
+        i += 4;
+        if (inSize == 0)
+            i += outSize; // chunk not compressed
+        else
+            i += inSize; // skip the actual compressed chunk
+        decompSize += outSize;
+    }
+    ASSERT_EQ(bufferSize, decompSize);
+    decompSize = 0;
+
+    unsigned char * decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+    ASSERT_EQ(decompSize, bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        EXPECT_EQ((unsigned char)(i * 13), decomp[i]) << "xor with 0 ref is identity";
+    free(decomp);
+
+    buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        buffer[i] = i * 13;
+    out.clear();
+    dbg.CompressReadPixelBuffer(&out);
+    decompSize = 0;
+    decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+    ASSERT_EQ(decompSize, bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        EXPECT_EQ(0, decomp[i]) << "xor with same ref is 0";
+    free(decomp);
+
+    buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        buffer[i] = i * 19;
+    out.clear();
+    dbg.CompressReadPixelBuffer(&out);
+    decompSize = 0;
+    decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+    ASSERT_EQ(decompSize, bufferSize);
+    for (unsigned int i = 0; i < bufferSize; i++)
+        EXPECT_EQ((unsigned char)(i * 13) ^ (unsigned char)(i * 19), decomp[i])
+        << "xor ref";
+    free(decomp);
+}
+
+TEST_F(DbgContextTest, UseProgram)
+{
+    static const GLuint _program = 74568;
+    static const struct Attribute {
+        const char * name;
+        GLint location;
+        GLint size;
+        GLenum type;
+    } _attributes [] = {
+        {"aaa", 2, 2, GL_FLOAT_VEC2},
+        {"bb", 6, 2, GL_FLOAT_MAT2},
+        {"c", 1, 1, GL_FLOAT},
+    };
+    static const unsigned int _attributeCount = sizeof(_attributes) / sizeof(*_attributes);
+    struct GL {
+        static void GetProgramiv(GLuint program, GLenum pname, GLint* params) {
+            EXPECT_EQ(_program, program);
+            ASSERT_NE((GLint *)NULL, params);
+            switch (pname) {
+            case GL_ACTIVE_ATTRIBUTES:
+                *params = _attributeCount;
+                return;
+            case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH:
+                *params = 4; // includes NULL terminator
+                return;
+            default:
+                ADD_FAILURE() << "not handled pname: " << pname;
+            }
+        }
+
+        static GLint GetAttribLocation(GLuint program, const GLchar* name) {
+            EXPECT_EQ(_program, program);
+            for (unsigned int i = 0; i < _attributeCount; i++)
+                if (!strcmp(name, _attributes[i].name))
+                    return _attributes[i].location;
+            ADD_FAILURE() << "unknown attribute name: " << name;
+            return -1;
+        }
+
+        static void GetActiveAttrib(GLuint program, GLuint index, GLsizei bufsize,
+                                    GLsizei* length, GLint* size, GLenum* type, GLchar* name) {
+            EXPECT_EQ(_program, program);
+            ASSERT_LT(index, _attributeCount);
+            const Attribute & att = _attributes[index];
+            ASSERT_GE(bufsize, strlen(att.name) + 1);
+            ASSERT_NE((GLint *)NULL, size);
+            ASSERT_NE((GLenum *)NULL, type);
+            ASSERT_NE((GLchar *)NULL, name);
+            strcpy(name, att.name);
+            if (length)
+                *length = strlen(name) + 1;
+            *size = att.size;
+            *type = att.type;
+        }
+    };
+    hooks.gl.glGetProgramiv = GL::GetProgramiv;
+    hooks.gl.glGetAttribLocation = GL::GetAttribLocation;
+    hooks.gl.glGetActiveAttrib = GL::GetActiveAttrib;
+    dbg.glUseProgram(_program);
+    EXPECT_EQ(10, dbg.maxAttrib);
+    dbg.glUseProgram(0);
+    EXPECT_EQ(0, dbg.maxAttrib);
+}
+}  // namespace
+
+int main(int argc, char **argv)
+{
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/opengl/libs/GLES2_dbg/test/test_server.cpp b/opengl/libs/GLES2_dbg/test/test_server.cpp
new file mode 100644
index 0000000..b6401e0
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_server.cpp
@@ -0,0 +1,251 @@
+/*
+ ** Copyright 2011, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "egl_tls.h"
+#include "hooks.h"
+
+namespace android
+{
+extern FILE * file;
+extern unsigned int MAX_FILE_SIZE;
+extern pthread_key_t dbgEGLThreadLocalStorageKey;
+};
+
+// tmpfile fails, so need to manually make a writable file first
+static const char * filePath = "/data/local/tmp/dump.gles2dbg";
+
+class ServerFileTest : public ::testing::Test
+{
+protected:
+    ServerFileTest() { }
+
+    virtual ~ServerFileTest() { }
+
+    virtual void SetUp() {
+        MAX_FILE_SIZE = 8 << 20;
+        ASSERT_EQ((FILE *)NULL, file);
+        file = fopen("/data/local/tmp/dump.gles2dbg", "wb+");
+        ASSERT_NE((FILE *)NULL, file) << "make sure file is writable: "
+        << filePath;
+    }
+
+    virtual void TearDown() {
+        ASSERT_NE((FILE *)NULL, file);
+        fclose(file);
+        file = NULL;
+    }
+
+    void Read(glesv2debugger::Message & msg) const {
+        msg.Clear();
+        uint32_t len = 0;
+        ASSERT_EQ(sizeof(len), fread(&len, 1, sizeof(len), file));
+        ASSERT_GT(len, 0u);
+        char * buffer = new char [len];
+        ASSERT_EQ(len, fread(buffer, 1, len, file));
+        msg.ParseFromArray(buffer, len);
+        delete buffer;
+    }
+
+    void CheckNoAvailable() {
+        const long pos = ftell(file);
+        fseek(file, 0, SEEK_END);
+        EXPECT_EQ(pos, ftell(file)) << "check no available";
+    }
+};
+
+TEST_F(ServerFileTest, Send)
+{
+    glesv2debugger::Message msg, cmd, read;
+    msg.set_context_id(1);
+    msg.set_function(msg.glFinish);
+    msg.set_expect_response(false);
+    msg.set_type(msg.BeforeCall);
+    rewind(file);
+    android::Send(msg, cmd);
+    rewind(file);
+    Read(read);
+    EXPECT_EQ(msg.context_id(), read.context_id());
+    EXPECT_EQ(msg.function(), read.function());
+    EXPECT_EQ(msg.expect_response(), read.expect_response());
+    EXPECT_EQ(msg.type(), read.type());
+}
+
+TEST_F(ServerFileTest, CreateDbgContext)
+{
+    gl_hooks_t hooks;
+    struct Constant {
+        GLenum pname;
+        GLint param;
+    };
+    static const Constant constants [] = {
+        {GL_MAX_VERTEX_ATTRIBS, 16},
+        {GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, 32},
+        {GL_IMPLEMENTATION_COLOR_READ_FORMAT, GL_RGBA},
+        {GL_IMPLEMENTATION_COLOR_READ_TYPE, GL_UNSIGNED_BYTE},
+    };
+    struct HookMock {
+        static void GetIntegerv(GLenum pname, GLint* params) {
+            ASSERT_TRUE(params != NULL);
+            for (unsigned int i = 0; i < sizeof(constants) / sizeof(*constants); i++)
+                if (pname == constants[i].pname) {
+                    *params = constants[i].param;
+                    return;
+                }
+            FAIL() << "GetIntegerv unknown pname: " << pname;
+        }
+        static GLenum GetError() {
+            return GL_NO_ERROR;
+        }
+    };
+    hooks.gl.glGetError = HookMock::GetError;
+    hooks.gl.glGetIntegerv = HookMock::GetIntegerv;
+    DbgContext * const dbg = CreateDbgContext(-1, 1, &hooks);
+    ASSERT_TRUE(dbg != NULL);
+    EXPECT_TRUE(dbg->vertexAttribs != NULL);
+
+    rewind(file);
+    glesv2debugger::Message read;
+    for (unsigned int i = 0; i < 2; i++) {
+        Read(read);
+        EXPECT_EQ(reinterpret_cast<int>(dbg), read.context_id());
+        EXPECT_FALSE(read.expect_response());
+        EXPECT_EQ(read.Response, read.type());
+        EXPECT_EQ(read.SETPROP, read.function());
+        EXPECT_EQ(read.GLConstant, read.prop());
+        GLint expectedConstant = 0;
+        HookMock::GetIntegerv(read.arg0(), &expectedConstant);
+        EXPECT_EQ(expectedConstant, read.arg1());
+    }
+    CheckNoAvailable();
+    DestroyDbgContext(dbg);
+}
+
+void * glNoop()
+{
+    return 0;
+}
+
+class ServerFileContextTest : public ServerFileTest
+{
+protected:
+    tls_t tls;
+    gl_hooks_t hooks;
+
+    ServerFileContextTest() { }
+
+    virtual ~ServerFileContextTest() { }
+
+    virtual void SetUp() {
+        ServerFileTest::SetUp();
+
+        if (dbgEGLThreadLocalStorageKey == -1)
+            pthread_key_create(&dbgEGLThreadLocalStorageKey, NULL);
+        ASSERT_NE(-1, dbgEGLThreadLocalStorageKey);
+        tls.dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE);
+        ASSERT_NE((void *)NULL, tls.dbg);
+        pthread_setspecific(dbgEGLThreadLocalStorageKey, &tls);
+        for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
+            ((void **)&hooks)[i] = reinterpret_cast<void *>(glNoop);
+    }
+
+    virtual void TearDown() {
+        ServerFileTest::TearDown();
+    }
+};
+
+TEST_F(ServerFileContextTest, MessageLoop)
+{
+    static const int arg0 = 45;
+    static const float arg7 = -87.2331f;
+    static const int arg8 = -3;
+    static const int * ret = reinterpret_cast<int *>(870);
+
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            msg.set_arg0(arg0);
+            msg.set_arg7((int &)arg7);
+            msg.set_arg8(arg8);
+            return ret;
+        }
+    } caller;
+    const int contextId = reinterpret_cast<int>(tls.dbg);
+    glesv2debugger::Message msg, read;
+
+    EXPECT_EQ(ret, MessageLoop(caller, msg, msg.glFinish));
+
+    rewind(file);
+    Read(read);
+    EXPECT_EQ(contextId, read.context_id());
+    EXPECT_EQ(read.glFinish, read.function());
+    EXPECT_EQ(false, read.expect_response());
+    EXPECT_EQ(read.BeforeCall, read.type());
+
+    Read(read);
+    EXPECT_EQ(contextId, read.context_id());
+    EXPECT_EQ(read.glFinish, read.function());
+    EXPECT_EQ(false, read.expect_response());
+    EXPECT_EQ(read.AfterCall, read.type());
+    EXPECT_TRUE(read.has_time());
+    EXPECT_EQ(arg0, read.arg0());
+    const int readArg7 = read.arg7();
+    EXPECT_EQ(arg7, (float &)readArg7);
+    EXPECT_EQ(arg8, read.arg8());
+
+    const long pos = ftell(file);
+    fseek(file, 0, SEEK_END);
+    EXPECT_EQ(pos, ftell(file))
+    << "should only write the BeforeCall and AfterCall messages";
+}
+
+TEST_F(ServerFileContextTest, DisableEnableVertexAttribArray)
+{
+    Debug_glEnableVertexAttribArray(tls.dbg->MAX_VERTEX_ATTRIBS + 2); // should just ignore invalid index
+
+    glesv2debugger::Message read;
+    rewind(file);
+    Read(read);
+    EXPECT_EQ(read.glEnableVertexAttribArray, read.function());
+    EXPECT_EQ(tls.dbg->MAX_VERTEX_ATTRIBS + 2, read.arg0());
+    Read(read);
+
+    rewind(file);
+    Debug_glDisableVertexAttribArray(tls.dbg->MAX_VERTEX_ATTRIBS + 4); // should just ignore invalid index
+    rewind(file);
+    Read(read);
+    Read(read);
+
+    for (unsigned int i = 0; i < tls.dbg->MAX_VERTEX_ATTRIBS; i += 5) {
+        rewind(file);
+        Debug_glEnableVertexAttribArray(i);
+        EXPECT_TRUE(tls.dbg->vertexAttribs[i].enabled);
+        rewind(file);
+        Read(read);
+        EXPECT_EQ(read.glEnableVertexAttribArray, read.function());
+        EXPECT_EQ(i, read.arg0());
+        Read(read);
+
+        rewind(file);
+        Debug_glDisableVertexAttribArray(i);
+        EXPECT_FALSE(tls.dbg->vertexAttribs[i].enabled);
+        rewind(file);
+        Read(read);
+        EXPECT_EQ(read.glDisableVertexAttribArray, read.function());
+        EXPECT_EQ(i, read.arg0());
+        Read(read);
+    }
+}
diff --git a/opengl/libs/GLES2_dbg/test/test_socket.cpp b/opengl/libs/GLES2_dbg/test/test_socket.cpp
new file mode 100644
index 0000000..617292e
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_socket.cpp
@@ -0,0 +1,474 @@
+/*
+ ** Copyright 2011, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "egl_tls.h"
+#include "hooks.h"
+
+namespace android
+{
+extern int serverSock, clientSock;
+extern pthread_key_t dbgEGLThreadLocalStorageKey;
+};
+
+void * glNoop();
+
+class SocketContextTest : public ::testing::Test
+{
+protected:
+    tls_t tls;
+    gl_hooks_t hooks;
+    int sock;
+    char * buffer;
+    unsigned int bufferSize;
+
+    SocketContextTest() : sock(-1) {
+    }
+
+    virtual ~SocketContextTest() {
+    }
+
+    virtual void SetUp() {
+        if (dbgEGLThreadLocalStorageKey == -1)
+            pthread_key_create(&dbgEGLThreadLocalStorageKey, NULL);
+        ASSERT_NE(-1, dbgEGLThreadLocalStorageKey);
+        tls.dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE);
+        ASSERT_TRUE(tls.dbg != NULL);
+        pthread_setspecific(dbgEGLThreadLocalStorageKey, &tls);
+        for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
+            ((void **)&hooks)[i] = (void *)glNoop;
+
+        int socks[2] = {-1, -1};
+        ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socks));
+        clientSock = socks[0];
+        sock = socks[1];
+
+        bufferSize = 128;
+        buffer = new char [128];
+        ASSERT_NE((char *)NULL, buffer);
+    }
+
+    virtual void TearDown() {
+        close(sock);
+        close(clientSock);
+        clientSock = -1;
+        delete buffer;
+    }
+
+    void Write(glesv2debugger::Message & msg) const {
+        msg.set_context_id((int)tls.dbg);
+        msg.set_type(msg.Response);
+        ASSERT_TRUE(msg.has_context_id());
+        ASSERT_TRUE(msg.has_function());
+        ASSERT_TRUE(msg.has_type());
+        ASSERT_TRUE(msg.has_expect_response());
+        static std::string str;
+        msg.SerializeToString(&str);
+        const uint32_t len = str.length();
+        ASSERT_EQ(sizeof(len), send(sock, &len, sizeof(len), 0));
+        ASSERT_EQ(str.length(), send(sock, str.data(), str.length(), 0));
+    }
+
+    void Read(glesv2debugger::Message & msg) {
+        int available = 0;
+        ASSERT_EQ(0, ioctl(sock, FIONREAD, &available));
+        ASSERT_GT(available, 0);
+        uint32_t len = 0;
+        ASSERT_EQ(sizeof(len), recv(sock, &len, sizeof(len), 0));
+        if (len > bufferSize) {
+            bufferSize = len;
+            buffer = new char[bufferSize];
+            ASSERT_TRUE(buffer != NULL);
+        }
+        ASSERT_EQ(len, recv(sock, buffer, len, 0));
+        msg.Clear();
+        msg.ParseFromArray(buffer, len);
+        ASSERT_TRUE(msg.has_context_id());
+        ASSERT_TRUE(msg.has_function());
+        ASSERT_TRUE(msg.has_type());
+        ASSERT_TRUE(msg.has_expect_response());
+    }
+
+    void CheckNoAvailable() {
+        int available = 0;
+        ASSERT_EQ(0, ioctl(sock, FIONREAD, &available));
+        ASSERT_EQ(available, 0);
+    }
+};
+
+TEST_F(SocketContextTest, MessageLoopSkip)
+{
+    static const int arg0 = 45;
+    static const float arg7 = -87.2331f;
+    static const int arg8 = -3;
+    static const int * ret = (int *)870;
+
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            msg.set_arg0(arg0);
+            msg.set_arg7((int &)arg7);
+            msg.set_arg8(arg8);
+            return ret;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    tls.dbg->expectResponse.Bit(msg.glFinish, true);
+
+    cmd.set_function(cmd.SKIP);
+    cmd.set_expect_response(false);
+    Write(cmd);
+
+    EXPECT_NE(ret, MessageLoop(caller, msg, msg.glFinish));
+
+    Read(read);
+    EXPECT_EQ(read.glFinish, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+    EXPECT_NE(arg0, read.arg0());
+    EXPECT_NE((int &)arg7, read.arg7());
+    EXPECT_NE(arg8, read.arg8());
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopContinue)
+{
+    static const int arg0 = GL_FRAGMENT_SHADER;
+    static const int ret = -342;
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            msg.set_ret(ret);
+            return (int *)ret;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    tls.dbg->expectResponse.Bit(msg.glCreateShader, true);
+
+    cmd.set_function(cmd.CONTINUE);
+    cmd.set_expect_response(false); // MessageLoop should automatically skip after continue
+    Write(cmd);
+
+    msg.set_arg0(arg0);
+    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateShader));
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+    EXPECT_EQ(arg0, read.arg0());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+    EXPECT_EQ(ret, read.ret());
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopGenerateCall)
+{
+    static const int ret = -342;
+    static unsigned int createShader, createProgram;
+    createShader = 0;
+    createProgram = 0;
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            const int r = (int)_c->glCreateProgram();
+            msg.set_ret(r);
+            return (int *)r;
+        }
+        static GLuint CreateShader(const GLenum type) {
+            createShader++;
+            return type;
+        }
+        static GLuint CreateProgram() {
+            createProgram++;
+            return ret;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    hooks.gl.glCreateShader = caller.CreateShader;
+    hooks.gl.glCreateProgram = caller.CreateProgram;
+    tls.dbg->expectResponse.Bit(msg.glCreateProgram, true);
+
+    cmd.set_function(cmd.glCreateShader);
+    cmd.set_arg0(GL_FRAGMENT_SHADER);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.Clear();
+    cmd.set_function(cmd.CONTINUE);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.set_function(cmd.glCreateShader);
+    cmd.set_arg0(GL_VERTEX_SHADER);
+    cmd.set_expect_response(false); // MessageLoop should automatically skip afterwards
+    Write(cmd);
+
+    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateProgram));
+
+    Read(read);
+    EXPECT_EQ(read.glCreateProgram, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterGeneratedCall, read.type());
+    EXPECT_EQ(GL_FRAGMENT_SHADER, read.ret());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateProgram, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+    EXPECT_EQ(ret, read.ret());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterGeneratedCall, read.type());
+    EXPECT_EQ(GL_VERTEX_SHADER, read.ret());
+
+    EXPECT_EQ(2, createShader);
+    EXPECT_EQ(1, createProgram);
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopSetProp)
+{
+    static const int ret = -342;
+    static unsigned int createShader, createProgram;
+    createShader = 0;
+    createProgram = 0;
+    struct Caller : public FunctionCall {
+        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+            const int r = (int)_c->glCreateProgram();
+            msg.set_ret(r);
+            return (int *)r;
+        }
+        static GLuint CreateShader(const GLenum type) {
+            createShader++;
+            return type;
+        }
+        static GLuint CreateProgram() {
+            createProgram++;
+            return ret;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    hooks.gl.glCreateShader = caller.CreateShader;
+    hooks.gl.glCreateProgram = caller.CreateProgram;
+    tls.dbg->expectResponse.Bit(msg.glCreateProgram, false);
+
+    cmd.set_function(cmd.SETPROP);
+    cmd.set_prop(cmd.ExpectResponse);
+    cmd.set_arg0(cmd.glCreateProgram);
+    cmd.set_arg1(true);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.Clear();
+    cmd.set_function(cmd.glCreateShader);
+    cmd.set_arg0(GL_FRAGMENT_SHADER);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.set_function(cmd.SETPROP);
+    cmd.set_prop(cmd.CaptureDraw);
+    cmd.set_arg0(819);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.Clear();
+    cmd.set_function(cmd.CONTINUE);
+    cmd.set_expect_response(true);
+    Write(cmd);
+
+    cmd.set_function(cmd.glCreateShader);
+    cmd.set_arg0(GL_VERTEX_SHADER);
+    cmd.set_expect_response(false); // MessageLoop should automatically skip afterwards
+    Write(cmd);
+
+    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateProgram));
+
+    EXPECT_TRUE(tls.dbg->expectResponse.Bit(msg.glCreateProgram));
+    EXPECT_EQ(819, tls.dbg->captureDraw);
+
+    Read(read);
+    EXPECT_EQ(read.glCreateProgram, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterGeneratedCall, read.type());
+    EXPECT_EQ(GL_FRAGMENT_SHADER, read.ret());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateProgram, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+    EXPECT_EQ(ret, read.ret());
+
+    Read(read);
+    EXPECT_EQ(read.glCreateShader, read.function());
+    EXPECT_EQ(read.AfterGeneratedCall, read.type());
+    EXPECT_EQ(GL_VERTEX_SHADER, read.ret());
+
+    EXPECT_EQ(2, createShader);
+    EXPECT_EQ(1, createProgram);
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, TexImage2D)
+{
+    static const GLenum _target = GL_TEXTURE_2D;
+    static const GLint _level = 1, _internalformat = GL_RGBA;
+    static const GLsizei _width = 2, _height = 2;
+    static const GLint _border = 333;
+    static const GLenum _format = GL_RGB, _type = GL_UNSIGNED_SHORT_5_6_5;
+    static const short _pixels [_width * _height] = {11, 22, 33, 44};
+    static unsigned int texImage2D;
+    texImage2D = 0;
+
+    struct Caller {
+        static void TexImage2D(GLenum target, GLint level, GLint internalformat,
+                               GLsizei width, GLsizei height, GLint border,
+                               GLenum format, GLenum type, const GLvoid* pixels) {
+            EXPECT_EQ(_target, target);
+            EXPECT_EQ(_level, level);
+            EXPECT_EQ(_internalformat, internalformat);
+            EXPECT_EQ(_width, width);
+            EXPECT_EQ(_height, height);
+            EXPECT_EQ(_border, border);
+            EXPECT_EQ(_format, format);
+            EXPECT_EQ(_type, type);
+            EXPECT_EQ(0, memcmp(_pixels, pixels, sizeof(_pixels)));
+            texImage2D++;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    hooks.gl.glTexImage2D = caller.TexImage2D;
+    tls.dbg->expectResponse.Bit(msg.glTexImage2D, false);
+
+    Debug_glTexImage2D(_target, _level, _internalformat, _width, _height, _border,
+                       _format, _type, _pixels);
+    EXPECT_EQ(1, texImage2D);
+
+    Read(read);
+    EXPECT_EQ(read.glTexImage2D, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+    EXPECT_EQ(_target, read.arg0());
+    EXPECT_EQ(_level, read.arg1());
+    EXPECT_EQ(_internalformat, read.arg2());
+    EXPECT_EQ(_width, read.arg3());
+    EXPECT_EQ(_height, read.arg4());
+    EXPECT_EQ(_border, read.arg5());
+    EXPECT_EQ(_format, read.arg6());
+    EXPECT_EQ(_type, read.arg7());
+
+    EXPECT_TRUE(read.has_data());
+    uint32_t dataLen = 0;
+    const unsigned char * data = tls.dbg->Decompress(read.data().data(),
+                                 read.data().length(), &dataLen);
+    EXPECT_EQ(sizeof(_pixels), dataLen);
+    if (sizeof(_pixels) == dataLen)
+        EXPECT_EQ(0, memcmp(_pixels, data, sizeof(_pixels)));
+
+    Read(read);
+    EXPECT_EQ(read.glTexImage2D, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+
+    CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, CopyTexImage2D)
+{
+    static const GLenum _target = GL_TEXTURE_2D;
+    static const GLint _level = 1, _internalformat = GL_RGBA;
+    static const GLint _x = 9, _y = 99;
+    static const GLsizei _width = 2, _height = 3;
+    static const GLint _border = 333;
+    static const int _pixels [_width * _height] = {11, 22, 33, 44, 55, 66};
+    static unsigned int copyTexImage2D, readPixels;
+    copyTexImage2D = 0, readPixels = 0;
+
+    struct Caller {
+        static void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat,
+                                   GLint x, GLint y, GLsizei width, GLsizei height, GLint border) {
+            EXPECT_EQ(_target, target);
+            EXPECT_EQ(_level, level);
+            EXPECT_EQ(_internalformat, internalformat);
+            EXPECT_EQ(_x, x);
+            EXPECT_EQ(_y, y);
+            EXPECT_EQ(_width, width);
+            EXPECT_EQ(_height, height);
+            EXPECT_EQ(_border, border);
+            copyTexImage2D++;
+        }
+        static void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
+                               GLenum format, GLenum type, GLvoid* pixels) {
+            EXPECT_EQ(_x, x);
+            EXPECT_EQ(_y, y);
+            EXPECT_EQ(_width, width);
+            EXPECT_EQ(_height, height);
+            EXPECT_EQ(GL_RGBA, format);
+            EXPECT_EQ(GL_UNSIGNED_BYTE, type);
+            ASSERT_TRUE(pixels != NULL);
+            memcpy(pixels, _pixels, sizeof(_pixels));
+            readPixels++;
+        }
+    } caller;
+    glesv2debugger::Message msg, read, cmd;
+    hooks.gl.glCopyTexImage2D = caller.CopyTexImage2D;
+    hooks.gl.glReadPixels = caller.ReadPixels;
+    tls.dbg->expectResponse.Bit(msg.glCopyTexImage2D, false);
+
+    Debug_glCopyTexImage2D(_target, _level, _internalformat, _x, _y, _width, _height,
+                           _border);
+    ASSERT_EQ(1, copyTexImage2D);
+    ASSERT_EQ(1, readPixels);
+
+    Read(read);
+    EXPECT_EQ(read.glCopyTexImage2D, read.function());
+    EXPECT_EQ(read.BeforeCall, read.type());
+    EXPECT_EQ(_target, read.arg0());
+    EXPECT_EQ(_level, read.arg1());
+    EXPECT_EQ(_internalformat, read.arg2());
+    EXPECT_EQ(_x, read.arg3());
+    EXPECT_EQ(_y, read.arg4());
+    EXPECT_EQ(_width, read.arg5());
+    EXPECT_EQ(_height, read.arg6());
+    EXPECT_EQ(_border, read.arg7());
+
+    EXPECT_TRUE(read.has_data());
+    EXPECT_EQ(read.ReferencedImage, read.data_type());
+    EXPECT_EQ(GL_RGBA, read.pixel_format());
+    EXPECT_EQ(GL_UNSIGNED_BYTE, read.pixel_type());
+    uint32_t dataLen = 0;
+    unsigned char * const data = tls.dbg->Decompress(read.data().data(),
+                                 read.data().length(), &dataLen);
+    ASSERT_EQ(sizeof(_pixels), dataLen);
+    for (unsigned i = 0; i < sizeof(_pixels) / sizeof(*_pixels); i++)
+        EXPECT_EQ(_pixels[i], ((const int *)data)[i]) << "xor with 0 ref is identity";
+    free(data);
+
+    Read(read);
+    EXPECT_EQ(read.glCopyTexImage2D, read.function());
+    EXPECT_EQ(read.AfterCall, read.type());
+
+    CheckNoAvailable();
+}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 9938a8b..879f1a8 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -26,16 +26,18 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
 
-import com.android.internal.view.BaseSurfaceHolder;
 import com.android.internal.view.RootViewSurfaceTaker;
 import com.android.internal.view.StandaloneActionMode;
 import com.android.internal.view.menu.ActionMenuView;
 import com.android.internal.view.menu.ContextMenuBuilder;
+import com.android.internal.view.menu.ListMenuPresenter;
+import com.android.internal.view.menu.IconMenuPresenter;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuDialogHelper;
 import com.android.internal.view.menu.MenuItemImpl;
 import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.MenuPresenter;
 import com.android.internal.view.menu.SubMenuBuilder;
 import com.android.internal.widget.ActionBarContextView;
 import com.android.internal.widget.ActionBarView;
@@ -75,7 +77,6 @@
 import android.view.ViewStub;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.View.MeasureSpec;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Animation;
@@ -125,6 +126,8 @@
     private TextView mTitleView;
     
     private ActionBarView mActionBar;
+    private ActionMenuPresenterCallback mActionMenuPresenterCallback;
+    private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
 
     private DrawableFeatureState[] mDrawables;
 
@@ -167,7 +170,6 @@
     
     private ContextMenuBuilder mContextMenu;
     private MenuDialogHelper mContextMenuHelper;
-    private ActionButtonSubmenu mActionButtonPopup;
     private boolean mClosingActionMenu;
 
     private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
@@ -342,7 +344,12 @@
                         return false;
                     }
                 }
-                // Call callback, and return if it doesn't want to display menu
+
+                // Call callback, and return if it doesn't want to display menu.
+
+                // Creating the panel menu will involve a lot of manipulation;
+                // don't dispatch change events to presenters until we're done.
+                st.menu.stopDispatchingItemsChanged();
                 if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) {
                     // Ditch the menu created above
                     st.menu = null;
@@ -353,14 +360,23 @@
                 st.refreshMenuContent = false;
 
                 if (mActionBar != null) {
-                    mActionBar.setMenu(st.menu);
+                    if (mActionMenuPresenterCallback == null) {
+                        mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
+                    }
+                    mActionBar.setMenu(st.menu, mActionMenuPresenterCallback);
                 }
             }
 
             // Callback and return if the callback does not want to show the menu
+
+            // Preparing the panel menu can involve a lot of manipulation;
+            // don't dispatch change events to presenters until we're done.
+            st.menu.stopDispatchingItemsChanged();
             if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {
+                st.menu.startDispatchingItemsChanged();
                 return false;
             }
+            st.menu.startDispatchingItemsChanged();
 
             // Set the proper keymap
             KeyCharacterMap kmap = KeyCharacterMap.load(
@@ -383,12 +399,15 @@
         if (mActionBar == null) {
             PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
             if ((st != null) && (st.menu != null)) {
-                final MenuBuilder menuBuilder = (MenuBuilder) st.menu;
-
                 if (st.isOpen) {
                     // Freeze state
                     final Bundle state = new Bundle();
-                    menuBuilder.saveHierarchyState(state);
+                    if (st.iconMenuPresenter != null) {
+                        st.iconMenuPresenter.saveHierarchyState(state);
+                    }
+                    if (st.expandedMenuPresenter != null) {
+                        st.expandedMenuPresenter.saveHierarchyState(state);
+                    }
 
                     // Remove the menu views since they need to be recreated
                     // according to the new configuration
@@ -398,7 +417,12 @@
                     reopenMenu(false);
 
                     // Restore state
-                    menuBuilder.restoreHierarchyState(state);
+                    if (st.iconMenuPresenter != null) {
+                        st.iconMenuPresenter.restoreHierarchyState(state);
+                    }
+                    if (st.expandedMenuPresenter != null) {
+                        st.expandedMenuPresenter.restoreHierarchyState(state);
+                    }
 
                 } else {
                     // Clear menu views so on next menu opening, it will use
@@ -418,8 +442,8 @@
 
         // Causes the decor view to be recreated
         st.refreshDecorView = true;
-
-        ((MenuBuilder) st.menu).clearMenuViews();
+        
+        st.clearMenuPresenters();
     }
 
     @Override
@@ -563,6 +587,12 @@
      */
     public final void closePanel(PanelFeatureState st, boolean doCallback) {
         // System.out.println("Close panel: isOpen=" + st.isOpen);
+        if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
+                mActionBar != null && mActionBar.isOverflowMenuShowing()) {
+            checkCloseActionMenu(st.menu);
+            return;
+        }
+
         final ViewManager wm = getWindowManager();
         if ((wm != null) && st.isOpen) {
             if (st.decorView != null) {
@@ -573,10 +603,8 @@
             if (doCallback) {
                 callOnPanelClosed(st.featureId, st, null);
             }
-        } else if (st.featureId == FEATURE_OPTIONS_PANEL && doCallback &&
-                mActionBar != null) {
-            checkCloseActionMenu(st.menu);
         }
+
         st.isPrepared = false;
         st.isHandled = false;
         st.isOpen = false;
@@ -602,17 +630,10 @@
             return;
         }
 
-        boolean closed = false;
         mClosingActionMenu = true;
-        if (mActionBar.isOverflowMenuOpen() && mActionBar.hideOverflowMenu()) {
-            closed = true;
-        }
-        if (mActionButtonPopup != null) {
-            mActionButtonPopup.dismiss();
-            closed = true;
-        }
+        mActionBar.dismissPopupMenus();
         Callback cb = getCallback();
-        if (cb != null && closed && !isDestroyed()) {
+        if (cb != null && !isDestroyed()) {
             cb.onPanelClosed(FEATURE_ACTION_BAR, menu);
         }
         mClosingActionMenu = false;
@@ -849,54 +870,6 @@
         return false;
     }
 
-    public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
-        final PanelFeatureState panel = findMenuPanel(menu);
-        if (panel != null) {
-            // Close the panel and only do the callback if the menu is being
-            // closed
-            // completely, not if opening a sub menu
-            closePanel(panel, allMenusAreClosing);
-        }
-    }
-
-    public void onCloseSubMenu(SubMenuBuilder subMenu) {
-        final Menu parentMenu = subMenu.getRootMenu();
-        final PanelFeatureState panel = findMenuPanel(parentMenu);
-
-        // Callback
-        if (panel != null) {
-            callOnPanelClosed(panel.featureId, panel, parentMenu);
-            closePanel(panel, true);
-        }
-    }
-
-    public boolean onSubMenuSelected(final SubMenuBuilder subMenu) {
-        if (!subMenu.hasVisibleItems()) {
-            return true;
-        }
-
-        final Menu parentMenu = subMenu.getRootMenu();
-        final PanelFeatureState panel = findMenuPanel(parentMenu);
-
-        if (hasFeature(FEATURE_ACTION_BAR) && panel.featureId == FEATURE_OPTIONS_PANEL) {
-            mDecor.post(new Runnable() {
-                public void run() {
-                    mActionButtonPopup = new ActionButtonSubmenu(getContext(), subMenu);
-                    mActionButtonPopup.show();
-                    Callback cb = getCallback();
-                    if (cb != null && !isDestroyed()) {
-                        cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
-                    }
-                }
-            });
-        } else {
-            // The window manager will give us a valid window token
-            new MenuDialogHelper(subMenu).show(null);
-        }
-
-        return true;
-    }
-
     public void onMenuModeChange(MenuBuilder menu) {
         reopenMenu(true);
     }
@@ -978,23 +951,28 @@
      * @return Whether the initialization was successful.
      */
     protected boolean initializePanelContent(PanelFeatureState st) {
-
         if (st.createdPanelView != null) {
             st.shownPanelView = st.createdPanelView;
             return true;
         }
 
-        final MenuBuilder menu = (MenuBuilder)st.menu;
-        if (menu == null) {
+        if (st.menu == null) {
             return false;
         }
 
-        st.shownPanelView = menu.getMenuView((st.isInExpandedMode) ? MenuBuilder.TYPE_EXPANDED
-                : MenuBuilder.TYPE_ICON, st.decorView);
+        if (mPanelMenuPresenterCallback == null) {
+            mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
+        }
+
+        MenuView menuView = st.isInExpandedMode
+                ? st.getExpandedMenuView(mPanelMenuPresenterCallback)
+                : st.getIconMenuView(mPanelMenuPresenterCallback);
+
+        st.shownPanelView = (View) menuView;
 
         if (st.shownPanelView != null) {
             // Use the menu View's default animations if it has any
-            final int defaultAnimations = ((MenuView) st.shownPanelView).getWindowAnimations();
+            final int defaultAnimations = menuView.getWindowAnimations();
             if (defaultAnimations != 0) {
                 st.windowAnimations = defaultAnimations;
             }
@@ -1581,6 +1559,54 @@
         }
     }
 
+    private class PanelMenuPresenterCallback implements MenuPresenter.Callback {
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+            final Menu parentMenu = menu.getRootMenu();
+            final boolean isSubMenu = parentMenu != menu;
+            final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu);
+            if (panel != null) {
+                if (isSubMenu) {
+                    callOnPanelClosed(panel.featureId, panel, parentMenu);
+                    closePanel(panel, true);
+                } else {
+                    // Close the panel and only do the callback if the menu is being
+                    // closed completely, not if opening a sub menu
+                    closePanel(panel, allMenusAreClosing);
+                }
+            }
+        }
+
+        @Override
+        public boolean onOpenSubMenu(MenuBuilder subMenu) {
+            if (subMenu == null && hasFeature(FEATURE_ACTION_BAR)) {
+                Callback cb = getCallback();
+                if (cb != null && !isDestroyed()) {
+                    cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
+                }
+            }
+
+            return true;
+        }
+    }
+
+    private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
+        @Override
+        public boolean onOpenSubMenu(MenuBuilder subMenu) {
+            Callback cb = getCallback();
+            if (cb != null) {
+                cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+            checkCloseActionMenu(menu);
+        }
+    }
+
     private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
         /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
 
@@ -2190,11 +2216,8 @@
                 cb.onDetachedFromWindow();
             }
 
-            if (mActionButtonPopup != null) {
-                if (mActionButtonPopup.isShowing()) {
-                    mActionButtonPopup.dismiss();
-                }
-                mActionButtonPopup = null;
+            if (mActionBar != null) {
+                mActionBar.dismissPopupMenus();
             }
 
             if (mActionModePopup != null) {
@@ -2207,14 +2230,6 @@
         }
 
         @Override
-        protected void onConfigurationChanged(Configuration newConfig) {
-            if (mActionButtonPopup != null) {
-                mActionButtonPopup.dismiss();
-                post(mActionButtonPopup);
-            }
-        }
-
-        @Override
         public void onCloseSystemDialogs(String reason) {
             if (mFeatureId >= 0) {
                 closeAllPanels();
@@ -2914,7 +2929,10 @@
         View shownPanelView;
 
         /** Use {@link #setMenu} to set this. */
-        Menu menu;
+        MenuBuilder menu;
+
+        IconMenuPresenter iconMenuPresenter;
+        ListMenuPresenter expandedMenuPresenter;
 
         /**
          * Whether the panel has been prepared (see
@@ -2958,6 +2976,18 @@
             refreshDecorView = false;
         }
 
+        /**
+         * Unregister and free attached MenuPresenters. They will be recreated as needed.
+         */
+        public void clearMenuPresenters() {
+            if (menu != null) {
+                menu.removeMenuPresenter(iconMenuPresenter);
+                menu.removeMenuPresenter(expandedMenuPresenter);
+            }
+            iconMenuPresenter = null;
+            expandedMenuPresenter = null;
+        }
+
         void setStyle(Context context) {
             TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
             background = a.getResourceId(
@@ -2969,13 +2999,56 @@
             a.recycle();
         }
 
-        void setMenu(Menu menu) {
+        void setMenu(MenuBuilder menu) {
             this.menu = menu;
+        }
 
-            if (frozenMenuState != null) {
-                ((MenuBuilder) menu).restoreHierarchyState(frozenMenuState);
+        MenuView getExpandedMenuView(MenuPresenter.Callback cb) {
+            if (menu == null) return null;
+
+            getIconMenuView(cb); // Need this initialized to know where our offset goes
+
+            boolean init = false;
+            if (expandedMenuPresenter == null) {
+                expandedMenuPresenter = new ListMenuPresenter(
+                        com.android.internal.R.layout.list_menu_item_layout,
+                        com.android.internal.R.style.Theme_ExpandedMenu);
+                expandedMenuPresenter.setCallback(cb);
+                menu.addMenuPresenter(expandedMenuPresenter);
+                init = true;
+            }
+
+            expandedMenuPresenter.setItemIndexOffset(iconMenuPresenter.getNumActualItemsShown());
+            MenuView result = expandedMenuPresenter.getMenuView(decorView);
+
+            if (init && frozenMenuState != null) {
+                expandedMenuPresenter.restoreHierarchyState(frozenMenuState);
+                // Once we initialize the expanded menu we're done with the frozen state
+                // since we will have also restored any icon menu state.
                 frozenMenuState = null;
             }
+
+            return result;
+        }
+
+        MenuView getIconMenuView(MenuPresenter.Callback cb) {
+            if (menu == null) return null;
+
+            boolean init = false;
+            if (iconMenuPresenter == null) {
+                iconMenuPresenter = new IconMenuPresenter();
+                iconMenuPresenter.setCallback(cb);
+                menu.addMenuPresenter(iconMenuPresenter);
+                init = true;
+            }
+
+            MenuView result = iconMenuPresenter.getMenuView(decorView);
+
+            if (init && frozenMenuState != null) {
+                iconMenuPresenter.restoreHierarchyState(frozenMenuState);
+            }
+
+            return result;
         }
 
         Parcelable onSaveInstanceState() {
@@ -2986,7 +3059,12 @@
 
             if (menu != null) {
                 savedState.menuState = new Bundle();
-                ((MenuBuilder) menu).saveHierarchyState(savedState.menuState);
+                if (iconMenuPresenter != null) {
+                    iconMenuPresenter.saveHierarchyState(savedState.menuState);
+                }
+                if (expandedMenuPresenter != null) {
+                    expandedMenuPresenter.saveHierarchyState(savedState.menuState);
+                }
             }
 
             return savedState;
@@ -3127,44 +3205,4 @@
     void sendCloseSystemWindows(String reason) {
         PhoneWindowManager.sendCloseSystemWindows(getContext(), reason);
     }
-
-    private class ActionButtonSubmenu extends MenuPopupHelper implements Runnable {
-        private SubMenuBuilder mSubMenu;
-
-        public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
-            super(context, subMenu);
-            mSubMenu = subMenu;
-
-            MenuBuilder parentMenu = subMenu.getRootMenu();
-            MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
-            if (!item.isActionButton()) {
-                // Give a reasonable anchor to nested submenus.
-                ActionMenuView amv = (ActionMenuView) parentMenu.getMenuView(
-                        MenuBuilder.TYPE_ACTION_BUTTON, null);
-
-                View anchor = amv.getOverflowButton();
-                if (anchor == null) {
-                    anchor = amv;
-                }
-                setAnchorView(anchor);
-            }
-        }
-
-        @Override
-        public void onDismiss() {
-            super.onDismiss();
-            mSubMenu.getCallback().onCloseSubMenu(mSubMenu);
-            mActionButtonPopup = null;
-        }
-
-        @Override
-        public void run() {
-            if (tryShow()) {
-                Callback cb = getCallback();
-                if (cb != null && !isDestroyed()) {
-                    cb.onMenuOpened(FEATURE_ACTION_BAR, mSubMenu);
-                }
-            }
-        }
-    }
 }
diff --git a/services/input/Android.mk b/services/input/Android.mk
index f9f8623..836c081 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -23,7 +23,6 @@
     InputReader.cpp \
     InputWindow.cpp \
     PointerController.cpp \
-    SpotController.cpp \
     SpriteController.cpp
 
 LOCAL_SHARED_LIBRARIES := \
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 6db445e..98b3526 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -36,7 +36,6 @@
 // Log debug messages about gesture detection.
 #define DEBUG_GESTURES 0
 
-
 #include "InputReader.h"
 
 #include <cutils/log.h>
@@ -71,23 +70,52 @@
 
 // Tap gesture delay time.
 // The time between down and up must be less than this to be considered a tap.
-static const nsecs_t TAP_INTERVAL = 100 * 1000000; // 100 ms
+static const nsecs_t TAP_INTERVAL = 150 * 1000000; // 150 ms
+
+// Tap drag gesture delay time.
+// The time between up and the next up must be greater than this to be considered a
+// drag.  Otherwise, the previous tap is finished and a new tap begins.
+static const nsecs_t TAP_DRAG_INTERVAL = 150 * 1000000; // 150 ms
 
 // The distance in pixels that the pointer is allowed to move from initial down
 // to up and still be called a tap.
-static const float TAP_SLOP = 5.0f; // 5 pixels
+static const float TAP_SLOP = 10.0f; // 10 pixels
 
-// The transition from INDETERMINATE_MULTITOUCH to SWIPE or FREEFORM gesture mode is made when
-// all of the pointers have traveled this number of pixels from the start point.
-static const float MULTITOUCH_MIN_TRAVEL = 5.0f;
+// Time after the first touch points go down to settle on an initial centroid.
+// This is intended to be enough time to handle cases where the user puts down two
+// fingers at almost but not quite exactly the same time.
+static const nsecs_t MULTITOUCH_SETTLE_INTERVAL = 100 * 1000000; // 100ms
 
-// The transition from INDETERMINATE_MULTITOUCH to SWIPE gesture mode can only occur when the
+// The transition from PRESS to SWIPE or FREEFORM gesture mode is made when
+// both of the pointers are moving at least this fast.
+static const float MULTITOUCH_MIN_SPEED = 150.0f; // pixels per second
+
+// The transition from PRESS to SWIPE gesture mode can only occur when the
 // cosine of the angle between the two vectors is greater than or equal to than this value
 // which indicates that the vectors are oriented in the same direction.
 // When the vectors are oriented in the exactly same direction, the cosine is 1.0.
 // (In exactly opposite directions, the cosine is -1.0.)
 static const float SWIPE_TRANSITION_ANGLE_COSINE = 0.5f; // cosine of 45 degrees
 
+// The transition from PRESS to SWIPE gesture mode can only occur when the
+// fingers are no more than this far apart relative to the diagonal size of
+// the touch pad.  For example, a ratio of 0.5 means that the fingers must be
+// no more than half the diagonal size of the touch pad apart.
+static const float SWIPE_MAX_WIDTH_RATIO = 0.333f; // 1/3
+
+// The gesture movement speed factor relative to the size of the display.
+// Movement speed applies when the fingers are moving in the same direction.
+// Without acceleration, a full swipe of the touch pad diagonal in movement mode
+// will cover this portion of the display diagonal.
+static const float GESTURE_MOVEMENT_SPEED_RATIO = 0.8f;
+
+// The gesture zoom speed factor relative to the size of the display.
+// Zoom speed applies when the fingers are mostly moving relative to each other
+// to execute a scale gesture or similar.
+// Without acceleration, a full swipe of the touch pad diagonal in zoom mode
+// will cover this portion of the display diagonal.
+static const float GESTURE_ZOOM_SPEED_RATIO = 0.3f;
+
 
 // --- Static Functions ---
 
@@ -112,14 +140,8 @@
     return (x + y) / 2;
 }
 
-inline static float pythag(float x, float y) {
-    return sqrtf(x * x + y * y);
-}
-
-inline static int32_t distanceSquared(int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
-    int32_t dx = x1 - x2;
-    int32_t dy = y1 - y2;
-    return dx * dx + dy * dy;
+inline static float distance(float x1, float y1, float x2, float y2) {
+    return hypotf(x1 - x2, y1 - y2);
 }
 
 inline static int32_t signExtendNybble(int32_t value) {
@@ -224,6 +246,33 @@
     return edgeFlags;
 }
 
+static void clampPositionUsingPointerBounds(
+        const sp<PointerControllerInterface>& pointerController, float* x, float* y) {
+    float minX, minY, maxX, maxY;
+    if (pointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+        if (*x < minX) {
+            *x = minX;
+        } else if (*x > maxX) {
+            *x = maxX;
+        }
+        if (*y < minY) {
+            *y = minY;
+        } else if (*y > maxY) {
+            *y = maxY;
+        }
+    }
+}
+
+static float calculateCommonVector(float a, float b) {
+    if (a > 0 && b > 0) {
+        return a < b ? a : b;
+    } else if (a < 0 && b < 0) {
+        return a > b ? a : b;
+    } else {
+        return 0;
+    }
+}
+
 
 // --- InputReader ---
 
@@ -1553,10 +1602,32 @@
 
         motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE;
 
+        if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
+            vscroll = mAccumulator.relWheel;
+        } else {
+            vscroll = 0;
+        }
+        if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
+            hscroll = mAccumulator.relHWheel;
+        } else {
+            hscroll = 0;
+        }
+
         if (mPointerController != NULL) {
-            mPointerController->move(deltaX, deltaY);
-            if (buttonsChanged) {
-                mPointerController->setButtonState(mLocked.buttonState);
+            if (deltaX != 0 || deltaY != 0 || vscroll != 0 || hscroll != 0
+                    || buttonsChanged) {
+                mPointerController->setPresentation(
+                        PointerControllerInterface::PRESENTATION_POINTER);
+
+                if (deltaX != 0 || deltaY != 0) {
+                    mPointerController->move(deltaX, deltaY);
+                }
+
+                if (buttonsChanged) {
+                    mPointerController->setButtonState(mLocked.buttonState);
+                }
+
+                mPointerController->unfade();
             }
 
             float x, y;
@@ -1574,20 +1645,6 @@
         }
 
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
-
-        if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
-            vscroll = mAccumulator.relWheel;
-        } else {
-            vscroll = 0;
-        }
-        if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
-            hscroll = mAccumulator.relHWheel;
-        } else {
-            hscroll = 0;
-        }
-        if (hscroll != 0 || vscroll != 0) {
-            mPointerController->unfade();
-        }
     } // release lock
 
     // Moving an external trackball or mouse should wake the device.
@@ -1751,8 +1808,8 @@
                     mLocked.pointerGestureXZoomScale);
             dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n",
                     mLocked.pointerGestureYZoomScale);
-            dump.appendFormat(INDENT4 "MaxSwipeWidthSquared: %d\n",
-                    mLocked.pointerGestureMaxSwipeWidthSquared);
+            dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n",
+                    mLocked.pointerGestureMaxSwipeWidth);
         }
     } // release lock
 }
@@ -1825,6 +1882,10 @@
     mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
     mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime();
 
+    // TODO: Make this configurable.
+    //mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER;
+    mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS;
+
     if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X)
             || getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
         // The device is a cursor device with a touch pad attached.
@@ -1983,7 +2044,7 @@
         mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale);
 
         // Size of diagonal axis.
-        float diagonalSize = pythag(width, height);
+        float diagonalSize = hypotf(width, height);
 
         // TouchMajor and TouchMinor factors.
         if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) {
@@ -2178,30 +2239,39 @@
         if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
             int32_t rawWidth = mRawAxes.x.maxValue - mRawAxes.x.minValue + 1;
             int32_t rawHeight = mRawAxes.y.maxValue - mRawAxes.y.minValue + 1;
+            float rawDiagonal = hypotf(rawWidth, rawHeight);
+            float displayDiagonal = hypotf(mLocked.associatedDisplayWidth,
+                    mLocked.associatedDisplayHeight);
 
-            // Scale movements such that one whole swipe of the touch pad covers a portion
-            // of the display along whichever axis of the touch pad is longer.
+            // Scale movements such that one whole swipe of the touch pad covers a
+            // given area relative to the diagonal size of the display.
             // Assume that the touch pad has a square aspect ratio such that movements in
             // X and Y of the same number of raw units cover the same physical distance.
             const float scaleFactor = 0.8f;
 
-            mLocked.pointerGestureXMovementScale = rawWidth > rawHeight
-                    ? scaleFactor * float(mLocked.associatedDisplayWidth) / rawWidth
-                    : scaleFactor * float(mLocked.associatedDisplayHeight) / rawHeight;
+            mLocked.pointerGestureXMovementScale = GESTURE_MOVEMENT_SPEED_RATIO
+                    * displayDiagonal / rawDiagonal;
             mLocked.pointerGestureYMovementScale = mLocked.pointerGestureXMovementScale;
 
             // Scale zooms to cover a smaller range of the display than movements do.
             // This value determines the area around the pointer that is affected by freeform
             // pointer gestures.
-            mLocked.pointerGestureXZoomScale = mLocked.pointerGestureXMovementScale * 0.4f;
-            mLocked.pointerGestureYZoomScale = mLocked.pointerGestureYMovementScale * 0.4f;
+            mLocked.pointerGestureXZoomScale = GESTURE_ZOOM_SPEED_RATIO
+                    * displayDiagonal / rawDiagonal;
+            mLocked.pointerGestureYZoomScale = mLocked.pointerGestureXZoomScale;
 
-            // Max width between pointers to detect a swipe gesture is 3/4 of the short
-            // axis of the touch pad.  Touches that are wider than this are translated
-            // into freeform gestures.
-            mLocked.pointerGestureMaxSwipeWidthSquared = min(rawWidth, rawHeight) * 3 / 4;
-            mLocked.pointerGestureMaxSwipeWidthSquared *=
-                    mLocked.pointerGestureMaxSwipeWidthSquared;
+            // Max width between pointers to detect a swipe gesture is more than some fraction
+            // of the diagonal axis of the touch pad.  Touches that are wider than this are
+            // translated into freeform gestures.
+            mLocked.pointerGestureMaxSwipeWidth = SWIPE_MAX_WIDTH_RATIO * rawDiagonal;
+
+            // Reset the current pointer gesture.
+            mPointerGesture.reset();
+
+            // Remove any current spots.
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerController->clearSpots();
+            }
         }
     }
 
@@ -2628,6 +2698,11 @@
     { // acquire lock
         AutoMutex _l(mLock);
         initializeLocked();
+
+        if (mPointerController != NULL
+                && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerController->clearSpots();
+        }
     } // release lock
 
     InputMapper::reset();
@@ -2691,14 +2766,21 @@
         }
     }
 
-    // Process touches and virtual keys.
-    TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);
-    if (touchResult == DISPATCH_TOUCH) {
-        suppressSwipeOntoVirtualKeys(when);
-        if (mPointerController != NULL) {
-            dispatchPointerGestures(when, policyFlags);
+    TouchResult touchResult;
+    if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount == 0
+            && mLastTouch.buttonState == mCurrentTouch.buttonState) {
+        // Drop spurious syncs.
+        touchResult = DROP_STROKE;
+    } else {
+        // Process touches and virtual keys.
+        touchResult = consumeOffScreenTouches(when, policyFlags);
+        if (touchResult == DISPATCH_TOUCH) {
+            suppressSwipeOntoVirtualKeys(when);
+            if (mPointerController != NULL) {
+                dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
+            }
+            dispatchTouches(when, policyFlags);
         }
-        dispatchTouches(when, policyFlags);
     }
 
     // Copy current touch to last touch in preparation for the next cycle.
@@ -2711,6 +2793,12 @@
     }
 }
 
+void TouchInputMapper::timeoutExpired(nsecs_t when) {
+    if (mPointerController != NULL) {
+        dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/);
+    }
+}
+
 TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches(
         nsecs_t when, uint32_t policyFlags) {
     int32_t keyEventAction, keyEventFlags;
@@ -3070,7 +3158,7 @@
             int32_t c2 = signExtendNybble(in.orientation & 0x0f);
             if (c1 != 0 || c2 != 0) {
                 orientation = atan2f(c1, c2) * 0.5f;
-                float scale = 1.0f + pythag(c1, c2) / 16.0f;
+                float scale = 1.0f + hypotf(c1, c2) / 16.0f;
                 touchMajor *= scale;
                 touchMinor /= scale;
                 toolMajor *= scale;
@@ -3154,23 +3242,43 @@
     *outYPrecision = mLocked.orientedYPrecision;
 }
 
-void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags,
+        bool isTimeout) {
+    // Switch pointer presentation.
+    mPointerController->setPresentation(
+            mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+                    ? PointerControllerInterface::PRESENTATION_SPOT
+                    : PointerControllerInterface::PRESENTATION_POINTER);
+
     // Update current gesture coordinates.
     bool cancelPreviousGesture, finishPreviousGesture;
-    preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture);
+    bool sendEvents = preparePointerGestures(when,
+            &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
+    if (!sendEvents) {
+        return;
+    }
+
+    // Show the pointer if needed.
+    if (mPointerGesture.currentGestureMode != PointerGesture::NEUTRAL
+            && mPointerGesture.currentGestureMode != PointerGesture::QUIET) {
+        mPointerController->unfade();
+    }
 
     // Send events!
     uint32_t metaState = getContext()->getGlobalMetaState();
 
     // Update last coordinates of pointers that have moved so that we observe the new
     // pointer positions at the same time as other pointers that have just gone up.
-    bool down = mPointerGesture.currentGestureMode == PointerGesture::CLICK_OR_DRAG
+    bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP
+            || mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG
+            || mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+            || mPointerGesture.currentGestureMode == PointerGesture::PRESS
             || mPointerGesture.currentGestureMode == PointerGesture::SWIPE
             || mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
     bool moveNeeded = false;
     if (down && !cancelPreviousGesture && !finishPreviousGesture
-            && mPointerGesture.lastGesturePointerCount != 0
-            && mPointerGesture.currentGesturePointerCount != 0) {
+            && !mPointerGesture.lastGestureIdBits.isEmpty()
+            && !mPointerGesture.currentGestureIdBits.isEmpty()) {
         BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value
                 & mPointerGesture.lastGestureIdBits.value);
         moveNeeded = updateMovedPointerCoords(
@@ -3251,27 +3359,6 @@
         }
     }
 
-    // Send down and up for a tap.
-    if (mPointerGesture.currentGestureMode == PointerGesture::TAP) {
-        const PointerCoords& coords = mPointerGesture.currentGestureCoords[0];
-        int32_t edgeFlags = calculateEdgeFlagsUsingPointerBounds(mPointerController,
-                coords.getAxisValue(AMOTION_EVENT_AXIS_X),
-                coords.getAxisValue(AMOTION_EVENT_AXIS_Y));
-        nsecs_t downTime = mPointerGesture.downTime = mPointerGesture.tapTime;
-        mPointerGesture.resetTapTime();
-
-        dispatchMotion(downTime, policyFlags, mPointerSource,
-                AMOTION_EVENT_ACTION_DOWN, 0, metaState, edgeFlags,
-                mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
-                mPointerGesture.currentGestureIdBits, -1,
-                0, 0, downTime);
-        dispatchMotion(when, policyFlags, mPointerSource,
-                AMOTION_EVENT_ACTION_UP, 0, metaState, edgeFlags,
-                mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
-                mPointerGesture.currentGestureIdBits, -1,
-                0, 0, downTime);
-    }
-
     // Send motion events for hover.
     if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) {
         dispatchMotion(when, policyFlags, mPointerSource,
@@ -3284,11 +3371,8 @@
     // Update state.
     mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
     if (!down) {
-        mPointerGesture.lastGesturePointerCount = 0;
         mPointerGesture.lastGestureIdBits.clear();
     } else {
-        uint32_t currentGesturePointerCount = mPointerGesture.currentGesturePointerCount;
-        mPointerGesture.lastGesturePointerCount = currentGesturePointerCount;
         mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
         for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) {
             uint32_t id = idBits.firstMarkedBit();
@@ -3301,13 +3385,49 @@
     }
 }
 
-void TouchInputMapper::preparePointerGestures(nsecs_t when,
-        bool* outCancelPreviousGesture, bool* outFinishPreviousGesture) {
+bool TouchInputMapper::preparePointerGestures(nsecs_t when,
+        bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) {
     *outCancelPreviousGesture = false;
     *outFinishPreviousGesture = false;
 
     AutoMutex _l(mLock);
 
+    // Handle TAP timeout.
+    if (isTimeout) {
+#if DEBUG_GESTURES
+        LOGD("Gestures: Processing timeout");
+#endif
+
+        if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+            if (when <= mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL) {
+                // The tap/drag timeout has not yet expired.
+                getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL);
+            } else {
+                // The tap is finished.
+#if DEBUG_GESTURES
+                LOGD("Gestures: TAP finished");
+#endif
+                *outFinishPreviousGesture = true;
+
+                mPointerGesture.activeGestureId = -1;
+                mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+                mPointerGesture.currentGestureIdBits.clear();
+
+                mPointerController->setButtonState(0);
+
+                if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                    mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+                    mPointerGesture.spotIdBits.clear();
+                    moveSpotsLocked();
+                }
+                return true;
+            }
+        }
+
+        // We did not handle this timeout.
+        return false;
+    }
+
     // Update the velocity tracker.
     {
         VelocityTracker::Position positions[MAX_POINTERS];
@@ -3328,77 +3448,51 @@
     // Choose an arbitrary pointer that just went down, if there is one.
     // Otherwise choose an arbitrary remaining pointer.
     // This guarantees we always have an active touch id when there is at least one pointer.
-    // We always switch to the newest pointer down because that's usually where the user's
-    // attention is focused.
-    int32_t activeTouchId;
-    BitSet32 downTouchIdBits(mCurrentTouch.idBits.value & ~mLastTouch.idBits.value);
-    if (!downTouchIdBits.isEmpty()) {
-        activeTouchId = mPointerGesture.activeTouchId = downTouchIdBits.firstMarkedBit();
-    } else {
-        activeTouchId = mPointerGesture.activeTouchId;
-        if (activeTouchId < 0 || !mCurrentTouch.idBits.hasBit(activeTouchId)) {
-            if (!mCurrentTouch.idBits.isEmpty()) {
-                activeTouchId = mPointerGesture.activeTouchId =
-                        mCurrentTouch.idBits.firstMarkedBit();
-            } else {
-                activeTouchId = mPointerGesture.activeTouchId = -1;
-            }
+    // We keep the same active touch id for as long as possible.
+    bool activeTouchChanged = false;
+    int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
+    int32_t activeTouchId = lastActiveTouchId;
+    if (activeTouchId < 0) {
+        if (!mCurrentTouch.idBits.isEmpty()) {
+            activeTouchChanged = true;
+            activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit();
+            mPointerGesture.firstTouchTime = when;
         }
-    }
-
-    // Update the touch origin data to track where each finger originally went down.
-    if (mCurrentTouch.pointerCount == 0 || mPointerGesture.touchOrigin.pointerCount == 0) {
-        // Fast path when all fingers have gone up or down.
-        mPointerGesture.touchOrigin.copyFrom(mCurrentTouch);
-    } else {
-        // Slow path when only some fingers have gone up or down.
-        for (BitSet32 idBits(mPointerGesture.touchOrigin.idBits.value
-                & ~mCurrentTouch.idBits.value); !idBits.isEmpty(); ) {
-            uint32_t id = idBits.firstMarkedBit();
-            idBits.clearBit(id);
-            mPointerGesture.touchOrigin.idBits.clearBit(id);
-            uint32_t index = mPointerGesture.touchOrigin.idToIndex[id];
-            uint32_t count = --mPointerGesture.touchOrigin.pointerCount;
-            while (index < count) {
-                mPointerGesture.touchOrigin.pointers[index] =
-                        mPointerGesture.touchOrigin.pointers[index + 1];
-                uint32_t movedId = mPointerGesture.touchOrigin.pointers[index].id;
-                mPointerGesture.touchOrigin.idToIndex[movedId] = index;
-                index += 1;
-            }
-        }
-        for (BitSet32 idBits(mCurrentTouch.idBits.value
-                & ~mPointerGesture.touchOrigin.idBits.value); !idBits.isEmpty(); ) {
-            uint32_t id = idBits.firstMarkedBit();
-            idBits.clearBit(id);
-            mPointerGesture.touchOrigin.idBits.markBit(id);
-            uint32_t index = mPointerGesture.touchOrigin.pointerCount++;
-            mPointerGesture.touchOrigin.pointers[index] =
-                    mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
-            mPointerGesture.touchOrigin.idToIndex[id] = index;
+    } else if (!mCurrentTouch.idBits.hasBit(activeTouchId)) {
+        activeTouchChanged = true;
+        if (!mCurrentTouch.idBits.isEmpty()) {
+            activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit();
+        } else {
+            activeTouchId = mPointerGesture.activeTouchId = -1;
         }
     }
 
     // Determine whether we are in quiet time.
-    bool isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL;
-    if (!isQuietTime) {
-        if ((mPointerGesture.lastGestureMode == PointerGesture::SWIPE
-                || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
-                && mCurrentTouch.pointerCount < 2) {
-            // Enter quiet time when exiting swipe or freeform state.
-            // This is to prevent accidentally entering the hover state and flinging the
-            // pointer when finishing a swipe and there is still one pointer left onscreen.
-            isQuietTime = true;
-        } else if (mPointerGesture.lastGestureMode == PointerGesture::CLICK_OR_DRAG
-                && mCurrentTouch.pointerCount >= 2
-                && !isPointerDown(mCurrentTouch.buttonState)) {
-            // Enter quiet time when releasing the button and there are still two or more
-            // fingers down.  This may indicate that one finger was used to press the button
-            // but it has not gone up yet.
-            isQuietTime = true;
-        }
-        if (isQuietTime) {
-            mPointerGesture.quietTime = when;
+    bool isQuietTime = false;
+    if (activeTouchId < 0) {
+        mPointerGesture.resetQuietTime();
+    } else {
+        isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL;
+        if (!isQuietTime) {
+            if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS
+                    || mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+                    || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
+                    && mCurrentTouch.pointerCount < 2) {
+                // Enter quiet time when exiting swipe or freeform state.
+                // This is to prevent accidentally entering the hover state and flinging the
+                // pointer when finishing a swipe and there is still one pointer left onscreen.
+                isQuietTime = true;
+            } else if (mPointerGesture.lastGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+                    && mCurrentTouch.pointerCount >= 2
+                    && !isPointerDown(mCurrentTouch.buttonState)) {
+                // Enter quiet time when releasing the button and there are still two or more
+                // fingers down.  This may indicate that one finger was used to press the button
+                // but it has not gone up yet.
+                isQuietTime = true;
+            }
+            if (isQuietTime) {
+                mPointerGesture.quietTime = when;
+            }
         }
     }
 
@@ -3413,10 +3507,17 @@
 
         mPointerGesture.activeGestureId = -1;
         mPointerGesture.currentGestureMode = PointerGesture::QUIET;
-        mPointerGesture.currentGesturePointerCount = 0;
         mPointerGesture.currentGestureIdBits.clear();
+
+        mPointerController->setButtonState(0);
+
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+            mPointerGesture.spotIdBits.clear();
+            moveSpotsLocked();
+        }
     } else if (isPointerDown(mCurrentTouch.buttonState)) {
-        // Case 2: Button is pressed. (DRAG)
+        // Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG)
         // The pointer follows the active touch point.
         // Emit DOWN, MOVE, UP events at the pointer location.
         //
@@ -3430,11 +3531,11 @@
         // finger to drag then the active pointer should switch to the finger that is
         // being dragged.
 #if DEBUG_GESTURES
-        LOGD("Gestures: CLICK_OR_DRAG activeTouchId=%d, "
+        LOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
                 "currentTouchPointerCount=%d", activeTouchId, mCurrentTouch.pointerCount);
 #endif
         // Reset state when just starting.
-        if (mPointerGesture.lastGestureMode != PointerGesture::CLICK_OR_DRAG) {
+        if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) {
             *outFinishPreviousGesture = true;
             mPointerGesture.activeGestureId = 0;
         }
@@ -3449,7 +3550,7 @@
                     uint32_t id = mCurrentTouch.pointers[i].id;
                     float vx, vy;
                     if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
-                        float speed = pythag(vx, vy);
+                        float speed = hypotf(vx, vy);
                         if (speed > bestSpeed) {
                             bestId = id;
                             bestSpeed = speed;
@@ -3458,8 +3559,9 @@
                 }
                 if (bestId >= 0 && bestId != activeTouchId) {
                     mPointerGesture.activeTouchId = activeTouchId = bestId;
+                    activeTouchChanged = true;
 #if DEBUG_GESTURES
-                    LOGD("Gestures: CLICK_OR_DRAG switched pointers, "
+                    LOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
                             "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed);
 #endif
                 }
@@ -3474,6 +3576,10 @@
                         * mLocked.pointerGestureXMovementScale;
                 float deltaY = (currentPointer.y - lastPointer.y)
                         * mLocked.pointerGestureYMovementScale;
+
+                // Move the pointer using a relative motion.
+                // When using spots, the click will occur at the position of the anchor
+                // spot and all other spots will move there.
                 mPointerController->move(deltaX, deltaY);
             }
         }
@@ -3481,8 +3587,7 @@
         float x, y;
         mPointerController->getPosition(&x, &y);
 
-        mPointerGesture.currentGestureMode = PointerGesture::CLICK_OR_DRAG;
-        mPointerGesture.currentGesturePointerCount = 1;
+        mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG;
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@@ -3490,26 +3595,54 @@
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+        mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            if (activeTouchId >= 0) {
+                // Collapse all spots into one point at the pointer location.
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_DRAG;
+                mPointerGesture.spotIdBits.clear();
+                for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
+                    uint32_t id = mCurrentTouch.pointers[i].id;
+                    mPointerGesture.spotIdBits.markBit(id);
+                    mPointerGesture.spotIdToIndex[id] = i;
+                    mPointerGesture.spotCoords[i] = mPointerGesture.currentGestureCoords[0];
+                }
+            } else {
+                // No fingers.  Generate a spot at the pointer location so the
+                // anchor appears to be pressed.
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_CLICK;
+                mPointerGesture.spotIdBits.clear();
+                mPointerGesture.spotIdBits.markBit(0);
+                mPointerGesture.spotIdToIndex[0] = 0;
+                mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+            }
+            moveSpotsLocked();
+        }
     } else if (mCurrentTouch.pointerCount == 0) {
         // Case 3. No fingers down and button is not pressed. (NEUTRAL)
         *outFinishPreviousGesture = true;
 
-        // Watch for taps coming out of HOVER or INDETERMINATE_MULTITOUCH mode.
+        // Watch for taps coming out of HOVER or TAP_DRAG mode.
         bool tapped = false;
-        if (mPointerGesture.lastGestureMode == PointerGesture::HOVER
-                || mPointerGesture.lastGestureMode
-                        == PointerGesture::INDETERMINATE_MULTITOUCH) {
-            if (when <= mPointerGesture.tapTime + TAP_INTERVAL) {
+        if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER
+                || mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG)
+                && mLastTouch.pointerCount == 1) {
+            if (when <= mPointerGesture.tapDownTime + TAP_INTERVAL) {
                 float x, y;
                 mPointerController->getPosition(&x, &y);
-                if (fabs(x - mPointerGesture.initialPointerX) <= TAP_SLOP
-                        && fabs(y - mPointerGesture.initialPointerY) <= TAP_SLOP) {
+                if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP
+                        && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) {
 #if DEBUG_GESTURES
                     LOGD("Gestures: TAP");
 #endif
+
+                    mPointerGesture.tapUpTime = when;
+                    getContext()->requestTimeoutAtTime(when + TAP_DRAG_INTERVAL);
+
                     mPointerGesture.activeGestureId = 0;
                     mPointerGesture.currentGestureMode = PointerGesture::TAP;
-                    mPointerGesture.currentGesturePointerCount = 1;
                     mPointerGesture.currentGestureIdBits.clear();
                     mPointerGesture.currentGestureIdBits.markBit(
                             mPointerGesture.activeGestureId);
@@ -3517,44 +3650,86 @@
                             mPointerGesture.activeGestureId] = 0;
                     mPointerGesture.currentGestureCoords[0].clear();
                     mPointerGesture.currentGestureCoords[0].setAxisValue(
-                            AMOTION_EVENT_AXIS_X, mPointerGesture.initialPointerX);
+                            AMOTION_EVENT_AXIS_X, mPointerGesture.tapX);
                     mPointerGesture.currentGestureCoords[0].setAxisValue(
-                            AMOTION_EVENT_AXIS_Y, mPointerGesture.initialPointerY);
+                            AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY);
                     mPointerGesture.currentGestureCoords[0].setAxisValue(
                             AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+                    mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+                    if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                        mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_TAP;
+                        mPointerGesture.spotIdBits.clear();
+                        mPointerGesture.spotIdBits.markBit(lastActiveTouchId);
+                        mPointerGesture.spotIdToIndex[lastActiveTouchId] = 0;
+                        mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+                        moveSpotsLocked();
+                    }
+
                     tapped = true;
                 } else {
 #if DEBUG_GESTURES
                     LOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f",
-                            x - mPointerGesture.initialPointerX,
-                            y - mPointerGesture.initialPointerY);
+                            x - mPointerGesture.tapX,
+                            y - mPointerGesture.tapY);
 #endif
                 }
             } else {
 #if DEBUG_GESTURES
-                LOGD("Gestures: Not a TAP, delay=%lld",
-                        when - mPointerGesture.tapTime);
+                LOGD("Gestures: Not a TAP, %0.3fms since down",
+                        (when - mPointerGesture.tapDownTime) * 0.000001f);
 #endif
             }
         }
+
         if (!tapped) {
 #if DEBUG_GESTURES
             LOGD("Gestures: NEUTRAL");
 #endif
             mPointerGesture.activeGestureId = -1;
             mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
-            mPointerGesture.currentGesturePointerCount = 0;
             mPointerGesture.currentGestureIdBits.clear();
+
+            mPointerController->setButtonState(0);
+
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+                mPointerGesture.spotIdBits.clear();
+                moveSpotsLocked();
+            }
         }
     } else if (mCurrentTouch.pointerCount == 1) {
-        // Case 4. Exactly one finger down, button is not pressed. (HOVER)
+        // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG)
         // The pointer follows the active touch point.
-        // Emit HOVER_MOVE events at the pointer location.
+        // When in HOVER, emit HOVER_MOVE events at the pointer location.
+        // When in TAP_DRAG, emit MOVE events at the pointer location.
         LOG_ASSERT(activeTouchId >= 0);
 
+        mPointerGesture.currentGestureMode = PointerGesture::HOVER;
+        if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+            if (when <= mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL) {
+                float x, y;
+                mPointerController->getPosition(&x, &y);
+                if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP
+                        && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) {
+                    mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+                } else {
 #if DEBUG_GESTURES
-        LOGD("Gestures: HOVER");
+                    LOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
+                            x - mPointerGesture.tapX,
+                            y - mPointerGesture.tapY);
 #endif
+                }
+            } else {
+#if DEBUG_GESTURES
+                LOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up",
+                        (when - mPointerGesture.tapUpTime) * 0.000001f);
+#endif
+            }
+        } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) {
+            mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+        }
 
         if (mLastTouch.idBits.hasBit(activeTouchId)) {
             const PointerData& currentPointer =
@@ -3565,180 +3740,305 @@
                     * mLocked.pointerGestureXMovementScale;
             float deltaY = (currentPointer.y - lastPointer.y)
                     * mLocked.pointerGestureYMovementScale;
+
+            // Move the pointer using a relative motion.
+            // When using spots, the hover or drag will occur at the position of the anchor spot.
             mPointerController->move(deltaX, deltaY);
         }
 
-        *outFinishPreviousGesture = true;
-        mPointerGesture.activeGestureId = 0;
+        bool down;
+        if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) {
+#if DEBUG_GESTURES
+            LOGD("Gestures: TAP_DRAG");
+#endif
+            down = true;
+        } else {
+#if DEBUG_GESTURES
+            LOGD("Gestures: HOVER");
+#endif
+            *outFinishPreviousGesture = true;
+            mPointerGesture.activeGestureId = 0;
+            down = false;
+        }
 
         float x, y;
         mPointerController->getPosition(&x, &y);
 
-        mPointerGesture.currentGestureMode = PointerGesture::HOVER;
-        mPointerGesture.currentGesturePointerCount = 1;
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
         mPointerGesture.currentGestureCoords[0].clear();
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
+        mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+                down ? 1.0f : 0.0f);
+
+        mPointerController->setButtonState(down ? BUTTON_STATE_PRIMARY : 0);
 
         if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
-            mPointerGesture.tapTime = when;
-            mPointerGesture.initialPointerX = x;
-            mPointerGesture.initialPointerY = y;
+            mPointerGesture.resetTap();
+            mPointerGesture.tapDownTime = when;
+            mPointerGesture.tapX = x;
+            mPointerGesture.tapY = y;
+        }
+
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerGesture.spotGesture = down ? PointerControllerInterface::SPOT_GESTURE_DRAG
+                    : PointerControllerInterface::SPOT_GESTURE_HOVER;
+            mPointerGesture.spotIdBits.clear();
+            mPointerGesture.spotIdBits.markBit(activeTouchId);
+            mPointerGesture.spotIdToIndex[activeTouchId] = 0;
+            mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+            moveSpotsLocked();
         }
     } else {
-        // Case 5. At least two fingers down, button is not pressed. (SWIPE or FREEFORM
-        // or INDETERMINATE_MULTITOUCH)
-        // Initially we watch and wait for something interesting to happen so as to
-        // avoid making a spurious guess as to the nature of the gesture.  For example,
-        // the fingers may be in transition to some other state such as pressing or
-        // releasing the button or we may be performing a two finger tap.
+        // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
+        // We need to provide feedback for each finger that goes down so we cannot wait
+        // for the fingers to move before deciding what to do.
         //
-        // Fix the centroid of the figure when the gesture actually starts.
-        // We do not recalculate the centroid at any other time during the gesture because
-        // it would affect the relationship of the touch points relative to the pointer location.
+        // The ambiguous case is deciding what to do when there are two fingers down but they
+        // have not moved enough to determine whether they are part of a drag or part of a
+        // freeform gesture, or just a press or long-press at the pointer location.
+        //
+        // When there are two fingers we start with the PRESS hypothesis and we generate a
+        // down at the pointer location.
+        //
+        // When the two fingers move enough or when additional fingers are added, we make
+        // a decision to transition into SWIPE or FREEFORM mode accordingly.
         LOG_ASSERT(activeTouchId >= 0);
 
-        uint32_t currentTouchPointerCount = mCurrentTouch.pointerCount;
-        if (currentTouchPointerCount > MAX_POINTERS) {
-            currentTouchPointerCount = MAX_POINTERS;
-        }
-
-        if (mPointerGesture.lastGestureMode != PointerGesture::INDETERMINATE_MULTITOUCH
+        bool needReference = false;
+        bool settled = when >= mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL;
+        if (mPointerGesture.lastGestureMode != PointerGesture::PRESS
                 && mPointerGesture.lastGestureMode != PointerGesture::SWIPE
                 && mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
-            mPointerGesture.currentGestureMode = PointerGesture::INDETERMINATE_MULTITOUCH;
-
             *outFinishPreviousGesture = true;
-            mPointerGesture.activeGestureId = -1;
+            mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+            mPointerGesture.activeGestureId = 0;
 
-            // Remember the initial pointer location.
-            // Everything we do will be relative to this location.
-            mPointerController->getPosition(&mPointerGesture.initialPointerX,
-                    &mPointerGesture.initialPointerY);
-
-            // Track taps.
-            if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
-                mPointerGesture.tapTime = when;
+            if (settled && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+                    && mLastTouch.idBits.hasBit(mPointerGesture.activeTouchId)) {
+                // The spot is already visible and has settled, use it as the reference point
+                // for the gesture.  Other spots will be positioned relative to this one.
+#if DEBUG_GESTURES
+                LOGD("Gestures: Using active spot as reference for MULTITOUCH, "
+                        "settle time expired %0.3fms ago",
+                        (when - mPointerGesture.firstTouchTime - MULTITOUCH_SETTLE_INTERVAL)
+                                * 0.000001f);
+#endif
+                const PointerData& d = mLastTouch.pointers[mLastTouch.idToIndex[
+                        mPointerGesture.activeTouchId]];
+                mPointerGesture.referenceTouchX = d.x;
+                mPointerGesture.referenceTouchY = d.y;
+                const PointerCoords& c = mPointerGesture.spotCoords[mPointerGesture.spotIdToIndex[
+                        mPointerGesture.activeTouchId]];
+                mPointerGesture.referenceGestureX = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+                mPointerGesture.referenceGestureY = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+            } else {
+#if DEBUG_GESTURES
+                LOGD("Gestures: Using centroid as reference for MULTITOUCH, "
+                        "settle time remaining %0.3fms",
+                        (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when)
+                                * 0.000001f);
+#endif
+                needReference = true;
             }
-
-            // Reset the touch origin to be relative to exactly where the fingers are now
-            // in case they have moved some distance away as part of a previous gesture.
-            // We want to know how far the fingers have traveled since we started considering
-            // a multitouch gesture.
-            mPointerGesture.touchOrigin.copyFrom(mCurrentTouch);
+        } else if (!settled && mCurrentTouch.pointerCount > mLastTouch.pointerCount) {
+            // Additional pointers have gone down but not yet settled.
+            // Reset the gesture.
+#if DEBUG_GESTURES
+            LOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
+                    "settle time remaining %0.3fms",
+                    (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when)
+                            * 0.000001f);
+#endif
+            *outCancelPreviousGesture = true;
+            mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+            mPointerGesture.activeGestureId = 0;
         } else {
+            // Continue previous gesture.
             mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
         }
 
-        if (mPointerGesture.currentGestureMode == PointerGesture::INDETERMINATE_MULTITOUCH) {
-            // Wait for the pointers to start moving before doing anything.
-            bool decideNow = true;
-            for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
-                const PointerData& current = mCurrentTouch.pointers[i];
-                const PointerData& origin = mPointerGesture.touchOrigin.pointers[
-                        mPointerGesture.touchOrigin.idToIndex[current.id]];
-                float distance = pythag(
-                        (current.x - origin.x) * mLocked.pointerGestureXZoomScale,
-                        (current.y - origin.y) * mLocked.pointerGestureYZoomScale);
-                if (distance < MULTITOUCH_MIN_TRAVEL) {
-                    decideNow = false;
-                    break;
-                }
-            }
+        if (needReference) {
+            // Use the centroid and pointer location as the reference points for the gesture.
+            mCurrentTouch.getCentroid(&mPointerGesture.referenceTouchX,
+                    &mPointerGesture.referenceTouchY);
+            mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+                    &mPointerGesture.referenceGestureY);
+        }
 
-            if (decideNow) {
+        if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+            float d;
+            if (mCurrentTouch.pointerCount > 2) {
+                // There are more than two pointers, switch to FREEFORM.
+#if DEBUG_GESTURES
+                LOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+                        mCurrentTouch.pointerCount);
+#endif
+                *outCancelPreviousGesture = true;
                 mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
-                if (currentTouchPointerCount == 2
-                        && distanceSquared(
-                                mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y,
-                                mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y)
-                                <= mLocked.pointerGestureMaxSwipeWidthSquared) {
-                    const PointerData& current1 = mCurrentTouch.pointers[0];
-                    const PointerData& current2 = mCurrentTouch.pointers[1];
-                    const PointerData& origin1 = mPointerGesture.touchOrigin.pointers[
-                            mPointerGesture.touchOrigin.idToIndex[current1.id]];
-                    const PointerData& origin2 = mPointerGesture.touchOrigin.pointers[
-                            mPointerGesture.touchOrigin.idToIndex[current2.id]];
+            } else if (((d = distance(
+                    mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y,
+                    mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y))
+                            > mLocked.pointerGestureMaxSwipeWidth)) {
+                // There are two pointers but they are too far apart, switch to FREEFORM.
+#if DEBUG_GESTURES
+                LOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+                        d, mLocked.pointerGestureMaxSwipeWidth);
+#endif
+                *outCancelPreviousGesture = true;
+                mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+            } else {
+                // There are two pointers.  Wait for both pointers to start moving
+                // before deciding whether this is a SWIPE or FREEFORM gesture.
+                uint32_t id1 = mCurrentTouch.pointers[0].id;
+                uint32_t id2 = mCurrentTouch.pointers[1].id;
 
-                    float x1 = (current1.x - origin1.x) * mLocked.pointerGestureXZoomScale;
-                    float y1 = (current1.y - origin1.y) * mLocked.pointerGestureYZoomScale;
-                    float x2 = (current2.x - origin2.x) * mLocked.pointerGestureXZoomScale;
-                    float y2 = (current2.y - origin2.y) * mLocked.pointerGestureYZoomScale;
-                    float magnitude1 = pythag(x1, y1);
-                    float magnitude2 = pythag(x2, y2);
+                float vx1, vy1, vx2, vy2;
+                mPointerGesture.velocityTracker.getVelocity(id1, &vx1, &vy1);
+                mPointerGesture.velocityTracker.getVelocity(id2, &vx2, &vy2);
 
-                    // Calculate the dot product of the vectors.
+                float speed1 = hypotf(vx1, vy1);
+                float speed2 = hypotf(vx2, vy2);
+                if (speed1 >= MULTITOUCH_MIN_SPEED && speed2 >= MULTITOUCH_MIN_SPEED) {
+                    // Calculate the dot product of the velocity vectors.
                     // When the vectors are oriented in approximately the same direction,
                     // the angle betweeen them is near zero and the cosine of the angle
                     // approches 1.0.  Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2).
-                    // We know that the magnitude is at least MULTITOUCH_MIN_TRAVEL because
-                    // we checked it above.
-                    float dot = x1 * x2 + y1 * y2;
-                    float cosine = dot / (magnitude1 * magnitude2); // denominator always > 0
-                    if (cosine > SWIPE_TRANSITION_ANGLE_COSINE) {
+                    float dot = vx1 * vx2 + vy1 * vy2;
+                    float cosine = dot / (speed1 * speed2); // denominator always > 0
+                    if (cosine >= SWIPE_TRANSITION_ANGLE_COSINE) {
+                        // Pointers are moving in the same direction.  Switch to SWIPE.
+#if DEBUG_GESTURES
+                        LOGD("Gestures: PRESS transitioned to SWIPE, "
+                                "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, "
+                                "cosine %0.3f >= %0.3f",
+                                speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED,
+                                cosine, SWIPE_TRANSITION_ANGLE_COSINE);
+#endif
                         mPointerGesture.currentGestureMode = PointerGesture::SWIPE;
+                    } else {
+                        // Pointers are moving in different directions.  Switch to FREEFORM.
+#if DEBUG_GESTURES
+                        LOGD("Gestures: PRESS transitioned to FREEFORM, "
+                                "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, "
+                                "cosine %0.3f < %0.3f",
+                                speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED,
+                                cosine, SWIPE_TRANSITION_ANGLE_COSINE);
+#endif
+                        *outCancelPreviousGesture = true;
+                        mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
                     }
                 }
-
-                // Remember the initial centroid for the duration of the gesture.
-                mPointerGesture.initialCentroidX = 0;
-                mPointerGesture.initialCentroidY = 0;
-                for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
-                    const PointerData& touch = mCurrentTouch.pointers[i];
-                    mPointerGesture.initialCentroidX += touch.x;
-                    mPointerGesture.initialCentroidY += touch.y;
-                }
-                mPointerGesture.initialCentroidX /= int32_t(currentTouchPointerCount);
-                mPointerGesture.initialCentroidY /= int32_t(currentTouchPointerCount);
-
-                mPointerGesture.activeGestureId = 0;
             }
         } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
-            // Switch to FREEFORM if additional pointers go down.
-            if (currentTouchPointerCount > 2) {
+            // Switch from SWIPE to FREEFORM if additional pointers go down.
+            // Cancel previous gesture.
+            if (mCurrentTouch.pointerCount > 2) {
+#if DEBUG_GESTURES
+                LOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+                        mCurrentTouch.pointerCount);
+#endif
                 *outCancelPreviousGesture = true;
                 mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
             }
         }
 
-        if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
-            // SWIPE mode.
+        // Move the reference points based on the overall group motion of the fingers.
+        // The objective is to calculate a vector delta that is common to the movement
+        // of all fingers.
+        BitSet32 commonIdBits(mLastTouch.idBits.value & mCurrentTouch.idBits.value);
+        if (!commonIdBits.isEmpty()) {
+            float commonDeltaX = 0, commonDeltaY = 0;
+            for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) {
+                bool first = (idBits == commonIdBits);
+                uint32_t id = idBits.firstMarkedBit();
+                idBits.clearBit(id);
+
+                const PointerData& cpd = mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
+                const PointerData& lpd = mLastTouch.pointers[mLastTouch.idToIndex[id]];
+                float deltaX = cpd.x - lpd.x;
+                float deltaY = cpd.y - lpd.y;
+
+                if (first) {
+                    commonDeltaX = deltaX;
+                    commonDeltaY = deltaY;
+                } else {
+                    commonDeltaX = calculateCommonVector(commonDeltaX, deltaX);
+                    commonDeltaY = calculateCommonVector(commonDeltaY, deltaY);
+                }
+            }
+
+            mPointerGesture.referenceTouchX += commonDeltaX;
+            mPointerGesture.referenceTouchY += commonDeltaY;
+            mPointerGesture.referenceGestureX +=
+                    commonDeltaX * mLocked.pointerGestureXMovementScale;
+            mPointerGesture.referenceGestureY +=
+                    commonDeltaY * mLocked.pointerGestureYMovementScale;
+            clampPositionUsingPointerBounds(mPointerController,
+                    &mPointerGesture.referenceGestureX,
+                    &mPointerGesture.referenceGestureY);
+        }
+
+        // Report gestures.
+        if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+            // PRESS mode.
 #if DEBUG_GESTURES
-            LOGD("Gestures: SWIPE activeTouchId=%d,"
+            LOGD("Gestures: PRESS activeTouchId=%d,"
                     "activeGestureId=%d, currentTouchPointerCount=%d",
-                    activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount);
+                    activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
 #endif
             LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
 
-            float x = (mCurrentTouch.pointers[0].x + mCurrentTouch.pointers[1].x
-                    - mPointerGesture.initialCentroidX * 2) * 0.5f
-                    * mLocked.pointerGestureXMovementScale + mPointerGesture.initialPointerX;
-            float y = (mCurrentTouch.pointers[0].y + mCurrentTouch.pointers[1].y
-                    - mPointerGesture.initialCentroidY * 2) * 0.5f
-                    * mLocked.pointerGestureYMovementScale + mPointerGesture.initialPointerY;
-
-            mPointerGesture.currentGesturePointerCount = 1;
             mPointerGesture.currentGestureIdBits.clear();
             mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
             mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
             mPointerGesture.currentGestureCoords[0].clear();
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                    mPointerGesture.referenceGestureX);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                    mPointerGesture.referenceGestureY);
             mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+            mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_PRESS;
+            }
+        } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+            // SWIPE mode.
+#if DEBUG_GESTURES
+            LOGD("Gestures: SWIPE activeTouchId=%d,"
+                    "activeGestureId=%d, currentTouchPointerCount=%d",
+                    activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
+#endif
+            LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+            mPointerGesture.currentGestureIdBits.clear();
+            mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+            mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+            mPointerGesture.currentGestureCoords[0].clear();
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                    mPointerGesture.referenceGestureX);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+                    mPointerGesture.referenceGestureY);
+            mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+            mPointerController->setButtonState(0); // touch is not actually following the pointer
+
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_SWIPE;
+            }
         } else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
             // FREEFORM mode.
 #if DEBUG_GESTURES
             LOGD("Gestures: FREEFORM activeTouchId=%d,"
                     "activeGestureId=%d, currentTouchPointerCount=%d",
-                    activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount);
+                    activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
 #endif
             LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
 
-            mPointerGesture.currentGesturePointerCount = currentTouchPointerCount;
             mPointerGesture.currentGestureIdBits.clear();
 
             BitSet32 mappedTouchIdBits;
@@ -3782,7 +4082,7 @@
                     mPointerGesture.activeGestureId);
 #endif
 
-            for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
+            for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
                 uint32_t touchId = mCurrentTouch.pointers[i].id;
                 uint32_t gestureId;
                 if (!mappedTouchIdBits.hasBit(touchId)) {
@@ -3805,10 +4105,10 @@
                 mPointerGesture.currentGestureIdBits.markBit(gestureId);
                 mPointerGesture.currentGestureIdToIndex[gestureId] = i;
 
-                float x = (mCurrentTouch.pointers[i].x - mPointerGesture.initialCentroidX)
-                        * mLocked.pointerGestureXZoomScale + mPointerGesture.initialPointerX;
-                float y = (mCurrentTouch.pointers[i].y - mPointerGesture.initialCentroidY)
-                        * mLocked.pointerGestureYZoomScale + mPointerGesture.initialPointerY;
+                float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX)
+                        * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX;
+                float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY)
+                        * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY;
 
                 mPointerGesture.currentGestureCoords[i].clear();
                 mPointerGesture.currentGestureCoords[i].setAxisValue(
@@ -3827,30 +4127,45 @@
                         "activeGestureId=%d", mPointerGesture.activeGestureId);
 #endif
             }
-        } else {
-            // INDETERMINATE_MULTITOUCH mode.
-            // Do nothing.
-#if DEBUG_GESTURES
-            LOGD("Gestures: INDETERMINATE_MULTITOUCH");
-#endif
-        }
-    }
 
-    // Unfade the pointer if the user is doing anything with the touch pad.
-    mPointerController->setButtonState(mCurrentTouch.buttonState);
-    if (mCurrentTouch.buttonState || mCurrentTouch.pointerCount != 0) {
-        mPointerController->unfade();
+            mPointerController->setButtonState(0); // touch is not actually following the pointer
+
+            if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+                mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_FREEFORM;
+            }
+        }
+
+        // Update spot locations for PRESS, SWIPE and FREEFORM.
+        // We use the same calculation as we do to calculate the gesture pointers
+        // for FREEFORM so that the spots smoothly track gestures.
+        if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+            mPointerGesture.spotIdBits.clear();
+            for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
+                uint32_t id = mCurrentTouch.pointers[i].id;
+                mPointerGesture.spotIdBits.markBit(id);
+                mPointerGesture.spotIdToIndex[id] = i;
+
+                float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX)
+                        * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX;
+                float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY)
+                        * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY;
+
+                mPointerGesture.spotCoords[i].clear();
+                mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+                mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+                mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+            }
+            moveSpotsLocked();
+        }
     }
 
 #if DEBUG_GESTURES
     LOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
-            "currentGestureMode=%d, currentGesturePointerCount=%d, currentGestureIdBits=0x%08x, "
-            "lastGestureMode=%d, lastGesturePointerCount=%d, lastGestureIdBits=0x%08x",
+            "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+            "lastGestureMode=%d, lastGestureIdBits=0x%08x",
             toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
-            mPointerGesture.currentGestureMode, mPointerGesture.currentGesturePointerCount,
-            mPointerGesture.currentGestureIdBits.value,
-            mPointerGesture.lastGestureMode, mPointerGesture.lastGesturePointerCount,
-            mPointerGesture.lastGestureIdBits.value);
+            mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+            mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
     for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) {
         uint32_t id = idBits.firstMarkedBit();
         idBits.clearBit(id);
@@ -3872,6 +4187,12 @@
                 coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
     }
 #endif
+    return true;
+}
+
+void TouchInputMapper::moveSpotsLocked() {
+    mPointerController->setSpots(mPointerGesture.spotGesture,
+            mPointerGesture.spotCoords, mPointerGesture.spotIdToIndex, mPointerGesture.spotIdBits);
 }
 
 void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 9ed1391..0485617 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -20,7 +20,6 @@
 #include "EventHub.h"
 #include "InputDispatcher.h"
 #include "PointerController.h"
-#include "SpotController.h"
 
 #include <ui/Input.h>
 #include <ui/DisplayInfo.h>
@@ -90,9 +89,6 @@
 
     /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
-
-    /* Gets a spot controller associated with the specified touch pad device. */
-    virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId) = 0;
 };
 
 
@@ -574,6 +570,7 @@
             const int32_t* keyCodes, uint8_t* outFlags);
 
     virtual void fadePointer();
+    virtual void timeoutExpired(nsecs_t when);
 
 protected:
     Mutex mLock;
@@ -648,6 +645,20 @@
             idBits.clear();
             buttonState = 0;
         }
+
+        void getCentroid(float* outX, float* outY) {
+            float x = 0, y = 0;
+            if (pointerCount != 0) {
+                for (uint32_t i = 0; i < pointerCount; i++) {
+                    x += pointers[i].x;
+                    y += pointers[i].y;
+                }
+                x /= pointerCount;
+                y /= pointerCount;
+            }
+            *outX = x;
+            *outY = y;
+        }
     };
 
     // Input sources supported by the device.
@@ -670,6 +681,12 @@
         bool useJumpyTouchFilter;
         bool useAveragingTouchFilter;
         nsecs_t virtualKeyQuietTime;
+
+        enum GestureMode {
+            GESTURE_MODE_POINTER,
+            GESTURE_MODE_SPOTS,
+        };
+        GestureMode gestureMode;
     } mParameters;
 
     // Immutable calibration parameters in parsed form.
@@ -841,8 +858,8 @@
         float pointerGestureXZoomScale;
         float pointerGestureYZoomScale;
 
-        // The maximum swipe width squared.
-        int32_t pointerGestureMaxSwipeWidthSquared;
+        // The maximum swipe width.
+        float pointerGestureMaxSwipeWidth;
     } mLocked;
 
     virtual void configureParameters();
@@ -919,38 +936,47 @@
             // Emits DOWN and UP events at the pointer location.
             TAP,
 
+            // Exactly one finger dragging following a tap.
+            // Pointer follows the active finger.
+            // Emits DOWN, MOVE and UP events at the pointer location.
+            TAP_DRAG,
+
             // Button is pressed.
             // Pointer follows the active finger if there is one.  Other fingers are ignored.
             // Emits DOWN, MOVE and UP events at the pointer location.
-            CLICK_OR_DRAG,
+            BUTTON_CLICK_OR_DRAG,
 
             // Exactly one finger, button is not pressed.
             // Pointer follows the active finger.
             // Emits HOVER_MOVE events at the pointer location.
             HOVER,
 
-            // More than two fingers involved but they haven't moved enough for us
-            // to figure out what is intended.
-            INDETERMINATE_MULTITOUCH,
+            // Exactly two fingers but neither have moved enough to clearly indicate
+            // whether a swipe or freeform gesture was intended.  We consider the
+            // pointer to be pressed so this enables clicking or long-pressing on buttons.
+            // Pointer does not move.
+            // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate.
+            PRESS,
 
             // Exactly two fingers moving in the same direction, button is not pressed.
             // Pointer does not move.
             // Emits DOWN, MOVE and UP events with a single pointer coordinate that
             // follows the midpoint between both fingers.
-            // The centroid is fixed when entering this state.
             SWIPE,
 
             // Two or more fingers moving in arbitrary directions, button is not pressed.
             // Pointer does not move.
             // Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
             // each finger individually relative to the initial centroid of the finger.
-            // The centroid is fixed when entering this state.
             FREEFORM,
 
             // Waiting for quiet time to end before starting the next gesture.
             QUIET,
         };
 
+        // Time the first finger went down.
+        nsecs_t firstTouchTime;
+
         // The active pointer id from the raw touch data.
         int32_t activeTouchId; // -1 if none
 
@@ -959,67 +985,67 @@
 
         // Pointer coords and ids for the current and previous pointer gesture.
         Mode currentGestureMode;
-        uint32_t currentGesturePointerCount;
         BitSet32 currentGestureIdBits;
         uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
         PointerCoords currentGestureCoords[MAX_POINTERS];
 
         Mode lastGestureMode;
-        uint32_t lastGesturePointerCount;
         BitSet32 lastGestureIdBits;
         uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
         PointerCoords lastGestureCoords[MAX_POINTERS];
 
-        // Tracks for all pointers originally went down.
-        TouchData touchOrigin;
-
-        // Describes how touch ids are mapped to gesture ids for freeform gestures.
-        uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
-
-        // Initial centroid of the movement.
-        // Used to calculate how far the touch pointers have moved since the gesture started.
-        int32_t initialCentroidX;
-        int32_t initialCentroidY;
-
-        // Initial pointer location.
-        // Used to track where the pointer was when the gesture started.
-        float initialPointerX;
-        float initialPointerY;
+        // Pointer coords and ids for the current spots.
+        PointerControllerInterface::SpotGesture spotGesture;
+        BitSet32 spotIdBits; // same set of ids as touch ids
+        uint32_t spotIdToIndex[MAX_POINTER_ID + 1];
+        PointerCoords spotCoords[MAX_POINTERS];
 
         // Time the pointer gesture last went down.
         nsecs_t downTime;
 
-        // Time we started waiting for a tap gesture.
-        nsecs_t tapTime;
+        // Time when the pointer went down for a TAP.
+        nsecs_t tapDownTime;
+
+        // Time when the pointer went up for a TAP.
+        nsecs_t tapUpTime;
+
+        // Location of initial tap.
+        float tapX, tapY;
 
         // Time we started waiting for quiescence.
         nsecs_t quietTime;
 
+        // Reference points for multitouch gestures.
+        float referenceTouchX;    // reference touch X/Y coordinates in surface units
+        float referenceTouchY;
+        float referenceGestureX;  // reference gesture X/Y coordinates in pixels
+        float referenceGestureY;
+
+        // Describes how touch ids are mapped to gesture ids for freeform gestures.
+        uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
+
         // A velocity tracker for determining whether to switch active pointers during drags.
         VelocityTracker velocityTracker;
 
         void reset() {
+            firstTouchTime = LLONG_MIN;
             activeTouchId = -1;
             activeGestureId = -1;
             currentGestureMode = NEUTRAL;
-            currentGesturePointerCount = 0;
             currentGestureIdBits.clear();
             lastGestureMode = NEUTRAL;
-            lastGesturePointerCount = 0;
             lastGestureIdBits.clear();
-            touchOrigin.clear();
-            initialCentroidX = 0;
-            initialCentroidY = 0;
-            initialPointerX = 0;
-            initialPointerY = 0;
+            spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+            spotIdBits.clear();
             downTime = 0;
             velocityTracker.clear();
-            resetTapTime();
+            resetTap();
             resetQuietTime();
         }
 
-        void resetTapTime() {
-            tapTime = LLONG_MIN;
+        void resetTap() {
+            tapDownTime = LLONG_MIN;
+            tapUpTime = LLONG_MIN;
         }
 
         void resetQuietTime() {
@@ -1032,9 +1058,10 @@
     TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags);
     void dispatchTouches(nsecs_t when, uint32_t policyFlags);
     void prepareTouches(int32_t* outEdgeFlags, float* outXPrecision, float* outYPrecision);
-    void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags);
-    void preparePointerGestures(nsecs_t when,
-            bool* outCancelPreviousGesture, bool* outFinishPreviousGesture);
+    void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout);
+    bool preparePointerGestures(nsecs_t when,
+            bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout);
+    void moveSpotsLocked();
 
     // Dispatches a motion event.
     // If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
index 15effb7..ffef720 100644
--- a/services/input/PointerController.cpp
+++ b/services/input/PointerController.cpp
@@ -36,40 +36,49 @@
 // --- PointerController ---
 
 // Time to wait before starting the fade when the pointer is inactive.
-static const nsecs_t INACTIVITY_FADE_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
-static const nsecs_t INACTIVITY_FADE_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+
+// Time to wait between animation frames.
+static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
+
+// Time to spend fading out the spot completely.
+static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
 
 // Time to spend fading out the pointer completely.
-static const nsecs_t FADE_DURATION = 500 * 1000000LL; // 500 ms
-
-// Time to wait between frames.
-static const nsecs_t FADE_FRAME_INTERVAL = 1000000000LL / 60;
-
-// Amount to subtract from alpha per frame.
-static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION;
+static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
 
 
-PointerController::PointerController(const sp<Looper>& looper,
-        const sp<SpriteController>& spriteController) :
-        mLooper(looper), mSpriteController(spriteController) {
+// --- PointerController ---
+
+PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
+        const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
+        mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
     mHandler = new WeakMessageHandler(this);
 
     AutoMutex _l(mLock);
 
+    mLocked.animationPending = false;
+
     mLocked.displayWidth = -1;
     mLocked.displayHeight = -1;
     mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
 
+    mLocked.presentation = PRESENTATION_POINTER;
+    mLocked.presentationChanged = false;
+
+    mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
+
+    mLocked.pointerIsFading = true; // keep the pointer initially faded
     mLocked.pointerX = 0;
     mLocked.pointerY = 0;
+    mLocked.pointerAlpha = 0.0f;
+    mLocked.pointerSprite = mSpriteController->createSprite();
+    mLocked.pointerIconChanged = false;
+
     mLocked.buttonState = 0;
 
-    mLocked.fadeAlpha = 1;
-    mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL;
-
-    mLocked.visible = false;
-
-    mLocked.sprite = mSpriteController->createSprite();
+    loadResources();
 }
 
 PointerController::~PointerController() {
@@ -77,7 +86,13 @@
 
     AutoMutex _l(mLock);
 
-    mLocked.sprite.clear();
+    mLocked.pointerSprite.clear();
+
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        delete mLocked.spots.itemAt(i);
+    }
+    mLocked.spots.clear();
+    mLocked.recycledSprites.clear();
 }
 
 bool PointerController::getBounds(float* outMinX, float* outMinY,
@@ -130,8 +145,6 @@
 
     if (mLocked.buttonState != buttonState) {
         mLocked.buttonState = buttonState;
-        unfadeBeforeUpdateLocked();
-        updateLocked();
     }
 }
 
@@ -167,8 +180,7 @@
         } else {
             mLocked.pointerY = y;
         }
-        unfadeBeforeUpdateLocked();
-        updateLocked();
+        updatePointerLocked();
     }
 }
 
@@ -182,32 +194,105 @@
 void PointerController::fade() {
     AutoMutex _l(mLock);
 
-    startFadeLocked();
+    sendImmediateInactivityTimeoutLocked();
 }
 
 void PointerController::unfade() {
     AutoMutex _l(mLock);
 
-    if (unfadeBeforeUpdateLocked()) {
-        updateLocked();
+    // Always reset the inactivity timer.
+    resetInactivityTimeoutLocked();
+
+    // Unfade immediately if needed.
+    if (mLocked.pointerIsFading) {
+        mLocked.pointerIsFading = false;
+        mLocked.pointerAlpha = 1.0f;
+        updatePointerLocked();
     }
 }
 
-void PointerController::setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay) {
+void PointerController::setPresentation(Presentation presentation) {
     AutoMutex _l(mLock);
 
-    if (mLocked.inactivityFadeDelay != inactivityFadeDelay) {
-        mLocked.inactivityFadeDelay = inactivityFadeDelay;
-        startInactivityFadeDelayLocked();
+    if (mLocked.presentation != presentation) {
+        mLocked.presentation = presentation;
+        mLocked.presentationChanged = true;
+
+        if (presentation != PRESENTATION_SPOT) {
+            fadeOutAndReleaseAllSpotsLocked();
+        }
+
+        updatePointerLocked();
     }
 }
 
-void PointerController::updateLocked() {
-    mLocked.sprite->openTransaction();
-    mLocked.sprite->setPosition(mLocked.pointerX, mLocked.pointerY);
-    mLocked.sprite->setAlpha(mLocked.fadeAlpha);
-    mLocked.sprite->setVisible(mLocked.visible);
-    mLocked.sprite->closeTransaction();
+void PointerController::setSpots(SpotGesture spotGesture,
+        const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+#if DEBUG_POINTER_UPDATES
+    LOGD("setSpots: spotGesture=%d", spotGesture);
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+        uint32_t id = idBits.firstMarkedBit();
+        idBits.clearBit(id);
+        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+        LOGD("  spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id,
+                c.getAxisValue(AMOTION_EVENT_AXIS_X),
+                c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+                c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+    }
+#endif
+
+    AutoMutex _l(mLock);
+
+    mSpriteController->openTransaction();
+
+    // Add or move spots for fingers that are down.
+    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+        uint32_t id = idBits.firstMarkedBit();
+        idBits.clearBit(id);
+
+        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+        const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+                ? mResources.spotTouch : mResources.spotHover;
+        float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+        float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+        Spot* spot = getSpotLocked(id);
+        if (!spot) {
+            spot = createAndAddSpotLocked(id);
+        }
+
+        spot->updateSprite(&icon, x, y);
+    }
+
+    // Remove spots for fingers that went up.
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        if (spot->id != Spot::INVALID_ID
+                && !spotIdBits.hasBit(spot->id)) {
+            fadeOutAndReleaseSpotLocked(spot);
+        }
+    }
+
+    mSpriteController->closeTransaction();
+}
+
+void PointerController::clearSpots() {
+#if DEBUG_POINTER_UPDATES
+    LOGD("clearSpots");
+#endif
+
+    AutoMutex _l(mLock);
+
+    fadeOutAndReleaseAllSpotsLocked();
+}
+
+void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+    AutoMutex _l(mLock);
+
+    if (mLocked.inactivityTimeout != inactivityTimeout) {
+        mLocked.inactivityTimeout = inactivityTimeout;
+        resetInactivityTimeoutLocked();
+    }
 }
 
 void PointerController::setDisplaySize(int32_t width, int32_t height) {
@@ -226,7 +311,8 @@
             mLocked.pointerY = 0;
         }
 
-        updateLocked();
+        fadeOutAndReleaseAllSpotsLocked();
+        updatePointerLocked();
     }
 }
 
@@ -283,74 +369,217 @@
         mLocked.pointerY = y - 0.5f;
         mLocked.displayOrientation = orientation;
 
-        updateLocked();
+        updatePointerLocked();
     }
 }
 
-void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) {
+void PointerController::setPointerIcon(const SpriteIcon& icon) {
     AutoMutex _l(mLock);
 
-    mLocked.sprite->setBitmap(bitmap, hotSpotX, hotSpotY);
+    mLocked.pointerIcon = icon.copy();
+    mLocked.pointerIconChanged = true;
+
+    updatePointerLocked();
 }
 
 void PointerController::handleMessage(const Message& message) {
     switch (message.what) {
-    case MSG_FADE_STEP: {
-        AutoMutex _l(mLock);
-        fadeStepLocked();
+    case MSG_ANIMATE:
+        doAnimate();
+        break;
+    case MSG_INACTIVITY_TIMEOUT:
+        doInactivityTimeout();
         break;
     }
-    }
 }
 
-bool PointerController::unfadeBeforeUpdateLocked() {
-    sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
+void PointerController::doAnimate() {
+    AutoMutex _l(mLock);
 
-    if (isFadingLocked()) {
-        mLocked.visible = true;
-        mLocked.fadeAlpha = 1;
-        return true; // update required to effect the unfade
-    }
-    return false; // update not required
-}
+    bool keepAnimating = false;
+    mLocked.animationPending = false;
+    nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
 
-void PointerController::startFadeLocked() {
-    if (!isFadingLocked()) {
-        sendFadeStepMessageDelayedLocked(0);
-    }
-}
-
-void PointerController::startInactivityFadeDelayLocked() {
-    if (!isFadingLocked()) {
-        sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
-    }
-}
-
-void PointerController::fadeStepLocked() {
-    if (mLocked.visible) {
-        mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME;
-        if (mLocked.fadeAlpha < 0) {
-            mLocked.fadeAlpha = 0;
-            mLocked.visible = false;
+    // Animate pointer fade.
+    if (mLocked.pointerIsFading) {
+        mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+        if (mLocked.pointerAlpha <= 0) {
+            mLocked.pointerAlpha = 0;
         } else {
-            sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL);
+            keepAnimating = true;
         }
-        updateLocked();
+        updatePointerLocked();
+    }
+
+    // Animate spots that are fading out and being removed.
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        if (spot->id == Spot::INVALID_ID) {
+            spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+            if (spot->alpha <= 0) {
+                mLocked.spots.removeAt(i--);
+                releaseSpotLocked(spot);
+            } else {
+                spot->sprite->setAlpha(spot->alpha);
+                keepAnimating = true;
+            }
+        }
+    }
+
+    if (keepAnimating) {
+        startAnimationLocked();
     }
 }
 
-bool PointerController::isFadingLocked() {
-    return !mLocked.visible || mLocked.fadeAlpha != 1;
+void PointerController::doInactivityTimeout() {
+    AutoMutex _l(mLock);
+
+    if (!mLocked.pointerIsFading) {
+        mLocked.pointerIsFading = true;
+        startAnimationLocked();
+    }
 }
 
-nsecs_t PointerController::getInactivityFadeDelayTimeLocked() {
-    return mLocked.inactivityFadeDelay == INACTIVITY_FADE_DELAY_SHORT
-            ? INACTIVITY_FADE_DELAY_TIME_SHORT : INACTIVITY_FADE_DELAY_TIME_NORMAL;
+void PointerController::startAnimationLocked() {
+    if (!mLocked.animationPending) {
+        mLocked.animationPending = true;
+        mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+        mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+    }
 }
 
-void PointerController::sendFadeStepMessageDelayedLocked(nsecs_t delayTime) {
-    mLooper->removeMessages(mHandler, MSG_FADE_STEP);
-    mLooper->sendMessageDelayed(delayTime, mHandler, Message(MSG_FADE_STEP));
+void PointerController::resetInactivityTimeoutLocked() {
+    mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+
+    nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT
+            ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+    mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::sendImmediateInactivityTimeoutLocked() {
+    mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+    mLooper->sendMessage(mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::updatePointerLocked() {
+    mSpriteController->openTransaction();
+
+    mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+    mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+
+    if (mLocked.pointerAlpha > 0) {
+        mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+        mLocked.pointerSprite->setVisible(true);
+    } else {
+        mLocked.pointerSprite->setVisible(false);
+    }
+
+    if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
+        mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
+                ? mLocked.pointerIcon : mResources.spotAnchor);
+        mLocked.pointerIconChanged = false;
+        mLocked.presentationChanged = false;
+    }
+
+    mSpriteController->closeTransaction();
+}
+
+PointerController::Spot* PointerController::getSpotLocked(uint32_t id) {
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        if (spot->id == id) {
+            return spot;
+        }
+    }
+    return NULL;
+}
+
+PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) {
+    // Remove spots until we have fewer than MAX_SPOTS remaining.
+    while (mLocked.spots.size() >= MAX_SPOTS) {
+        Spot* spot = removeFirstFadingSpotLocked();
+        if (!spot) {
+            spot = mLocked.spots.itemAt(0);
+            mLocked.spots.removeAt(0);
+        }
+        releaseSpotLocked(spot);
+    }
+
+    // Obtain a sprite from the recycled pool.
+    sp<Sprite> sprite;
+    if (! mLocked.recycledSprites.isEmpty()) {
+        sprite = mLocked.recycledSprites.top();
+        mLocked.recycledSprites.pop();
+    } else {
+        sprite = mSpriteController->createSprite();
+    }
+
+    // Return the new spot.
+    Spot* spot = new Spot(id, sprite);
+    mLocked.spots.push(spot);
+    return spot;
+}
+
+PointerController::Spot* PointerController::removeFirstFadingSpotLocked() {
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        if (spot->id == Spot::INVALID_ID) {
+            mLocked.spots.removeAt(i);
+            return spot;
+        }
+    }
+    return NULL;
+}
+
+void PointerController::releaseSpotLocked(Spot* spot) {
+    spot->sprite->clearIcon();
+
+    if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+        mLocked.recycledSprites.push(spot->sprite);
+    }
+
+    delete spot;
+}
+
+void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
+    if (spot->id != Spot::INVALID_ID) {
+        spot->id = Spot::INVALID_ID;
+        startAnimationLocked();
+    }
+}
+
+void PointerController::fadeOutAndReleaseAllSpotsLocked() {
+    for (size_t i = 0; i < mLocked.spots.size(); i++) {
+        Spot* spot = mLocked.spots.itemAt(i);
+        fadeOutAndReleaseSpotLocked(spot);
+    }
+}
+
+void PointerController::loadResources() {
+    mPolicy->loadPointerResources(&mResources);
+}
+
+
+// --- PointerController::Spot ---
+
+void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) {
+    sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+    sprite->setAlpha(alpha);
+    sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+    sprite->setPosition(x, y);
+
+    this->x = x;
+    this->y = y;
+
+    if (icon != lastIcon) {
+        lastIcon = icon;
+        if (icon) {
+            sprite->setIcon(*icon);
+            sprite->setVisible(true);
+        } else {
+            sprite->setVisible(false);
+        }
+    }
 }
 
 } // namespace android
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
index d467a5a..b9184ac 100644
--- a/services/input/PointerController.h
+++ b/services/input/PointerController.h
@@ -30,7 +30,10 @@
 namespace android {
 
 /**
- * Interface for tracking a single (mouse) pointer.
+ * Interface for tracking a mouse / touch pad pointer and touch pad spots.
+ *
+ * The spots are sprites on screen that visually represent the positions of
+ * fingers
  *
  * The pointer controller is responsible for providing synchronization and for tracking
  * display orientation changes if needed.
@@ -64,8 +67,98 @@
     /* Fades the pointer out now. */
     virtual void fade() = 0;
 
-    /* Makes the pointer visible if it has faded out. */
+    /* Makes the pointer visible if it has faded out.
+     * The pointer never unfades itself automatically.  This method must be called
+     * by the client whenever the pointer is moved or a button is pressed and it
+     * wants to ensure that the pointer becomes visible again. */
     virtual void unfade() = 0;
+
+    enum Presentation {
+        // Show the mouse pointer.
+        PRESENTATION_POINTER,
+        // Show spots and a spot anchor in place of the mouse pointer.
+        PRESENTATION_SPOT,
+    };
+
+    /* Sets the mode of the pointer controller. */
+    virtual void setPresentation(Presentation presentation) = 0;
+
+    // Describes the current gesture.
+    enum SpotGesture {
+        // No gesture.
+        // Do not display any spots.
+        SPOT_GESTURE_NEUTRAL,
+        // Tap at current location.
+        // Briefly display one spot at the tapped location.
+        SPOT_GESTURE_TAP,
+        // Drag at current location.
+        // Display spot at pressed location.
+        SPOT_GESTURE_DRAG,
+        // Button pressed but no finger is down.
+        // Display spot at pressed location.
+        SPOT_GESTURE_BUTTON_CLICK,
+        // Button pressed and a finger is down.
+        // Display spot at pressed location.
+        SPOT_GESTURE_BUTTON_DRAG,
+        // One finger down and hovering.
+        // Display spot at the hovered location.
+        SPOT_GESTURE_HOVER,
+        // Two fingers down but not sure in which direction they are moving so we consider
+        // it a press at the pointer location.
+        // Display two spots near the pointer location.
+        SPOT_GESTURE_PRESS,
+        // Two fingers down and moving in same direction.
+        // Display two spots near the pointer location.
+        SPOT_GESTURE_SWIPE,
+        // Two or more fingers down and moving in arbitrary directions.
+        // Display two or more spots near the pointer location, one for each finger.
+        SPOT_GESTURE_FREEFORM,
+    };
+
+    /* Sets the spots for the current gesture.
+     * The spots are not subject to the inactivity timeout like the pointer
+     * itself it since they are expected to remain visible for so long as
+     * the fingers are on the touch pad.
+     *
+     * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant.
+     * For spotCoords, pressure != 0 indicates that the spot's location is being
+     * pressed (not hovering).
+     */
+    virtual void setSpots(SpotGesture spotGesture,
+            const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+            BitSet32 spotIdBits) = 0;
+
+    /* Removes all spots. */
+    virtual void clearSpots() = 0;
+};
+
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+    SpriteIcon spotHover;
+    SpriteIcon spotTouch;
+    SpriteIcon spotAnchor;
+};
+
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI.  This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+    PointerControllerPolicyInterface() { }
+    virtual ~PointerControllerPolicyInterface() { }
+
+public:
+    virtual void loadPointerResources(PointerResources* outResources) = 0;
 };
 
 
@@ -79,12 +172,13 @@
     virtual ~PointerController();
 
 public:
-    enum InactivityFadeDelay {
-        INACTIVITY_FADE_DELAY_NORMAL = 0,
-        INACTIVITY_FADE_DELAY_SHORT = 1,
+    enum InactivityTimeout {
+        INACTIVITY_TIMEOUT_NORMAL = 0,
+        INACTIVITY_TIMEOUT_SHORT = 1,
     };
 
-    PointerController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+    PointerController(const sp<PointerControllerPolicyInterface>& policy,
+            const sp<Looper>& looper, const sp<SpriteController>& spriteController);
 
     virtual bool getBounds(float* outMinX, float* outMinY,
             float* outMaxX, float* outMaxY) const;
@@ -96,51 +190,101 @@
     virtual void fade();
     virtual void unfade();
 
+    virtual void setPresentation(Presentation presentation);
+    virtual void setSpots(SpotGesture spotGesture,
+            const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
+    virtual void clearSpots();
+
     void setDisplaySize(int32_t width, int32_t height);
     void setDisplayOrientation(int32_t orientation);
-    void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
-    void setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay);
+    void setPointerIcon(const SpriteIcon& icon);
+    void setInactivityTimeout(InactivityTimeout inactivityTimeout);
 
 private:
+    static const size_t MAX_RECYCLED_SPRITES = 12;
+    static const size_t MAX_SPOTS = 12;
+
     enum {
-        MSG_FADE_STEP = 0,
+        MSG_ANIMATE,
+        MSG_INACTIVITY_TIMEOUT,
+    };
+
+    struct Spot {
+        static const uint32_t INVALID_ID = 0xffffffff;
+
+        uint32_t id;
+        sp<Sprite> sprite;
+        float alpha;
+        float scale;
+        float x, y;
+
+        inline Spot(uint32_t id, const sp<Sprite>& sprite)
+                : id(id), sprite(sprite), alpha(1.0f), scale(1.0f),
+                  x(0.0f), y(0.0f), lastIcon(NULL) { }
+
+        void updateSprite(const SpriteIcon* icon, float x, float y);
+
+    private:
+        const SpriteIcon* lastIcon;
     };
 
     mutable Mutex mLock;
 
+    sp<PointerControllerPolicyInterface> mPolicy;
     sp<Looper> mLooper;
     sp<SpriteController> mSpriteController;
     sp<WeakMessageHandler> mHandler;
 
+    PointerResources mResources;
+
     struct Locked {
+        bool animationPending;
+        nsecs_t animationTime;
+
         int32_t displayWidth;
         int32_t displayHeight;
         int32_t displayOrientation;
 
+        InactivityTimeout inactivityTimeout;
+
+        Presentation presentation;
+        bool presentationChanged;
+
+        bool pointerIsFading;
         float pointerX;
         float pointerY;
+        float pointerAlpha;
+        sp<Sprite> pointerSprite;
+        SpriteIcon pointerIcon;
+        bool pointerIconChanged;
+
         uint32_t buttonState;
 
-        float fadeAlpha;
-        InactivityFadeDelay inactivityFadeDelay;
-
-        bool visible;
-
-        sp<Sprite> sprite;
+        Vector<Spot*> spots;
+        Vector<sp<Sprite> > recycledSprites;
     } mLocked;
 
     bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
     void setPositionLocked(float x, float y);
-    void updateLocked();
 
     void handleMessage(const Message& message);
-    bool unfadeBeforeUpdateLocked();
-    void startFadeLocked();
-    void startInactivityFadeDelayLocked();
-    void fadeStepLocked();
-    bool isFadingLocked();
-    nsecs_t getInactivityFadeDelayTimeLocked();
-    void sendFadeStepMessageDelayedLocked(nsecs_t delayTime);
+    void doAnimate();
+    void doInactivityTimeout();
+
+    void startAnimationLocked();
+
+    void resetInactivityTimeoutLocked();
+    void sendImmediateInactivityTimeoutLocked();
+    void updatePointerLocked();
+
+    Spot* getSpotLocked(uint32_t id);
+    Spot* createAndAddSpotLocked(uint32_t id);
+    Spot* removeFirstFadingSpotLocked();
+    void releaseSpotLocked(Spot* spot);
+    void fadeOutAndReleaseSpotLocked(Spot* spot);
+    void fadeOutAndReleaseAllSpotsLocked();
+
+    void loadResources();
 };
 
 } // namespace android
diff --git a/services/input/SpotController.cpp b/services/input/SpotController.cpp
deleted file mode 100644
index dffad81..0000000
--- a/services/input/SpotController.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "SpotController"
-
-//#define LOG_NDEBUG 0
-
-// Log debug messages about spot updates
-#define DEBUG_SPOT_UPDATES 0
-
-#include "SpotController.h"
-
-#include <cutils/log.h>
-
-namespace android {
-
-// --- SpotController ---
-
-SpotController::SpotController(const sp<Looper>& looper,
-        const sp<SpriteController>& spriteController) :
-        mLooper(looper), mSpriteController(spriteController) {
-    mHandler = new WeakMessageHandler(this);
-}
-
-SpotController::~SpotController() {
-    mLooper->removeMessages(mHandler);
-}
-
-void SpotController:: handleMessage(const Message& message) {
-}
-
-} // namespace android
diff --git a/services/input/SpotController.h b/services/input/SpotController.h
deleted file mode 100644
index 1d091d7..0000000
--- a/services/input/SpotController.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _UI_SPOT_CONTROLLER_H
-#define _UI_SPOT_CONTROLLER_H
-
-#include "SpriteController.h"
-
-#include <utils/RefBase.h>
-#include <utils/Looper.h>
-
-#include <SkBitmap.h>
-
-namespace android {
-
-/*
- * Interface for displaying spots on screen that visually represent the positions
- * of fingers on a touch pad.
- *
- * The spot controller is responsible for providing synchronization and for tracking
- * display orientation changes if needed.
- */
-class SpotControllerInterface : public virtual RefBase {
-protected:
-    SpotControllerInterface() { }
-    virtual ~SpotControllerInterface() { }
-
-public:
-
-};
-
-
-/*
- * Sprite-based spot controller implementation.
- */
-class SpotController : public SpotControllerInterface, public MessageHandler {
-protected:
-    virtual ~SpotController();
-
-public:
-    SpotController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
-
-private:
-    mutable Mutex mLock;
-
-    sp<Looper> mLooper;
-    sp<SpriteController> mSpriteController;
-    sp<WeakMessageHandler> mHandler;
-
-    struct Locked {
-    } mLocked;
-
-    void handleMessage(const Message& message);
-};
-
-} // namespace android
-
-#endif // _UI_SPOT_CONTROLLER_H
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
index c6d4390f..2fd1f0a 100644
--- a/services/input/SpriteController.cpp
+++ b/services/input/SpriteController.cpp
@@ -36,6 +36,9 @@
 SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
         mLooper(looper), mOverlayLayer(overlayLayer) {
     mHandler = new WeakMessageHandler(this);
+
+    mLocked.transactionNestingCount = 0;
+    mLocked.deferredSpriteUpdate = false;
 }
 
 SpriteController::~SpriteController() {
@@ -51,17 +54,40 @@
     return new SpriteImpl(this);
 }
 
-void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
-    bool wasEmpty = mInvalidatedSprites.isEmpty();
-    mInvalidatedSprites.push(sprite);
-    if (wasEmpty) {
+void SpriteController::openTransaction() {
+    AutoMutex _l(mLock);
+
+    mLocked.transactionNestingCount += 1;
+}
+
+void SpriteController::closeTransaction() {
+    AutoMutex _l(mLock);
+
+    LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0,
+            "Sprite closeTransaction() called but there is no open sprite transaction");
+
+    mLocked.transactionNestingCount -= 1;
+    if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
+        mLocked.deferredSpriteUpdate = false;
         mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
     }
 }
 
+void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
+    bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
+    mLocked.invalidatedSprites.push(sprite);
+    if (wasEmpty) {
+        if (mLocked.transactionNestingCount != 0) {
+            mLocked.deferredSpriteUpdate = true;
+        } else {
+            mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+        }
+    }
+}
+
 void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
-    bool wasEmpty = mDisposedSurfaces.isEmpty();
-    mDisposedSurfaces.push(surfaceControl);
+    bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
+    mLocked.disposedSurfaces.push(surfaceControl);
     if (wasEmpty) {
         mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
     }
@@ -89,14 +115,14 @@
     { // acquire lock
         AutoMutex _l(mLock);
 
-        numSprites = mInvalidatedSprites.size();
+        numSprites = mLocked.invalidatedSprites.size();
         for (size_t i = 0; i < numSprites; i++) {
-            const sp<SpriteImpl>& sprite = mInvalidatedSprites.itemAt(i);
+            const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
 
             updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
             sprite->resetDirtyLocked();
         }
-        mInvalidatedSprites.clear();
+        mLocked.invalidatedSprites.clear();
     } // release lock
 
     // Create missing surfaces.
@@ -105,8 +131,8 @@
         SpriteUpdate& update = updates.editItemAt(i);
 
         if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
-            update.state.surfaceWidth = update.state.bitmap.width();
-            update.state.surfaceHeight = update.state.bitmap.height();
+            update.state.surfaceWidth = update.state.icon.bitmap.width();
+            update.state.surfaceHeight = update.state.icon.bitmap.height();
             update.state.surfaceDrawn = false;
             update.state.surfaceVisible = false;
             update.state.surfaceControl = obtainSurface(
@@ -123,8 +149,8 @@
         SpriteUpdate& update = updates.editItemAt(i);
 
         if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
-            int32_t desiredWidth = update.state.bitmap.width();
-            int32_t desiredHeight = update.state.bitmap.height();
+            int32_t desiredWidth = update.state.icon.bitmap.width();
+            int32_t desiredHeight = update.state.icon.bitmap.height();
             if (update.state.surfaceWidth < desiredWidth
                     || update.state.surfaceHeight < desiredHeight) {
                 if (!haveGlobalTransaction) {
@@ -187,16 +213,16 @@
 
                 SkPaint paint;
                 paint.setXfermodeMode(SkXfermode::kSrc_Mode);
-                surfaceCanvas.drawBitmap(update.state.bitmap, 0, 0, &paint);
+                surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
 
-                if (surfaceInfo.w > uint32_t(update.state.bitmap.width())) {
+                if (surfaceInfo.w > uint32_t(update.state.icon.bitmap.width())) {
                     paint.setColor(0); // transparent fill color
-                    surfaceCanvas.drawRectCoords(update.state.bitmap.width(), 0,
-                            surfaceInfo.w, update.state.bitmap.height(), paint);
+                    surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
+                            surfaceInfo.w, update.state.icon.bitmap.height(), paint);
                 }
-                if (surfaceInfo.h > uint32_t(update.state.bitmap.height())) {
+                if (surfaceInfo.h > uint32_t(update.state.icon.bitmap.height())) {
                     paint.setColor(0); // transparent fill color
-                    surfaceCanvas.drawRectCoords(0, update.state.bitmap.height(),
+                    surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
                             surfaceInfo.w, surfaceInfo.h, paint);
                 }
 
@@ -246,8 +272,8 @@
                     && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
                             | DIRTY_HOTSPOT)))) {
                 status = update.state.surfaceControl->setPosition(
-                        update.state.positionX - update.state.hotSpotX,
-                        update.state.positionY - update.state.hotSpotY);
+                        update.state.positionX - update.state.icon.hotSpotX,
+                        update.state.positionY - update.state.icon.hotSpotY);
                 if (status) {
                     LOGE("Error %d setting sprite surface position.", status);
                 }
@@ -329,8 +355,10 @@
     // Collect disposed surfaces.
     Vector<sp<SurfaceControl> > disposedSurfaces;
     { // acquire lock
-        disposedSurfaces = mDisposedSurfaces;
-        mDisposedSurfaces.clear();
+        AutoMutex _l(mLock);
+
+        disposedSurfaces = mLocked.disposedSurfaces;
+        mLocked.disposedSurfaces.clear();
     } // release lock
 
     // Release the last reference to each surface outside of the lock.
@@ -349,7 +377,8 @@
 
     sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
             getpid(), String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888);
-    if (surfaceControl == NULL) {
+    if (surfaceControl == NULL || !surfaceControl->isValid()
+            || !surfaceControl->getSurface()->isValid()) {
         LOGE("Error creating sprite surface.");
         return NULL;
     }
@@ -360,7 +389,7 @@
 // --- SpriteController::SpriteImpl ---
 
 SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
-        mController(controller), mTransactionNestingCount(0) {
+        mController(controller) {
 }
 
 SpriteController::SpriteImpl::~SpriteImpl() {
@@ -368,27 +397,33 @@
 
     // Let the controller take care of deleting the last reference to sprite
     // surfaces so that we do not block the caller on an IPC here.
-    if (mState.surfaceControl != NULL) {
-        mController->disposeSurfaceLocked(mState.surfaceControl);
-        mState.surfaceControl.clear();
+    if (mLocked.state.surfaceControl != NULL) {
+        mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+        mLocked.state.surfaceControl.clear();
     }
 }
 
-void SpriteController::SpriteImpl::setBitmap(const SkBitmap* bitmap,
-        float hotSpotX, float hotSpotY) {
+void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
     AutoMutex _l(mController->mLock);
 
-    if (bitmap) {
-        bitmap->copyTo(&mState.bitmap, SkBitmap::kARGB_8888_Config);
-    } else {
-        mState.bitmap.reset();
-    }
+    uint32_t dirty;
+    if (icon.isValid()) {
+        icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config);
 
-    uint32_t dirty = DIRTY_BITMAP;
-    if (mState.hotSpotX != hotSpotX || mState.hotSpotY != hotSpotY) {
-        mState.hotSpotX = hotSpotX;
-        mState.hotSpotY = hotSpotY;
-        dirty |= DIRTY_HOTSPOT;
+        if (!mLocked.state.icon.isValid()
+                || mLocked.state.icon.hotSpotX != icon.hotSpotX
+                || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+            mLocked.state.icon.hotSpotX = icon.hotSpotX;
+            mLocked.state.icon.hotSpotY = icon.hotSpotY;
+            dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+        } else {
+            dirty = DIRTY_BITMAP;
+        }
+    } else if (mLocked.state.icon.isValid()) {
+        mLocked.state.icon.bitmap.reset();
+        dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+    } else {
+        return; // setting to invalid icon and already invalid so nothing to do
     }
 
     invalidateLocked(dirty);
@@ -397,8 +432,8 @@
 void SpriteController::SpriteImpl::setVisible(bool visible) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.visible != visible) {
-        mState.visible = visible;
+    if (mLocked.state.visible != visible) {
+        mLocked.state.visible = visible;
         invalidateLocked(DIRTY_VISIBILITY);
     }
 }
@@ -406,9 +441,9 @@
 void SpriteController::SpriteImpl::setPosition(float x, float y) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.positionX != x || mState.positionY != y) {
-        mState.positionX = x;
-        mState.positionY = y;
+    if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
+        mLocked.state.positionX = x;
+        mLocked.state.positionY = y;
         invalidateLocked(DIRTY_POSITION);
     }
 }
@@ -416,8 +451,8 @@
 void SpriteController::SpriteImpl::setLayer(int32_t layer) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.layer != layer) {
-        mState.layer = layer;
+    if (mLocked.state.layer != layer) {
+        mLocked.state.layer = layer;
         invalidateLocked(DIRTY_LAYER);
     }
 }
@@ -425,8 +460,8 @@
 void SpriteController::SpriteImpl::setAlpha(float alpha) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.alpha != alpha) {
-        mState.alpha = alpha;
+    if (mLocked.state.alpha != alpha) {
+        mLocked.state.alpha = alpha;
         invalidateLocked(DIRTY_ALPHA);
     }
 }
@@ -435,37 +470,18 @@
         const SpriteTransformationMatrix& matrix) {
     AutoMutex _l(mController->mLock);
 
-    if (mState.transformationMatrix != matrix) {
-        mState.transformationMatrix = matrix;
+    if (mLocked.state.transformationMatrix != matrix) {
+        mLocked.state.transformationMatrix = matrix;
         invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
     }
 }
 
-void SpriteController::SpriteImpl::openTransaction() {
-    AutoMutex _l(mController->mLock);
-
-    mTransactionNestingCount += 1;
-}
-
-void SpriteController::SpriteImpl::closeTransaction() {
-    AutoMutex _l(mController->mLock);
-
-    LOG_ALWAYS_FATAL_IF(mTransactionNestingCount == 0,
-            "Sprite closeTransaction() called but there is no open sprite transaction");
-
-    mTransactionNestingCount -= 1;
-    if (mTransactionNestingCount == 0 && mState.dirty) {
-        mController->invalidateSpriteLocked(this);
-    }
-}
-
 void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
-    if (mTransactionNestingCount > 0) {
-        bool wasDirty = mState.dirty;
-        mState.dirty |= dirty;
-        if (!wasDirty) {
-            mController->invalidateSpriteLocked(this);
-        }
+    bool wasDirty = mLocked.state.dirty;
+    mLocked.state.dirty |= dirty;
+
+    if (!wasDirty) {
+        mController->invalidateSpriteLocked(this);
     }
 }
 
diff --git a/services/input/SpriteController.h b/services/input/SpriteController.h
index 27afb5e..50ae8a5 100644
--- a/services/input/SpriteController.h
+++ b/services/input/SpriteController.h
@@ -33,6 +33,8 @@
  */
 struct SpriteTransformationMatrix {
     inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+    inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) :
+            dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { }
 
     float dsdx;
     float dtdx;
@@ -52,6 +54,35 @@
 };
 
 /*
+ * Icon that a sprite displays, including its hotspot.
+ */
+struct SpriteIcon {
+    inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { }
+    inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) :
+            bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
+
+    SkBitmap bitmap;
+    float hotSpotX;
+    float hotSpotY;
+
+    inline SpriteIcon copy() const {
+        SkBitmap bitmapCopy;
+        bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config);
+        return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
+    }
+
+    inline void reset() {
+        bitmap.reset();
+        hotSpotX = 0;
+        hotSpotY = 0;
+    }
+
+    inline bool isValid() const {
+        return !bitmap.isNull() && !bitmap.empty();
+    }
+};
+
+/*
  * A sprite is a simple graphical object that is displayed on-screen above other layers.
  * The basic sprite class is an interface.
  * The implementation is provided by the sprite controller.
@@ -62,9 +93,21 @@
     virtual ~Sprite() { }
 
 public:
+    enum {
+        // The base layer for pointer sprites.
+        BASE_LAYER_POINTER = 0, // reserve space for 1 pointer
+
+        // The base layer for spot sprites.
+        BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots
+    };
+
     /* Sets the bitmap that is drawn by the sprite.
      * The sprite retains a copy of the bitmap for subsequent rendering. */
-    virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) = 0;
+    virtual void setIcon(const SpriteIcon& icon) = 0;
+
+    inline void clearIcon() {
+        setIcon(SpriteIcon());
+    }
 
     /* Sets whether the sprite is visible. */
     virtual void setVisible(bool visible) = 0;
@@ -81,14 +124,6 @@
 
     /* Sets the sprite transformation matrix. */
     virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
-
-    /* Opens or closes a transaction to perform a batch of sprite updates as part of
-     * a single operation such as setPosition and setAlpha.  It is not necessary to
-     * open a transaction when updating a single property.
-     * Calls to openTransaction() nest and must be matched by an equal number
-     * of calls to closeTransaction(). */
-    virtual void openTransaction() = 0;
-    virtual void closeTransaction() = 0;
 };
 
 /*
@@ -112,6 +147,14 @@
     /* Creates a new sprite, initially invisible. */
     sp<Sprite> createSprite();
 
+    /* Opens or closes a transaction to perform a batch of sprite updates as part of
+     * a single operation such as setPosition and setAlpha.  It is not necessary to
+     * open a transaction when updating a single property.
+     * Calls to openTransaction() nest and must be matched by an equal number
+     * of calls to closeTransaction(). */
+    void openTransaction();
+    void closeTransaction();
+
 private:
     enum {
         MSG_UPDATE_SPRITES,
@@ -135,16 +178,14 @@
      * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
     struct SpriteState {
         inline SpriteState() :
-                dirty(0), hotSpotX(0), hotSpotY(0), visible(false),
+                dirty(0), visible(false),
                 positionX(0), positionY(0), layer(0), alpha(1.0f),
                 surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
         }
 
         uint32_t dirty;
 
-        SkBitmap bitmap;
-        float hotSpotX;
-        float hotSpotY;
+        SpriteIcon icon;
         bool visible;
         float positionX;
         float positionY;
@@ -159,7 +200,7 @@
         bool surfaceVisible;
 
         inline bool wantSurfaceVisible() const {
-            return visible && alpha > 0.0f && !bitmap.isNull() && !bitmap.empty();
+            return visible && alpha > 0.0f && icon.isValid();
         }
     };
 
@@ -177,37 +218,36 @@
     public:
         SpriteImpl(const sp<SpriteController> controller);
 
-        virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
+        virtual void setIcon(const SpriteIcon& icon);
         virtual void setVisible(bool visible);
         virtual void setPosition(float x, float y);
         virtual void setLayer(int32_t layer);
         virtual void setAlpha(float alpha);
         virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
-        virtual void openTransaction();
-        virtual void closeTransaction();
 
         inline const SpriteState& getStateLocked() const {
-            return mState;
+            return mLocked.state;
         }
 
         inline void resetDirtyLocked() {
-            mState.dirty = 0;
+            mLocked.state.dirty = 0;
         }
 
         inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
                 int32_t width, int32_t height, bool drawn, bool visible) {
-            mState.surfaceControl = surfaceControl;
-            mState.surfaceWidth = width;
-            mState.surfaceHeight = height;
-            mState.surfaceDrawn = drawn;
-            mState.surfaceVisible = visible;
+            mLocked.state.surfaceControl = surfaceControl;
+            mLocked.state.surfaceWidth = width;
+            mLocked.state.surfaceHeight = height;
+            mLocked.state.surfaceDrawn = drawn;
+            mLocked.state.surfaceVisible = visible;
         }
 
     private:
         sp<SpriteController> mController;
 
-        SpriteState mState; // guarded by mController->mLock
-        uint32_t mTransactionNestingCount; // guarded by mController->mLock
+        struct Locked {
+            SpriteState state;
+        } mLocked; // guarded by mController->mLock
 
         void invalidateLocked(uint32_t dirty);
     };
@@ -232,8 +272,12 @@
 
     sp<SurfaceComposerClient> mSurfaceComposerClient;
 
-    Vector<sp<SpriteImpl> > mInvalidatedSprites; // guarded by mLock
-    Vector<sp<SurfaceControl> > mDisposedSurfaces; // guarded by mLock
+    struct Locked {
+        Vector<sp<SpriteImpl> > invalidatedSprites;
+        Vector<sp<SurfaceControl> > disposedSurfaces;
+        uint32_t transactionNestingCount;
+        bool deferredSpriteUpdate;
+    } mLocked; // guarded by mLock
 
     void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
     void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index ba8ca9c..54bb9d7 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -97,6 +97,16 @@
 
     virtual void unfade() {
     }
+
+    virtual void setPresentation(Presentation presentation) {
+    }
+
+    virtual void setSpots(SpotGesture spotGesture,
+            const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+    }
+
+    virtual void clearSpots() {
+    }
 };
 
 
@@ -192,10 +202,6 @@
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
         return mPointerControllers.valueFor(deviceId);
     }
-
-    virtual sp<SpotControllerInterface> obtainSpotController(int32_t device) {
-        return NULL;
-    }
 };
 
 
diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java
index b0978a3..69bde41 100644
--- a/services/java/com/android/server/wm/InputManager.java
+++ b/services/java/com/android/server/wm/InputManager.java
@@ -23,12 +23,6 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 import android.os.Environment;
 import android.os.Looper;
 import android.os.MessageQueue;
@@ -39,6 +33,7 @@
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.KeyEvent;
+import android.view.PointerIcon;
 import android.view.Surface;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
@@ -63,7 +58,8 @@
     private final Context mContext;
     private final WindowManagerService mWindowManagerService;
     
-    private static native void nativeInit(Callbacks callbacks, MessageQueue messageQueue);
+    private static native void nativeInit(Context context,
+            Callbacks callbacks, MessageQueue messageQueue);
     private static native void nativeStart();
     private static native void nativeSetDisplaySize(int displayId, int width, int height);
     private static native void nativeSetDisplayOrientation(int displayId, int rotation);
@@ -133,7 +129,7 @@
         Looper looper = windowManagerService.mH.getLooper();
 
         Slog.i(TAG, "Initializing input manager");
-        nativeInit(mCallbacks, looper.getQueue());
+        nativeInit(mContext, mCallbacks, looper.getQueue());
     }
     
     public void start() {
@@ -435,48 +431,6 @@
         }
     }
 
-    private static final class PointerIcon {
-        public Bitmap bitmap;
-        public float hotSpotX;
-        public float hotSpotY;
-
-        public static PointerIcon load(Resources resources, int resourceId) {
-            PointerIcon icon = new PointerIcon();
-
-            XmlResourceParser parser = resources.getXml(resourceId);
-            final int bitmapRes;
-            try {
-                XmlUtils.beginDocument(parser, "pointer-icon");
-
-                TypedArray a = resources.obtainAttributes(
-                        parser, com.android.internal.R.styleable.PointerIcon);
-                bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
-                icon.hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
-                icon.hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
-                a.recycle();
-            } catch (Exception ex) {
-                Slog.e(TAG, "Exception parsing pointer icon resource.", ex);
-                return null;
-            } finally {
-                parser.close();
-            }
-
-            if (bitmapRes == 0) {
-                Slog.e(TAG, "<pointer-icon> is missing bitmap attribute");
-                return null;
-            }
-
-            Drawable drawable = resources.getDrawable(bitmapRes);
-            if (!(drawable instanceof BitmapDrawable)) {
-                Slog.e(TAG, "<pointer-icon> bitmap attribute must refer to a bitmap drawable");
-                return null;
-            }
-
-            icon.bitmap = ((BitmapDrawable)drawable).getBitmap();
-            return icon;
-        }
-    }
-
     /*
      * Callbacks from native.
      */
@@ -641,8 +595,7 @@
 
         @SuppressWarnings("unused")
         public PointerIcon getPointerIcon() {
-            return PointerIcon.load(mContext.getResources(),
-                    com.android.internal.R.drawable.pointer_arrow_icon);
+            return PointerIcon.getDefaultIcon(mContext);
         }
     }
 }
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index aaa305e..1f10d9c 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -36,13 +36,13 @@
 
 #include <input/InputManager.h>
 #include <input/PointerController.h>
-#include <input/SpotController.h>
 #include <input/SpriteController.h>
 
 #include <android_os_MessageQueue.h>
 #include <android_view_KeyEvent.h>
 #include <android_view_MotionEvent.h>
 #include <android_view_InputChannel.h>
+#include <android_view_PointerIcon.h>
 #include <android/graphics/GraphicsJNI.h>
 
 #include "com_android_server_PowerManagerService.h"
@@ -101,12 +101,6 @@
     jfieldID navigation;
 } gConfigurationClassInfo;
 
-static struct {
-    jfieldID bitmap;
-    jfieldID hotSpotX;
-    jfieldID hotSpotY;
-} gPointerIconClassInfo;
-
 
 // --- Global functions ---
 
@@ -128,17 +122,30 @@
             getInputWindowHandleObjLocalRef(env);
 }
 
+static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t style,
+        SpriteIcon* outSpriteIcon) {
+    PointerIcon pointerIcon;
+    status_t status = android_view_PointerIcon_loadSystemIcon(env,
+            contextObj, style, &pointerIcon);
+    if (!status) {
+        pointerIcon.bitmap.copyTo(&outSpriteIcon->bitmap, SkBitmap::kARGB_8888_Config);
+        outSpriteIcon->hotSpotX = pointerIcon.hotSpotX;
+        outSpriteIcon->hotSpotY = pointerIcon.hotSpotY;
+    }
+}
+
 
 // --- NativeInputManager ---
 
 class NativeInputManager : public virtual RefBase,
     public virtual InputReaderPolicyInterface,
-    public virtual InputDispatcherPolicyInterface {
+    public virtual InputDispatcherPolicyInterface,
+    public virtual PointerControllerPolicyInterface {
 protected:
     virtual ~NativeInputManager();
 
 public:
-    NativeInputManager(jobject callbacksObj, const sp<Looper>& looper);
+    NativeInputManager(jobject contextObj, jobject callbacksObj, const sp<Looper>& looper);
 
     inline sp<InputManager> getInputManager() const { return mInputManager; }
 
@@ -165,7 +172,6 @@
     virtual nsecs_t getVirtualKeyQuietTime();
     virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
     virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
-    virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId);
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -189,9 +195,14 @@
     virtual bool checkInjectEventsPermissionNonReentrant(
             int32_t injectorPid, int32_t injectorUid);
 
+    /* --- PointerControllerPolicyInterface implementation --- */
+
+    virtual void loadPointerResources(PointerResources* outResources);
+
 private:
     sp<InputManager> mInputManager;
 
+    jobject mContextObj;
     jobject mCallbacksObj;
     sp<Looper> mLooper;
 
@@ -223,7 +234,7 @@
         wp<PointerController> pointerController;
     } mLocked;
 
-    void updateInactivityFadeDelayLocked(const sp<PointerController>& controller);
+    void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
     void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
     void ensureSpriteControllerLocked();
 
@@ -240,13 +251,15 @@
 
 
 
-NativeInputManager::NativeInputManager(jobject callbacksObj, const sp<Looper>& looper) :
+NativeInputManager::NativeInputManager(jobject contextObj,
+        jobject callbacksObj, const sp<Looper>& looper) :
         mLooper(looper),
         mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
         mKeyRepeatTimeout(-1), mKeyRepeatDelay(-1),
         mMaxEventsPerSecond(-1) {
     JNIEnv* env = jniEnv();
 
+    mContextObj = env->NewGlobalRef(contextObj);
     mCallbacksObj = env->NewGlobalRef(callbacksObj);
 
     {
@@ -265,6 +278,7 @@
 NativeInputManager::~NativeInputManager() {
     JNIEnv* env = jniEnv();
 
+    env->DeleteGlobalRef(mContextObj);
     env->DeleteGlobalRef(mCallbacksObj);
 }
 
@@ -288,9 +302,13 @@
 
 void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height) {
     if (displayId == 0) {
-        AutoMutex _l(mLock);
+        { // acquire lock
+            AutoMutex _l(mLock);
 
-        if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+            if (mLocked.displayWidth == width && mLocked.displayHeight == height) {
+                return;
+            }
+
             mLocked.displayWidth = width;
             mLocked.displayHeight = height;
 
@@ -298,7 +316,7 @@
             if (controller != NULL) {
                 controller->setDisplaySize(width, height);
             }
-        }
+        } // release lock
     }
 }
 
@@ -428,40 +446,33 @@
     if (controller == NULL) {
         ensureSpriteControllerLocked();
 
-        controller = new PointerController(mLooper, mLocked.spriteController);
+        controller = new PointerController(this, mLooper, mLocked.spriteController);
         mLocked.pointerController = controller;
 
         controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
         controller->setDisplayOrientation(mLocked.displayOrientation);
 
         JNIEnv* env = jniEnv();
-        jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon);
-        if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) {
-            jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX);
-            jfloat iconHotSpotY = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotY);
-            jobject iconBitmapObj = env->GetObjectField(iconObj, gPointerIconClassInfo.bitmap);
-            if (iconBitmapObj) {
-                SkBitmap* iconBitmap = GraphicsJNI::getNativeBitmap(env, iconBitmapObj);
-                if (iconBitmap) {
-                    controller->setPointerIcon(iconBitmap, iconHotSpotX, iconHotSpotY);
-                }
-                env->DeleteLocalRef(iconBitmapObj);
+        jobject pointerIconObj = env->CallObjectMethod(mCallbacksObj,
+                gCallbacksClassInfo.getPointerIcon);
+        if (!checkAndClearExceptionFromCallback(env, "getPointerIcon")) {
+            PointerIcon pointerIcon;
+            status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+                    mContextObj, &pointerIcon);
+            if (!status && !pointerIcon.isNullIcon()) {
+                controller->setPointerIcon(SpriteIcon(pointerIcon.bitmap,
+                        pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+            } else {
+                controller->setPointerIcon(SpriteIcon());
             }
-            env->DeleteLocalRef(iconObj);
+            env->DeleteLocalRef(pointerIconObj);
         }
 
-        updateInactivityFadeDelayLocked(controller);
+        updateInactivityTimeoutLocked(controller);
     }
     return controller;
 }
 
-sp<SpotControllerInterface> NativeInputManager::obtainSpotController(int32_t deviceId) {
-    AutoMutex _l(mLock);
-
-    ensureSpriteControllerLocked();
-    return new SpotController(mLooper, mLocked.spriteController);
-}
-
 void NativeInputManager::ensureSpriteControllerLocked() {
     if (mLocked.spriteController == NULL) {
         JNIEnv* env = jniEnv();
@@ -642,16 +653,16 @@
 
         sp<PointerController> controller = mLocked.pointerController.promote();
         if (controller != NULL) {
-            updateInactivityFadeDelayLocked(controller);
+            updateInactivityTimeoutLocked(controller);
         }
     }
 }
 
-void NativeInputManager::updateInactivityFadeDelayLocked(const sp<PointerController>& controller) {
+void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) {
     bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
-    controller->setInactivityFadeDelay(lightsOut
-            ? PointerController::INACTIVITY_FADE_DELAY_SHORT
-            : PointerController::INACTIVITY_FADE_DELAY_NORMAL);
+    controller->setInactivityTimeout(lightsOut
+            ? PointerController::INACTIVITY_TIMEOUT_SHORT
+            : PointerController::INACTIVITY_TIMEOUT_NORMAL);
 }
 
 bool NativeInputManager::isScreenOn() {
@@ -884,6 +895,17 @@
     return result;
 }
 
+void NativeInputManager::loadPointerResources(PointerResources* outResources) {
+    JNIEnv* env = jniEnv();
+
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_HOVER,
+            &outResources->spotHover);
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_TOUCH,
+            &outResources->spotTouch);
+    loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_ANCHOR,
+            &outResources->spotAnchor);
+}
+
 
 // ----------------------------------------------------------------------------
 
@@ -899,10 +921,10 @@
 }
 
 static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
-        jobject callbacks, jobject messageQueueObj) {
+        jobject contextObj, jobject callbacksObj, jobject messageQueueObj) {
     if (gNativeInputManager == NULL) {
         sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
-        gNativeInputManager = new NativeInputManager(callbacks, looper);
+        gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper);
     } else {
         LOGE("Input manager already initialized.");
         jniThrowRuntimeException(env, "Input manager already initialized.");
@@ -1246,7 +1268,8 @@
 
 static JNINativeMethod gInputManagerMethods[] = {
     /* name, signature, funcPtr */
-    { "nativeInit", "(Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
+    { "nativeInit", "(Landroid/content/Context;"
+            "Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
             (void*) android_server_InputManager_nativeInit },
     { "nativeStart", "()V",
             (void*) android_server_InputManager_nativeStart },
@@ -1372,7 +1395,7 @@
             "getPointerLayer", "()I");
 
     GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, clazz,
-            "getPointerIcon", "()Lcom/android/server/wm/InputManager$PointerIcon;");
+            "getPointerIcon", "()Landroid/view/PointerIcon;");
 
     // KeyEvent
 
@@ -1421,19 +1444,6 @@
     GET_FIELD_ID(gConfigurationClassInfo.navigation, clazz,
             "navigation", "I");
 
-    // PointerIcon
-
-    FIND_CLASS(clazz, "com/android/server/wm/InputManager$PointerIcon");
-
-    GET_FIELD_ID(gPointerIconClassInfo.bitmap, clazz,
-            "bitmap", "Landroid/graphics/Bitmap;");
-
-    GET_FIELD_ID(gPointerIconClassInfo.hotSpotX, clazz,
-            "hotSpotX", "F");
-
-    GET_FIELD_ID(gPointerIconClassInfo.hotSpotY, clazz,
-            "hotSpotY", "F");
-
     return 0;
 }
 
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp
index 90865da..59b7e5a 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp
@@ -38,23 +38,10 @@
 #include "SurfaceFlinger.h"
 
 // ----------------------------------------------------------------------------
-// the sim build doesn't have gettid
-
-#ifndef HAVE_GETTID
-# define gettid getpid
-#endif
-
-// ----------------------------------------------------------------------------
 namespace android {
 
-static char const * kSleepFileName = "/sys/power/wait_for_fb_sleep";
-static char const * kWakeFileName = "/sys/power/wait_for_fb_wake";
-static char const * const kOldSleepFileName = "/sys/android_power/wait_for_fb_sleep";
-static char const * const kOldWakeFileName = "/sys/android_power/wait_for_fb_wake";
-
-// This dir exists if the framebuffer console is present, either built into
-// the kernel or loaded as a module.
-static char const * const kFbconSysDir = "/sys/class/graphics/fbcon";
+static char const * const kSleepFileName = "/sys/power/wait_for_fb_sleep";
+static char const * const kWakeFileName  = "/sys/power/wait_for_fb_wake";
 
 // ----------------------------------------------------------------------------
 
@@ -122,237 +109,13 @@
 
 status_t DisplayHardwareBase::DisplayEventThread::readyToRun()
 {
-    if (access(kSleepFileName, R_OK) || access(kWakeFileName, R_OK)) {
-        if (access(kOldSleepFileName, R_OK) || access(kOldWakeFileName, R_OK)) {
-            LOGE("Couldn't open %s or %s", kSleepFileName, kWakeFileName);
-            return NO_INIT;
-        }
-        kSleepFileName = kOldSleepFileName;
-        kWakeFileName = kOldWakeFileName;
-    }
     return NO_ERROR;
 }
 
 status_t DisplayHardwareBase::DisplayEventThread::initCheck() const
 {
-    return (((access(kSleepFileName, R_OK) == 0 &&
-            access(kWakeFileName, R_OK) == 0) ||
-            (access(kOldSleepFileName, R_OK) == 0 &&
-            access(kOldWakeFileName, R_OK) == 0)) &&
-            access(kFbconSysDir, F_OK) != 0) ? NO_ERROR : NO_INIT;
-}
-
-// ----------------------------------------------------------------------------
-
-pid_t DisplayHardwareBase::ConsoleManagerThread::sSignalCatcherPid = 0;
-
-DisplayHardwareBase::ConsoleManagerThread::ConsoleManagerThread(
-        const sp<SurfaceFlinger>& flinger)
-    : DisplayEventThreadBase(flinger), consoleFd(-1)
-{   
-    sSignalCatcherPid = 0;
-
-    // create a new console
-    char const * const ttydev = "/dev/tty0";
-    int fd = open(ttydev, O_RDWR | O_SYNC);
-    if (fd<0) {
-        LOGE("Can't open %s", ttydev);
-        this->consoleFd = -errno;
-        return;
-    }
-
-    // to make sure that we are in text mode
-    int res = ioctl(fd, KDSETMODE, (void*) KD_TEXT);
-    if (res<0) {
-        LOGE("ioctl(%d, KDSETMODE, ...) failed, res %d (%s)",
-                fd, res, strerror(errno));
-    }
-    
-    // get the current console
-    struct vt_stat vs;
-    res = ioctl(fd, VT_GETSTATE, &vs);
-    if (res<0) {
-        LOGE("ioctl(%d, VT_GETSTATE, ...) failed, res %d (%s)",
-                fd, res, strerror(errno));
-        this->consoleFd = -errno;
-        return;
-    }
-
-    // switch to console 7 (which is what X normaly uses)
-    int vtnum = 7;
-    do {
-        res = ioctl(fd, VT_ACTIVATE, (void*)vtnum);
-    } while(res < 0 && errno == EINTR);
-    if (res<0) {
-        LOGE("ioctl(%d, VT_ACTIVATE, ...) failed, %d (%s) for %d",
-                fd, errno, strerror(errno), vtnum);
-        this->consoleFd = -errno;
-        return;
-    }
-
-    do {
-        res = ioctl(fd, VT_WAITACTIVE, (void*)vtnum);
-    } while(res < 0 && errno == EINTR);
-    if (res<0) {
-        LOGE("ioctl(%d, VT_WAITACTIVE, ...) failed, %d %d %s for %d",
-                fd, res, errno, strerror(errno), vtnum);
-        this->consoleFd = -errno;
-        return;
-    }
-
-    // open the new console
-    close(fd);
-    fd = open(ttydev, O_RDWR | O_SYNC);
-    if (fd<0) {
-        LOGE("Can't open new console %s", ttydev);
-        this->consoleFd = -errno;
-        return;
-    }
-
-    /* disable console line buffer, echo, ... */
-    struct termios ttyarg;
-    ioctl(fd, TCGETS , &ttyarg);
-    ttyarg.c_iflag = 0;
-    ttyarg.c_lflag = 0;
-    ioctl(fd, TCSETS , &ttyarg);
-
-    // set up signals so we're notified when the console changes
-    // we can't use SIGUSR1 because it's used by the java-vm
-    vm.mode = VT_PROCESS;
-    vm.waitv = 0;
-    vm.relsig = SIGUSR2;
-    vm.acqsig = SIGUNUSED;
-    vm.frsig = 0;
-
-    struct sigaction act;
-    sigemptyset(&act.sa_mask);
-    act.sa_handler = sigHandler;
-    act.sa_flags = 0;
-    sigaction(vm.relsig, &act, NULL);
-
-    sigemptyset(&act.sa_mask);
-    act.sa_handler = sigHandler;
-    act.sa_flags = 0;
-    sigaction(vm.acqsig, &act, NULL);
-
-    sigset_t mask;
-    sigemptyset(&mask);
-    sigaddset(&mask, vm.relsig);
-    sigaddset(&mask, vm.acqsig);
-    sigprocmask(SIG_BLOCK, &mask, NULL);
-
-    // switch to graphic mode
-    res = ioctl(fd, KDSETMODE, (void*)KD_GRAPHICS);
-    LOGW_IF(res<0,
-            "ioctl(%d, KDSETMODE, KD_GRAPHICS) failed, res %d", fd, res);
-
-    this->prev_vt_num = vs.v_active;
-    this->vt_num = vtnum;
-    this->consoleFd = fd;
-}
-
-DisplayHardwareBase::ConsoleManagerThread::~ConsoleManagerThread()
-{   
-    if (this->consoleFd >= 0) {
-        int fd = this->consoleFd;
-        int prev_vt_num = this->prev_vt_num;
-        int res;
-        ioctl(fd, KDSETMODE, (void*)KD_TEXT);
-        do {
-            res = ioctl(fd, VT_ACTIVATE, (void*)prev_vt_num);
-        } while(res < 0 && errno == EINTR);
-        do {
-            res = ioctl(fd, VT_WAITACTIVE, (void*)prev_vt_num);
-        } while(res < 0 && errno == EINTR);
-        close(fd);    
-        char const * const ttydev = "/dev/tty0";
-        fd = open(ttydev, O_RDWR | O_SYNC);
-        ioctl(fd, VT_DISALLOCATE, 0);
-        close(fd);
-    }
-}
-
-status_t DisplayHardwareBase::ConsoleManagerThread::readyToRun()
-{
-    if (this->consoleFd >= 0) {
-        sSignalCatcherPid = gettid();
-        
-        sigset_t mask;
-        sigemptyset(&mask);
-        sigaddset(&mask, vm.relsig);
-        sigaddset(&mask, vm.acqsig);
-        sigprocmask(SIG_BLOCK, &mask, NULL);
-
-        int res = ioctl(this->consoleFd, VT_SETMODE, &vm);
-        if (res<0) {
-            LOGE("ioctl(%d, VT_SETMODE, ...) failed, %d (%s)",
-                    this->consoleFd, errno, strerror(errno));
-        }
-        return NO_ERROR;
-    }
-    return this->consoleFd;
-}
-
-void DisplayHardwareBase::ConsoleManagerThread::requestExit()
-{
-    Thread::requestExit();
-    if (sSignalCatcherPid != 0) {
-        // wake the thread up
-        kill(sSignalCatcherPid, SIGINT);
-        // wait for it...
-    }
-}
-
-void DisplayHardwareBase::ConsoleManagerThread::sigHandler(int sig)
-{
-    // resend the signal to our signal catcher thread
-    LOGW("received signal %d in thread %d, resending to %d",
-            sig, gettid(), sSignalCatcherPid);
-
-    // we absolutely need the delays below because without them
-    // our main thread never gets a chance to handle the signal.
-    usleep(10000);
-    kill(sSignalCatcherPid, sig);
-    usleep(10000);
-}
-
-status_t DisplayHardwareBase::ConsoleManagerThread::releaseScreen() const
-{
-    int fd = this->consoleFd;
-    int err = ioctl(fd, VT_RELDISP, (void*)1);
-    LOGE_IF(err<0, "ioctl(%d, VT_RELDISP, 1) failed %d (%s)",
-        fd, errno, strerror(errno));
-    return (err<0) ? (-errno) : status_t(NO_ERROR);
-}
-
-bool DisplayHardwareBase::ConsoleManagerThread::threadLoop()
-{
-    sigset_t mask;
-    sigemptyset(&mask);
-    sigaddset(&mask, vm.relsig);
-    sigaddset(&mask, vm.acqsig);
-
-    int sig = 0;
-    sigwait(&mask, &sig);
-
-    if (sig == vm.relsig) {
-        sp<SurfaceFlinger> flinger = mFlinger.promote();
-        //LOGD("About to give-up screen, flinger = %p", flinger.get());
-        if (flinger != 0)
-            flinger->screenReleased(0);
-    } else if (sig == vm.acqsig) {
-        sp<SurfaceFlinger> flinger = mFlinger.promote();
-        //LOGD("Screen about to return, flinger = %p", flinger.get());
-        if (flinger != 0) 
-            flinger->screenAcquired(0);
-    }
-    
-    return true;
-}
-
-status_t DisplayHardwareBase::ConsoleManagerThread::initCheck() const
-{
-    return consoleFd >= 0 ? NO_ERROR : NO_INIT;
+    return ((access(kSleepFileName, R_OK) == 0 &&
+            access(kWakeFileName, R_OK) == 0)) ? NO_ERROR : NO_INIT;
 }
 
 // ----------------------------------------------------------------------------
@@ -362,10 +125,6 @@
     : mCanDraw(true), mScreenAcquired(true)
 {
     mDisplayEventThread = new DisplayEventThread(flinger);
-    if (mDisplayEventThread->initCheck() != NO_ERROR) {
-        // fall-back on the console
-        mDisplayEventThread = new ConsoleManagerThread(flinger);
-    }
 }
 
 DisplayHardwareBase::~DisplayHardwareBase()
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h
index fa6a0c4..30eb258 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h
@@ -73,24 +73,6 @@
         virtual status_t initCheck() const;
     };
 
-    class ConsoleManagerThread : public DisplayEventThreadBase 
-    {
-        int consoleFd;
-        int vt_num;
-        int prev_vt_num;
-        vt_mode vm;
-        static void sigHandler(int sig);
-        static pid_t sSignalCatcherPid;
-    public:
-                ConsoleManagerThread(const sp<SurfaceFlinger>& flinger);
-        virtual ~ConsoleManagerThread();
-        virtual bool threadLoop();
-        virtual status_t readyToRun();
-        virtual void requestExit();
-        virtual status_t releaseScreen() const;
-        virtual status_t initCheck() const;
-    };
-
     sp<DisplayEventThreadBase>  mDisplayEventThread;
     mutable int                 mCanDraw;
     mutable int                 mScreenAcquired;