Merge "Add LocaleList support to Paint and TextView."
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 412e3cd..e15ba74 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4718,7 +4718,13 @@
             mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
             mInstrumentedLibDir = data.info.getLibDir();
 
-            ApplicationInfo instrApp = new ApplicationInfo();
+            // The app context's info was created against this thread, but
+            // the class loader may have already been loaded and cached with
+            // outdated paths. Clear it so we can load it again using the
+            // instrumentation paths.
+            data.info.clearClassLoader();
+
+            final ApplicationInfo instrApp = new ApplicationInfo();
             instrApp.packageName = ii.packageName;
             instrApp.sourceDir = ii.sourceDir;
             instrApp.publicSourceDir = ii.publicSourceDir;
@@ -4731,6 +4737,7 @@
             ContextImpl instrContext = ContextImpl.createAppContext(this, pi);
 
             try {
+
                 java.lang.ClassLoader cl = instrContext.getClassLoader();
                 mInstrumentation = (Instrumentation)
                     cl.loadClass(data.instrumentationName.getClassName()).newInstance();
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index c2bf28a..3b1c60b 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -255,6 +255,13 @@
         return ai.sharedLibraryFiles;
     }
 
+    /** @hide */
+    public void clearClassLoader() {
+        synchronized (this) {
+            mClassLoader = null;
+        }
+    }
+
     public ClassLoader getClassLoader() {
         synchronized (this) {
             if (mClassLoader != null) {
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 444f020..4292050 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -291,6 +291,7 @@
         }
 
         synchronized (mInputDevicesLock) {
+            populateInputDevicesLocked();
             int index = findInputDeviceListenerLocked(listener);
             if (index < 0) {
                 mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
diff --git a/core/java/android/view/HandlerActionQueue.java b/core/java/android/view/HandlerActionQueue.java
new file mode 100644
index 0000000..4758a34
--- /dev/null
+++ b/core/java/android/view/HandlerActionQueue.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 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.GrowingArrayUtils;
+
+import android.os.Handler;
+
+import java.util.ArrayList;
+
+/**
+ * Class used to enqueue pending work from Views when no Handler is attached.
+ *
+ * @hide Exposed for test framework only.
+ */
+public class HandlerActionQueue {
+    private HandlerAction[] mActions;
+    private int mCount;
+
+    public void post(Runnable action) {
+        postDelayed(action, 0);
+    }
+
+    public void postDelayed(Runnable action, long delayMillis) {
+        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
+
+        synchronized (this) {
+            if (mActions == null) {
+                mActions = new HandlerAction[4];
+            }
+            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
+            mCount++;
+        }
+    }
+
+    public void removeCallbacks(Runnable action) {
+        synchronized (this) {
+            final int count = mCount;
+            int j = 0;
+
+            final HandlerAction[] actions = mActions;
+            for (int i = 0; i < count; i++) {
+                if (actions[i].matches(action)) {
+                    // Remove this action by overwriting it within
+                    // this loop or nulling it out later.
+                    continue;
+                }
+
+                if (j != i) {
+                    // At least one previous entry was removed, so
+                    // this one needs to move to the "new" list.
+                    actions[j] = actions[i];
+                }
+
+                j++;
+            }
+
+            // The "new" list only has j entries.
+            mCount = j;
+
+            // Null out any remaining entries.
+            for (; j < count; j++) {
+                actions[j] = null;
+            }
+        }
+    }
+
+    public void executeActions(Handler handler) {
+        synchronized (this) {
+            final HandlerAction[] actions = mActions;
+            for (int i = 0, count = mCount; i < count; i++) {
+                final HandlerAction handlerAction = actions[i];
+                handler.postDelayed(handlerAction.action, handlerAction.delay);
+            }
+
+            mActions = null;
+            mCount = 0;
+        }
+    }
+
+    public int size() {
+        return mCount;
+    }
+
+    public Runnable getRunnable(int index) {
+        if (index >= mCount) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mActions[index].action;
+    }
+
+    public long getDelay(int index) {
+        if (index >= mCount) {
+            throw new IndexOutOfBoundsException();
+        }
+        return mActions[index].delay;
+    }
+
+    private static class HandlerAction {
+        final Runnable action;
+        final long delay;
+
+        public HandlerAction(Runnable action, long delay) {
+            this.action = action;
+            this.delay = delay;
+        }
+
+        public boolean matches(Runnable otherAction) {
+            return otherAction == null && action == null
+                    || action != null && action.equals(otherAction);
+        }
+    }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 16d9bf3..665069c 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3823,6 +3823,12 @@
     private static SparseArray<String> mAttributeMap;
 
     /**
+     * Queue of pending runnables. Used to postpone calls to post() until this
+     * view is attached and has a handler.
+     */
+    private HandlerActionQueue mRunQueue;
+
+    /**
      * @hide
      */
     String mStartActivityRequestWho;
@@ -13010,6 +13016,18 @@
     }
 
     /**
+     * Returns the queue of runnable for this view.
+     *
+     * @return the queue of runnables for this view
+     */
+    private HandlerActionQueue getRunQueue() {
+        if (mRunQueue == null) {
+            mRunQueue = new HandlerActionQueue();
+        }
+        return mRunQueue;
+    }
+
+    /**
      * Gets the view root associated with the View.
      * @return The view root, or null if none.
      * @hide
@@ -13046,8 +13064,10 @@
         if (attachInfo != null) {
             return attachInfo.mHandler.post(action);
         }
-        // Assume that post will succeed later
-        ViewRootImpl.getRunQueue().post(action);
+
+        // Postpone the runnable until we know on which thread it needs to run.
+        // Assume that the runnable will be successfully placed after attach.
+        getRunQueue().post(action);
         return true;
     }
 
@@ -13075,8 +13095,10 @@
         if (attachInfo != null) {
             return attachInfo.mHandler.postDelayed(action, delayMillis);
         }
-        // Assume that post will succeed later
-        ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
+
+        // Postpone the runnable until we know on which thread it needs to run.
+        // Assume that the runnable will be successfully placed after attach.
+        getRunQueue().postDelayed(action, delayMillis);
         return true;
     }
 
@@ -13095,8 +13117,9 @@
             attachInfo.mViewRootImpl.mChoreographer.postCallback(
                     Choreographer.CALLBACK_ANIMATION, action, null);
         } else {
-            // Assume that post will succeed later
-            ViewRootImpl.getRunQueue().post(action);
+            // Postpone the runnable until we know
+            // on which thread it needs to run.
+            getRunQueue().post(action);
         }
     }
 
@@ -13118,8 +13141,9 @@
             attachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
                     Choreographer.CALLBACK_ANIMATION, action, null, delayMillis);
         } else {
-            // Assume that post will succeed later
-            ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
+            // Postpone the runnable until we know
+            // on which thread it needs to run.
+            getRunQueue().postDelayed(action, delayMillis);
         }
     }
 
@@ -13146,8 +13170,7 @@
                 attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                         Choreographer.CALLBACK_ANIMATION, action, null);
             }
-            // Assume that post will succeed later
-            ViewRootImpl.getRunQueue().removeCallbacks(action);
+            getRunQueue().removeCallbacks(action);
         }
         return true;
     }
@@ -14565,7 +14588,6 @@
      *        this view
      */
     void dispatchAttachedToWindow(AttachInfo info, int visibility) {
-        //System.out.println("Attached! " + this);
         mAttachInfo = info;
         if (mOverlay != null) {
             mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
@@ -14581,6 +14603,11 @@
             mAttachInfo.mScrollContainers.add(this);
             mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
         }
+        // Transfer all pending runnables.
+        if (mRunQueue != null) {
+            mRunQueue.executeActions(info.mHandler);
+            mRunQueue = null;
+        }
         performCollectViewAttributes(mAttachInfo, visibility);
         onAttachedToWindow();
 
@@ -16866,7 +16893,9 @@
                         Choreographer.CALLBACK_ANIMATION, what, who,
                         Choreographer.subtractFrameDelay(delay));
             } else {
-                ViewRootImpl.getRunQueue().postDelayed(what, delay);
+                // Postpone the runnable until we know
+                // on which thread it needs to run.
+                getRunQueue().postDelayed(what, delay);
             }
         }
     }
@@ -16884,7 +16913,7 @@
                 mAttachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                         Choreographer.CALLBACK_ANIMATION, what, who);
             }
-            ViewRootImpl.getRunQueue().removeCallbacks(what);
+            getRunQueue().removeCallbacks(what);
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 8bbaf36..d26e914 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -28,7 +28,6 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
-import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -131,7 +130,7 @@
      */
     static final int MAX_TRACKBALL_DELAY = 250;
 
-    static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();
+    static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
 
     static final ArrayList<Runnable> sFirstDrawHandlers = new ArrayList<Runnable>();
     static boolean sFirstDrawComplete = false;
@@ -6759,90 +6758,17 @@
         }
     }
 
-    static RunQueue getRunQueue() {
-        RunQueue rq = sRunQueues.get();
+    static HandlerActionQueue getRunQueue() {
+        HandlerActionQueue rq = sRunQueues.get();
         if (rq != null) {
             return rq;
         }
-        rq = new RunQueue();
+        rq = new HandlerActionQueue();
         sRunQueues.set(rq);
         return rq;
     }
 
     /**
-     * The run queue is used to enqueue pending work from Views when no Handler is
-     * attached.  The work is executed during the next call to performTraversals on
-     * the thread.
-     * @hide
-     */
-    static final class RunQueue {
-        private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();
-
-        void post(Runnable action) {
-            postDelayed(action, 0);
-        }
-
-        void postDelayed(Runnable action, long delayMillis) {
-            HandlerAction handlerAction = new HandlerAction();
-            handlerAction.action = action;
-            handlerAction.delay = delayMillis;
-
-            synchronized (mActions) {
-                mActions.add(handlerAction);
-            }
-        }
-
-        void removeCallbacks(Runnable action) {
-            final HandlerAction handlerAction = new HandlerAction();
-            handlerAction.action = action;
-
-            synchronized (mActions) {
-                final ArrayList<HandlerAction> actions = mActions;
-
-                while (actions.remove(handlerAction)) {
-                    // Keep going
-                }
-            }
-        }
-
-        void executeActions(Handler handler) {
-            synchronized (mActions) {
-                final ArrayList<HandlerAction> actions = mActions;
-                final int count = actions.size();
-
-                for (int i = 0; i < count; i++) {
-                    final HandlerAction handlerAction = actions.get(i);
-                    handler.postDelayed(handlerAction.action, handlerAction.delay);
-                }
-
-                actions.clear();
-            }
-        }
-
-        private static class HandlerAction {
-            Runnable action;
-            long delay;
-
-            @Override
-            public boolean equals(Object o) {
-                if (this == o) return true;
-                if (o == null || getClass() != o.getClass()) return false;
-
-                HandlerAction that = (HandlerAction) o;
-                return !(action != null ? !action.equals(that.action) : that.action != null);
-
-            }
-
-            @Override
-            public int hashCode() {
-                int result = action != null ? action.hashCode() : 0;
-                result = 31 * result + (int) (delay ^ (delay >>> 32));
-                return result;
-            }
-        }
-    }
-
-    /**
      * Class for managing the accessibility interaction connection
      * based on the global accessibility state.
      */
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index a5696ee..64c4103 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -37,7 +37,6 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.ListPopupWindow.ForwardingListener;
 import com.android.internal.view.ActionBarPolicy;
 import com.android.internal.view.menu.ActionMenuItemView;
 import com.android.internal.view.menu.BaseMenuPresenter;
@@ -45,6 +44,7 @@
 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.ShowableListMenu;
 import com.android.internal.view.menu.SubMenuBuilder;
 
 import java.util.ArrayList;
@@ -828,7 +828,7 @@
 
             setOnTouchListener(new ForwardingListener(this) {
                 @Override
-                public ListPopupWindow getPopup() {
+                public ShowableListMenu getPopup() {
                     if (mOverflowPopup == null) {
                         return null;
                     }
@@ -1003,7 +1003,7 @@
 
     private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
         @Override
-        public ListPopupWindow getPopup() {
+        public ShowableListMenu getPopup() {
             return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
         }
     }
diff --git a/core/java/android/widget/ActivityChooserView.java b/core/java/android/widget/ActivityChooserView.java
index f34ad71..f5c46db 100644
--- a/core/java/android/widget/ActivityChooserView.java
+++ b/core/java/android/widget/ActivityChooserView.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
 
 import android.annotation.StringRes;
 import android.content.Context;
@@ -37,7 +38,6 @@
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ActivityChooserModel.ActivityChooserModelClient;
-import android.widget.ListPopupWindow.ForwardingListener;
 
 /**
  * This class is a view for choosing an activity for handling a given {@link Intent}.
@@ -263,7 +263,7 @@
         });
         expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
             @Override
-            public ListPopupWindow getPopup() {
+            public ShowableListMenu getPopup() {
                 return getListPopupWindow();
             }
 
diff --git a/core/java/android/widget/DropDownListView.java b/core/java/android/widget/DropDownListView.java
new file mode 100644
index 0000000..5536513
--- /dev/null
+++ b/core/java/android/widget/DropDownListView.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2015 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.widget;
+
+
+import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.IntProperty;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.TextView;
+import android.widget.ListView;
+
+
+/**
+ * Wrapper class for a ListView. This wrapper can hijack the focus to
+ * make sure the list uses the appropriate drawables and states when
+ * displayed on screen within a drop down. The focus is never actually
+ * passed to the drop down in this mode; the list only looks focused.
+ *
+ * @hide
+ */
+public class DropDownListView extends ListView {
+    /** Duration in milliseconds of the drag-to-open click animation. */
+    private static final long CLICK_ANIM_DURATION = 150;
+
+    /** Target alpha value for drag-to-open click animation. */
+    private static final int CLICK_ANIM_ALPHA = 0x80;
+
+    /** Wrapper around Drawable's <code>alpha</code> property. */
+    private static final IntProperty<Drawable> DRAWABLE_ALPHA =
+            new IntProperty<Drawable>("alpha") {
+                @Override
+                public void setValue(Drawable object, int value) {
+                    object.setAlpha(value);
+                }
+
+                @Override
+                public Integer get(Drawable object) {
+                    return object.getAlpha();
+                }
+            };
+
+    /*
+     * WARNING: This is a workaround for a touch mode issue.
+     *
+     * Touch mode is propagated lazily to windows. This causes problems in
+     * the following scenario:
+     * - Type something in the AutoCompleteTextView and get some results
+     * - Move down with the d-pad to select an item in the list
+     * - Move up with the d-pad until the selection disappears
+     * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+     *   and get new results; you are now in touch mode
+     * - The selection comes back on the first item in the list, even though
+     *   the list is supposed to be in touch mode
+     *
+     * Using the soft keyboard triggers the touch mode change but that change
+     * is propagated to our window only after the first list layout, therefore
+     * after the list attempts to resurrect the selection.
+     *
+     * The trick to work around this issue is to pretend the list is in touch
+     * mode when we know that the selection should not appear, that is when
+     * we know the user moved the selection away from the list.
+     *
+     * This boolean is set to true whenever we explicitly hide the list's
+     * selection and reset to false whenever we know the user moved the
+     * selection back to the list.
+     *
+     * When this boolean is true, isInTouchMode() returns true, otherwise it
+     * returns super.isInTouchMode().
+     */
+    private boolean mListSelectionHidden;
+
+    /**
+     * True if this wrapper should fake focus.
+     */
+    private boolean mHijackFocus;
+
+    /** Whether to force drawing of the pressed state selector. */
+    private boolean mDrawsInPressedState;
+
+    /** Current drag-to-open click animation, if any. */
+    private Animator mClickAnimation;
+
+    /** Helper for drag-to-open auto scrolling. */
+    private AbsListViewAutoScroller mScrollHelper;
+
+    /**
+     * Creates a new list view wrapper.
+     *
+     * @param context this view's context
+     */
+    public DropDownListView(Context context, boolean hijackFocus) {
+        this(context, hijackFocus, com.android.internal.R.attr.dropDownListViewStyle);
+    }
+
+    /**
+     * Creates a new list view wrapper.
+     *
+     * @param context this view's context
+     */
+    public DropDownListView(Context context, boolean hijackFocus, int defStyleAttr) {
+        super(context, null, defStyleAttr);
+        mHijackFocus = hijackFocus;
+        // TODO: Add an API to control this
+        setCacheColorHint(0); // Transparent, since the background drawable could be anything.
+    }
+
+    /**
+     * Handles forwarded events.
+     *
+     * @param activePointerId id of the pointer that activated forwarding
+     * @return whether the event was handled
+     */
+    public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
+        boolean handledEvent = true;
+        boolean clearPressedItem = false;
+
+        final int actionMasked = event.getActionMasked();
+        switch (actionMasked) {
+            case MotionEvent.ACTION_CANCEL:
+                handledEvent = false;
+                break;
+            case MotionEvent.ACTION_UP:
+                handledEvent = false;
+                // $FALL-THROUGH$
+            case MotionEvent.ACTION_MOVE:
+                final int activeIndex = event.findPointerIndex(activePointerId);
+                if (activeIndex < 0) {
+                    handledEvent = false;
+                    break;
+                }
+
+                final int x = (int) event.getX(activeIndex);
+                final int y = (int) event.getY(activeIndex);
+                final int position = pointToPosition(x, y);
+                if (position == INVALID_POSITION) {
+                    clearPressedItem = true;
+                    break;
+                }
+
+                final View child = getChildAt(position - getFirstVisiblePosition());
+                setPressedItem(child, position, x, y);
+                handledEvent = true;
+
+                if (actionMasked == MotionEvent.ACTION_UP) {
+                    clickPressedItem(child, position);
+                }
+                break;
+        }
+
+        // Failure to handle the event cancels forwarding.
+        if (!handledEvent || clearPressedItem) {
+            clearPressedItem();
+        }
+
+        // Manage automatic scrolling.
+        if (handledEvent) {
+            if (mScrollHelper == null) {
+                mScrollHelper = new AbsListViewAutoScroller(this);
+            }
+            mScrollHelper.setEnabled(true);
+            mScrollHelper.onTouch(this, event);
+        } else if (mScrollHelper != null) {
+            mScrollHelper.setEnabled(false);
+        }
+
+        return handledEvent;
+    }
+
+    /**
+     * Sets whether the list selection is hidden, as part of a workaround for a touch mode issue
+     * (see the declaration for mListSelectionHidden).
+     * @param listSelectionHidden
+     */
+    public void setListSelectionHidden(boolean listSelectionHidden) {
+        this.mListSelectionHidden = listSelectionHidden;
+    }
+
+    /**
+     * Starts an alpha animation on the selector. When the animation ends,
+     * the list performs a click on the item.
+     */
+    private void clickPressedItem(final View child, final int position) {
+        final long id = getItemIdAtPosition(position);
+        final Animator anim = ObjectAnimator.ofInt(
+                mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF);
+        anim.setDuration(CLICK_ANIM_DURATION);
+        anim.setInterpolator(new AccelerateDecelerateInterpolator());
+        anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+            public void onAnimationEnd(Animator animation) {
+                performItemClick(child, position, id);
+            }
+        });
+        anim.start();
+
+        if (mClickAnimation != null) {
+            mClickAnimation.cancel();
+        }
+        mClickAnimation = anim;
+    }
+
+    private void clearPressedItem() {
+        mDrawsInPressedState = false;
+        setPressed(false);
+        updateSelectorState();
+
+        final View motionView = getChildAt(mMotionPosition - mFirstPosition);
+        if (motionView != null) {
+            motionView.setPressed(false);
+        }
+
+        if (mClickAnimation != null) {
+            mClickAnimation.cancel();
+            mClickAnimation = null;
+        }
+    }
+
+    private void setPressedItem(View child, int position, float x, float y) {
+        mDrawsInPressedState = true;
+
+        // Ordering is essential. First, update the container's pressed state.
+        drawableHotspotChanged(x, y);
+        if (!isPressed()) {
+            setPressed(true);
+        }
+
+        // Next, run layout if we need to stabilize child positions.
+        if (mDataChanged) {
+            layoutChildren();
+        }
+
+        // Manage the pressed view based on motion position. This allows us to
+        // play nicely with actual touch and scroll events.
+        final View motionView = getChildAt(mMotionPosition - mFirstPosition);
+        if (motionView != null && motionView != child && motionView.isPressed()) {
+            motionView.setPressed(false);
+        }
+        mMotionPosition = position;
+
+        // Offset for child coordinates.
+        final float childX = x - child.getLeft();
+        final float childY = y - child.getTop();
+        child.drawableHotspotChanged(childX, childY);
+        if (!child.isPressed()) {
+            child.setPressed(true);
+        }
+
+        // Ensure that keyboard focus starts from the last touched position.
+        setSelectedPositionInt(position);
+        positionSelectorLikeTouch(position, child, x, y);
+
+        // Refresh the drawable state to reflect the new pressed state,
+        // which will also update the selector state.
+        refreshDrawableState();
+
+        if (mClickAnimation != null) {
+            mClickAnimation.cancel();
+            mClickAnimation = null;
+        }
+    }
+
+    @Override
+    boolean touchModeDrawsInPressedState() {
+        return mDrawsInPressedState || super.touchModeDrawsInPressedState();
+    }
+
+    /**
+     * Avoids jarring scrolling effect by ensuring that list elements
+     * made of a text view fit on a single line.
+     *
+     * @param position the item index in the list to get a view for
+     * @return the view for the specified item
+     */
+    @Override
+    View obtainView(int position, boolean[] isScrap) {
+        View view = super.obtainView(position, isScrap);
+
+        if (view instanceof TextView) {
+            ((TextView) view).setHorizontallyScrolling(true);
+        }
+
+        return view;
+    }
+
+    @Override
+    public boolean isInTouchMode() {
+        // WARNING: Please read the comment where mListSelectionHidden is declared
+        return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
+    }
+
+    /**
+     * Returns the focus state in the drop down.
+     *
+     * @return true always if hijacking focus
+     */
+    @Override
+    public boolean hasWindowFocus() {
+        return mHijackFocus || super.hasWindowFocus();
+    }
+
+    /**
+     * Returns the focus state in the drop down.
+     *
+     * @return true always if hijacking focus
+     */
+    @Override
+    public boolean isFocused() {
+        return mHijackFocus || super.isFocused();
+    }
+
+    /**
+     * Returns the focus state in the drop down.
+     *
+     * @return true always if hijacking focus
+     */
+    @Override
+    public boolean hasFocus() {
+        return mHijackFocus || super.hasFocus();
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/widget/ForwardingListener.java b/core/java/android/widget/ForwardingListener.java
new file mode 100644
index 0000000..fd7140f
--- /dev/null
+++ b/core/java/android/widget/ForwardingListener.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2015 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.widget;
+
+import android.os.SystemClock;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+import com.android.internal.view.menu.ShowableListMenu;
+
+/**
+ * Abstract class that forwards touch events to a {@link ListPopupWindow}.
+ *
+ * @hide
+ */
+public abstract class ForwardingListener
+        implements View.OnTouchListener, View.OnAttachStateChangeListener {
+
+    /** Scaled touch slop, used for detecting movement outside bounds. */
+    private final float mScaledTouchSlop;
+
+    /** Timeout before disallowing intercept on the source's parent. */
+    private final int mTapTimeout;
+
+    /** Timeout before accepting a long-press to start forwarding. */
+    private final int mLongPressTimeout;
+
+    /** Source view from which events are forwarded. */
+    private final View mSrc;
+
+    /** Runnable used to prevent conflicts with scrolling parents. */
+    private Runnable mDisallowIntercept;
+
+    /** Runnable used to trigger forwarding on long-press. */
+    private Runnable mTriggerLongPress;
+
+    /** Whether this listener is currently forwarding touch events. */
+    private boolean mForwarding;
+
+    /**
+     * Whether forwarding was initiated by a long-press. If so, we won't
+     * force the window to dismiss when the touch stream ends.
+     */
+    private boolean mWasLongPress;
+
+    /** The id of the first pointer down in the current event stream. */
+    private int mActivePointerId;
+
+    public ForwardingListener(View src) {
+        mSrc = src;
+        mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
+        mTapTimeout = ViewConfiguration.getTapTimeout();
+
+        // Use a medium-press timeout. Halfway between tap and long-press.
+        mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
+
+        src.addOnAttachStateChangeListener(this);
+    }
+
+    /**
+     * Returns the popup to which this listener is forwarding events.
+     * <p>
+     * Override this to return the correct popup. If the popup is displayed
+     * asynchronously, you may also need to override
+     * {@link #onForwardingStopped} to prevent premature cancellation of
+     * forwarding.
+     *
+     * @return the popup to which this listener is forwarding events
+     */
+    public abstract ShowableListMenu getPopup();
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        final boolean wasForwarding = mForwarding;
+        final boolean forwarding;
+        if (wasForwarding) {
+            forwarding = onTouchForwarded(event) || !onForwardingStopped();
+        } else {
+            forwarding = onTouchObserved(event) && onForwardingStarted();
+
+            if (forwarding) {
+                // Make sure we cancel any ongoing source event stream.
+                final long now = SystemClock.uptimeMillis();
+                final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
+                        0.0f, 0.0f, 0);
+                mSrc.onTouchEvent(e);
+                e.recycle();
+            }
+        }
+
+        mForwarding = forwarding;
+        return forwarding || wasForwarding;
+    }
+
+    @Override
+    public void onViewAttachedToWindow(View v) {
+    }
+
+    @Override
+    public void onViewDetachedFromWindow(View v) {
+        mForwarding = false;
+        mActivePointerId = MotionEvent.INVALID_POINTER_ID;
+
+        if (mDisallowIntercept != null) {
+            mSrc.removeCallbacks(mDisallowIntercept);
+        }
+    }
+
+    /**
+     * Called when forwarding would like to start.
+     * <p>
+     * By default, this will show the popup returned by {@link #getPopup()}.
+     * It may be overridden to perform another action, like clicking the
+     * source view or preparing the popup before showing it.
+     *
+     * @return true to start forwarding, false otherwise
+     */
+    protected boolean onForwardingStarted() {
+        final ShowableListMenu popup = getPopup();
+        if (popup != null && !popup.isShowing()) {
+            popup.show();
+        }
+        return true;
+    }
+
+    /**
+     * Called when forwarding would like to stop.
+     * <p>
+     * By default, this will dismiss the popup returned by
+     * {@link #getPopup()}. It may be overridden to perform some other
+     * action.
+     *
+     * @return true to stop forwarding, false otherwise
+     */
+    protected boolean onForwardingStopped() {
+        final ShowableListMenu popup = getPopup();
+        if (popup != null && popup.isShowing()) {
+            popup.dismiss();
+        }
+        return true;
+    }
+
+    /**
+     * Observes motion events and determines when to start forwarding.
+     *
+     * @param srcEvent motion event in source view coordinates
+     * @return true to start forwarding motion events, false otherwise
+     */
+    private boolean onTouchObserved(MotionEvent srcEvent) {
+        final View src = mSrc;
+        if (!src.isEnabled()) {
+            return false;
+        }
+
+        final int actionMasked = srcEvent.getActionMasked();
+        switch (actionMasked) {
+            case MotionEvent.ACTION_DOWN:
+                mActivePointerId = srcEvent.getPointerId(0);
+                mWasLongPress = false;
+
+                if (mDisallowIntercept == null) {
+                    mDisallowIntercept = new DisallowIntercept();
+                }
+                src.postDelayed(mDisallowIntercept, mTapTimeout);
+
+                if (mTriggerLongPress == null) {
+                    mTriggerLongPress = new TriggerLongPress();
+                }
+                src.postDelayed(mTriggerLongPress, mLongPressTimeout);
+                break;
+            case MotionEvent.ACTION_MOVE:
+                final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
+                if (activePointerIndex >= 0) {
+                    final float x = srcEvent.getX(activePointerIndex);
+                    final float y = srcEvent.getY(activePointerIndex);
+
+                    // Has the pointer moved outside of the view?
+                    if (!src.pointInView(x, y, mScaledTouchSlop)) {
+                        clearCallbacks();
+
+                        // Don't let the parent intercept our events.
+                        src.getParent().requestDisallowInterceptTouchEvent(true);
+                        return true;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                clearCallbacks();
+                break;
+        }
+
+        return false;
+    }
+
+    private void clearCallbacks() {
+        if (mTriggerLongPress != null) {
+            mSrc.removeCallbacks(mTriggerLongPress);
+        }
+
+        if (mDisallowIntercept != null) {
+            mSrc.removeCallbacks(mDisallowIntercept);
+        }
+    }
+
+    private void onLongPress() {
+        clearCallbacks();
+
+        final View src = mSrc;
+        if (!src.isEnabled() || src.isLongClickable()) {
+            // Ignore long-press if the view is disabled or has its own
+            // handler.
+            return;
+        }
+
+        if (!onForwardingStarted()) {
+            return;
+        }
+
+        // Don't let the parent intercept our events.
+        src.getParent().requestDisallowInterceptTouchEvent(true);
+
+        // Make sure we cancel any ongoing source event stream.
+        final long now = SystemClock.uptimeMillis();
+        final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+        src.onTouchEvent(e);
+        e.recycle();
+
+        mForwarding = true;
+        mWasLongPress = true;
+    }
+
+    /**
+     * Handles forwarded motion events and determines when to stop
+     * forwarding.
+     *
+     * @param srcEvent motion event in source view coordinates
+     * @return true to continue forwarding motion events, false to cancel
+     */
+    private boolean onTouchForwarded(MotionEvent srcEvent) {
+        final View src = mSrc;
+        final ShowableListMenu popup = getPopup();
+        if (popup == null || !popup.isShowing()) {
+            return false;
+        }
+
+        final DropDownListView dst = (DropDownListView) popup.getListView();
+        if (dst == null || !dst.isShown()) {
+            return false;
+        }
+
+        // Convert event to destination-local coordinates.
+        final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
+        src.toGlobalMotionEvent(dstEvent);
+        dst.toLocalMotionEvent(dstEvent);
+
+        // Forward converted event to destination view, then recycle it.
+        final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
+        dstEvent.recycle();
+
+        // Always cancel forwarding when the touch stream ends.
+        final int action = srcEvent.getActionMasked();
+        final boolean keepForwarding = action != MotionEvent.ACTION_UP
+                && action != MotionEvent.ACTION_CANCEL;
+
+        return handled && keepForwarding;
+    }
+
+    private class DisallowIntercept implements Runnable {
+        @Override
+        public void run() {
+            final ViewParent parent = mSrc.getParent();
+            parent.requestDisallowInterceptTouchEvent(true);
+        }
+    }
+
+    private class TriggerLongPress implements Runnable {
+        @Override
+        public void run() {
+            onLongPress();
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
index a02efcf..3d07d87 100644
--- a/core/java/android/widget/ListPopupWindow.java
+++ b/core/java/android/widget/ListPopupWindow.java
@@ -25,7 +25,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
-import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.IntProperty;
@@ -36,13 +35,13 @@
 import android.view.View;
 import android.view.View.MeasureSpec;
 import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.WindowManager;
 import android.view.animation.AccelerateDecelerateInterpolator;
 
 import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
 import com.android.internal.widget.AutoScrollHelper.AbsListViewAutoScroller;
 
 import java.util.Locale;
@@ -58,7 +57,7 @@
  * @see android.widget.AutoCompleteTextView
  * @see android.widget.Spinner
  */
-public class ListPopupWindow {
+public class ListPopupWindow implements ShowableListMenu {
     private static final String TAG = "ListPopupWindow";
     private static final boolean DEBUG = false;
 
@@ -580,6 +579,7 @@
      * Show the popup list. If the list is already showing, this method
      * will recalculate the popup's size and position.
      */
+    @Override
     public void show() {
         int height = buildDropDown();
 
@@ -671,6 +671,7 @@
     /**
      * Dismiss the popup window.
      */
+    @Override
     public void dismiss() {
         mPopup.dismiss();
         removePromptView();
@@ -732,7 +733,7 @@
     public void setSelection(int position) {
         DropDownListView list = mDropDownList;
         if (isShowing() && list != null) {
-            list.mListSelectionHidden = false;
+            list.setListSelectionHidden(false);
             list.setSelection(position);
             if (list.getChoiceMode() != ListView.CHOICE_MODE_NONE) {
                 list.setItemChecked(position, true);
@@ -748,7 +749,7 @@
         final DropDownListView list = mDropDownList;
         if (list != null) {
             // WARNING: Please read the comment where mListSelectionHidden is declared
-            list.mListSelectionHidden = true;
+            list.setListSelectionHidden(true);
             list.hideSelector();
             list.requestLayout();
         }
@@ -757,6 +758,7 @@
     /**
      * @return {@code true} if the popup is currently showing, {@code false} otherwise.
      */
+    @Override
     public boolean isShowing() {
         return mPopup.isShowing();
     }
@@ -842,6 +844,7 @@
      * @return The {@link ListView} displayed within the popup window.
      * Only valid when {@link #isShowing()} == {@code true}.
      */
+    @Override
     public ListView getListView() {
         return mDropDownList;
     }
@@ -911,7 +914,7 @@
                 } else {
                     // WARNING: Please read the comment where mListSelectionHidden
                     //          is declared
-                    mDropDownList.mListSelectionHidden = false;
+                    mDropDownList.setListSelectionHidden(false);
                 }
 
                 consumed = mDropDownList.onKeyDown(keyCode, event);
@@ -1037,7 +1040,7 @@
     public OnTouchListener createDragToOpenListener(View src) {
         return new ForwardingListener(src) {
             @Override
-            public ListPopupWindow getPopup() {
+            public ShowableListMenu getPopup() {
                 return ListPopupWindow.this;
             }
         };
@@ -1088,7 +1091,7 @@
                         DropDownListView dropDownList = mDropDownList;
 
                         if (dropDownList != null) {
-                            dropDownList.mListSelectionHidden = false;
+                            dropDownList.setListSelectionHidden(false);
                         }
                     }
                 }
@@ -1219,568 +1222,6 @@
         return listContent + otherHeights;
     }
 
-    /**
-     * Abstract class that forwards touch events to a {@link ListPopupWindow}.
-     *
-     * @hide
-     */
-    public static abstract class ForwardingListener
-            implements View.OnTouchListener, View.OnAttachStateChangeListener {
-        /** Scaled touch slop, used for detecting movement outside bounds. */
-        private final float mScaledTouchSlop;
-
-        /** Timeout before disallowing intercept on the source's parent. */
-        private final int mTapTimeout;
-
-        /** Timeout before accepting a long-press to start forwarding. */
-        private final int mLongPressTimeout;
-
-        /** Source view from which events are forwarded. */
-        private final View mSrc;
-
-        /** Runnable used to prevent conflicts with scrolling parents. */
-        private Runnable mDisallowIntercept;
-
-        /** Runnable used to trigger forwarding on long-press. */
-        private Runnable mTriggerLongPress;
-
-        /** Whether this listener is currently forwarding touch events. */
-        private boolean mForwarding;
-
-        /**
-         * Whether forwarding was initiated by a long-press. If so, we won't
-         * force the window to dismiss when the touch stream ends.
-         */
-        private boolean mWasLongPress;
-
-        /** The id of the first pointer down in the current event stream. */
-        private int mActivePointerId;
-
-        public ForwardingListener(View src) {
-            mSrc = src;
-            mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
-            mTapTimeout = ViewConfiguration.getTapTimeout();
-
-            // Use a medium-press timeout. Halfway between tap and long-press.
-            mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
-
-            src.addOnAttachStateChangeListener(this);
-        }
-
-        /**
-         * Returns the popup to which this listener is forwarding events.
-         * <p>
-         * Override this to return the correct popup. If the popup is displayed
-         * asynchronously, you may also need to override
-         * {@link #onForwardingStopped} to prevent premature cancelation of
-         * forwarding.
-         *
-         * @return the popup to which this listener is forwarding events
-         */
-        public abstract ListPopupWindow getPopup();
-
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            final boolean wasForwarding = mForwarding;
-            final boolean forwarding;
-            if (wasForwarding) {
-                forwarding = onTouchForwarded(event) || !onForwardingStopped();
-            } else {
-                forwarding = onTouchObserved(event) && onForwardingStarted();
-
-                if (forwarding) {
-                    // Make sure we cancel any ongoing source event stream.
-                    final long now = SystemClock.uptimeMillis();
-                    final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
-                            0.0f, 0.0f, 0);
-                    mSrc.onTouchEvent(e);
-                    e.recycle();
-                }
-            }
-
-            mForwarding = forwarding;
-            return forwarding || wasForwarding;
-        }
-
-        @Override
-        public void onViewAttachedToWindow(View v) {
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            mForwarding = false;
-            mActivePointerId = MotionEvent.INVALID_POINTER_ID;
-
-            if (mDisallowIntercept != null) {
-                mSrc.removeCallbacks(mDisallowIntercept);
-            }
-        }
-
-        /**
-         * Called when forwarding would like to start.
-         * <p>
-         * By default, this will show the popup returned by {@link #getPopup()}.
-         * It may be overridden to perform another action, like clicking the
-         * source view or preparing the popup before showing it.
-         *
-         * @return true to start forwarding, false otherwise
-         */
-        protected boolean onForwardingStarted() {
-            final ListPopupWindow popup = getPopup();
-            if (popup != null && !popup.isShowing()) {
-                popup.show();
-            }
-            return true;
-        }
-
-        /**
-         * Called when forwarding would like to stop.
-         * <p>
-         * By default, this will dismiss the popup returned by
-         * {@link #getPopup()}. It may be overridden to perform some other
-         * action.
-         *
-         * @return true to stop forwarding, false otherwise
-         */
-        protected boolean onForwardingStopped() {
-            final ListPopupWindow popup = getPopup();
-            if (popup != null && popup.isShowing()) {
-                popup.dismiss();
-            }
-            return true;
-        }
-
-        /**
-         * Observes motion events and determines when to start forwarding.
-         *
-         * @param srcEvent motion event in source view coordinates
-         * @return true to start forwarding motion events, false otherwise
-         */
-        private boolean onTouchObserved(MotionEvent srcEvent) {
-            final View src = mSrc;
-            if (!src.isEnabled()) {
-                return false;
-            }
-
-            final int actionMasked = srcEvent.getActionMasked();
-            switch (actionMasked) {
-                case MotionEvent.ACTION_DOWN:
-                    mActivePointerId = srcEvent.getPointerId(0);
-                    mWasLongPress = false;
-
-                    if (mDisallowIntercept == null) {
-                        mDisallowIntercept = new DisallowIntercept();
-                    }
-                    src.postDelayed(mDisallowIntercept, mTapTimeout);
-
-                    if (mTriggerLongPress == null) {
-                        mTriggerLongPress = new TriggerLongPress();
-                    }
-                    src.postDelayed(mTriggerLongPress, mLongPressTimeout);
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
-                    if (activePointerIndex >= 0) {
-                        final float x = srcEvent.getX(activePointerIndex);
-                        final float y = srcEvent.getY(activePointerIndex);
-
-                        // Has the pointer has moved outside of the view?
-                        if (!src.pointInView(x, y, mScaledTouchSlop)) {
-                            clearCallbacks();
-
-                            // Don't let the parent intercept our events.
-                            src.getParent().requestDisallowInterceptTouchEvent(true);
-                            return true;
-                        }
-                    }
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    clearCallbacks();
-                    break;
-            }
-
-            return false;
-        }
-
-        private void clearCallbacks() {
-            if (mTriggerLongPress != null) {
-                mSrc.removeCallbacks(mTriggerLongPress);
-            }
-
-            if (mDisallowIntercept != null) {
-                mSrc.removeCallbacks(mDisallowIntercept);
-            }
-        }
-
-        private void onLongPress() {
-            clearCallbacks();
-
-            final View src = mSrc;
-            if (!src.isEnabled() || src.isLongClickable()) {
-                // Ignore long-press if the view is disabled or has its own
-                // handler.
-                return;
-            }
-
-            if (!onForwardingStarted()) {
-                return;
-            }
-
-            // Don't let the parent intercept our events.
-            src.getParent().requestDisallowInterceptTouchEvent(true);
-
-            // Make sure we cancel any ongoing source event stream.
-            final long now = SystemClock.uptimeMillis();
-            final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
-            src.onTouchEvent(e);
-            e.recycle();
-
-            mForwarding = true;
-            mWasLongPress = true;
-        }
-
-        /**
-         * Handled forwarded motion events and determines when to stop
-         * forwarding.
-         *
-         * @param srcEvent motion event in source view coordinates
-         * @return true to continue forwarding motion events, false to cancel
-         */
-        private boolean onTouchForwarded(MotionEvent srcEvent) {
-            final View src = mSrc;
-            final ListPopupWindow popup = getPopup();
-            if (popup == null || !popup.isShowing()) {
-                return false;
-            }
-
-            final DropDownListView dst = popup.mDropDownList;
-            if (dst == null || !dst.isShown()) {
-                return false;
-            }
-
-            // Convert event to destination-local coordinates.
-            final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
-            src.toGlobalMotionEvent(dstEvent);
-            dst.toLocalMotionEvent(dstEvent);
-
-            // Forward converted event to destination view, then recycle it.
-            final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
-            dstEvent.recycle();
-
-            // Always cancel forwarding when the touch stream ends.
-            final int action = srcEvent.getActionMasked();
-            final boolean keepForwarding = action != MotionEvent.ACTION_UP
-                    && action != MotionEvent.ACTION_CANCEL;
-
-            return handled && keepForwarding;
-        }
-
-        private class DisallowIntercept implements Runnable {
-            @Override
-            public void run() {
-                final ViewParent parent = mSrc.getParent();
-                parent.requestDisallowInterceptTouchEvent(true);
-            }
-        }
-
-        private class TriggerLongPress implements Runnable {
-            @Override
-            public void run() {
-                onLongPress();
-            }
-        }
-    }
-
-    /**
-     * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
-     * make sure the list uses the appropriate drawables and states when
-     * displayed on screen within a drop down. The focus is never actually
-     * passed to the drop down in this mode; the list only looks focused.</p>
-     */
-    static class DropDownListView extends ListView {
-        /** Duration in milliseconds of the drag-to-open click animation. */
-        private static final long CLICK_ANIM_DURATION = 150;
-
-        /** Target alpha value for drag-to-open click animation. */
-        private static final int CLICK_ANIM_ALPHA = 0x80;
-
-        /** Wrapper around Drawable's <code>alpha</code> property. */
-        private static final IntProperty<Drawable> DRAWABLE_ALPHA =
-                new IntProperty<Drawable>("alpha") {
-                    @Override
-                    public void setValue(Drawable object, int value) {
-                        object.setAlpha(value);
-                    }
-
-                    @Override
-                    public Integer get(Drawable object) {
-                        return object.getAlpha();
-                    }
-                };
-
-        /*
-         * WARNING: This is a workaround for a touch mode issue.
-         *
-         * Touch mode is propagated lazily to windows. This causes problems in
-         * the following scenario:
-         * - Type something in the AutoCompleteTextView and get some results
-         * - Move down with the d-pad to select an item in the list
-         * - Move up with the d-pad until the selection disappears
-         * - Type more text in the AutoCompleteTextView *using the soft keyboard*
-         *   and get new results; you are now in touch mode
-         * - The selection comes back on the first item in the list, even though
-         *   the list is supposed to be in touch mode
-         *
-         * Using the soft keyboard triggers the touch mode change but that change
-         * is propagated to our window only after the first list layout, therefore
-         * after the list attempts to resurrect the selection.
-         *
-         * The trick to work around this issue is to pretend the list is in touch
-         * mode when we know that the selection should not appear, that is when
-         * we know the user moved the selection away from the list.
-         *
-         * This boolean is set to true whenever we explicitly hide the list's
-         * selection and reset to false whenever we know the user moved the
-         * selection back to the list.
-         *
-         * When this boolean is true, isInTouchMode() returns true, otherwise it
-         * returns super.isInTouchMode().
-         */
-        private boolean mListSelectionHidden;
-        
-        /**
-         * True if this wrapper should fake focus.
-         */
-        private boolean mHijackFocus;
-
-        /** Whether to force drawing of the pressed state selector. */
-        private boolean mDrawsInPressedState;
-
-        /** Current drag-to-open click animation, if any. */
-        private Animator mClickAnimation;
-
-        /** Helper for drag-to-open auto scrolling. */
-        private AbsListViewAutoScroller mScrollHelper;
-
-        /**
-         * <p>Creates a new list view wrapper.</p>
-         *
-         * @param context this view's context
-         */
-        public DropDownListView(Context context, boolean hijackFocus) {
-            super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
-            mHijackFocus = hijackFocus;
-            // TODO: Add an API to control this
-            setCacheColorHint(0); // Transparent, since the background drawable could be anything.
-        }
-
-        /**
-         * Handles forwarded events.
-         *
-         * @param activePointerId id of the pointer that activated forwarding
-         * @return whether the event was handled
-         */
-        public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
-            boolean handledEvent = true;
-            boolean clearPressedItem = false;
-
-            final int actionMasked = event.getActionMasked();
-            switch (actionMasked) {
-                case MotionEvent.ACTION_CANCEL:
-                    handledEvent = false;
-                    break;
-                case MotionEvent.ACTION_UP:
-                    handledEvent = false;
-                    // $FALL-THROUGH$
-                case MotionEvent.ACTION_MOVE:
-                    final int activeIndex = event.findPointerIndex(activePointerId);
-                    if (activeIndex < 0) {
-                        handledEvent = false;
-                        break;
-                    }
-
-                    final int x = (int) event.getX(activeIndex);
-                    final int y = (int) event.getY(activeIndex);
-                    final int position = pointToPosition(x, y);
-                    if (position == INVALID_POSITION) {
-                        clearPressedItem = true;
-                        break;
-                    }
-
-                    final View child = getChildAt(position - getFirstVisiblePosition());
-                    setPressedItem(child, position, x, y);
-                    handledEvent = true;
-
-                    if (actionMasked == MotionEvent.ACTION_UP) {
-                        clickPressedItem(child, position);
-                    }
-                    break;
-            }
-
-            // Failure to handle the event cancels forwarding.
-            if (!handledEvent || clearPressedItem) {
-                clearPressedItem();
-            }
-
-            // Manage automatic scrolling.
-            if (handledEvent) {
-                if (mScrollHelper == null) {
-                    mScrollHelper = new AbsListViewAutoScroller(this);
-                }
-                mScrollHelper.setEnabled(true);
-                mScrollHelper.onTouch(this, event);
-            } else if (mScrollHelper != null) {
-                mScrollHelper.setEnabled(false);
-            }
-
-            return handledEvent;
-        }
-
-        /**
-         * Starts an alpha animation on the selector. When the animation ends,
-         * the list performs a click on the item.
-         */
-        private void clickPressedItem(final View child, final int position) {
-            final long id = getItemIdAtPosition(position);
-            final Animator anim = ObjectAnimator.ofInt(
-                    mSelector, DRAWABLE_ALPHA, 0xFF, CLICK_ANIM_ALPHA, 0xFF);
-            anim.setDuration(CLICK_ANIM_DURATION);
-            anim.setInterpolator(new AccelerateDecelerateInterpolator());
-            anim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                public void onAnimationEnd(Animator animation) {
-                    performItemClick(child, position, id);
-                }
-            });
-            anim.start();
-
-            if (mClickAnimation != null) {
-                mClickAnimation.cancel();
-            }
-            mClickAnimation = anim;
-        }
-
-        private void clearPressedItem() {
-            mDrawsInPressedState = false;
-            setPressed(false);
-            updateSelectorState();
-
-            final View motionView = getChildAt(mMotionPosition - mFirstPosition);
-            if (motionView != null) {
-                motionView.setPressed(false);
-            }
-
-            if (mClickAnimation != null) {
-                mClickAnimation.cancel();
-                mClickAnimation = null;
-            }
-        }
-
-        private void setPressedItem(View child, int position, float x, float y) {
-            mDrawsInPressedState = true;
-
-            // Ordering is essential. First, update the container's pressed state.
-            drawableHotspotChanged(x, y);
-            if (!isPressed()) {
-                setPressed(true);
-            }
-
-            // Next, run layout if we need to stabilize child positions.
-            if (mDataChanged) {
-                layoutChildren();
-            }
-
-            // Manage the pressed view based on motion position. This allows us to
-            // play nicely with actual touch and scroll events.
-            final View motionView = getChildAt(mMotionPosition - mFirstPosition);
-            if (motionView != null && motionView != child && motionView.isPressed()) {
-                motionView.setPressed(false);
-            }
-            mMotionPosition = position;
-
-            // Offset for child coordinates.
-            final float childX = x - child.getLeft();
-            final float childY = y - child.getTop();
-            child.drawableHotspotChanged(childX, childY);
-            if (!child.isPressed()) {
-                child.setPressed(true);
-            }
-
-            // Ensure that keyboard focus starts from the last touched position.
-            setSelectedPositionInt(position);
-            positionSelectorLikeTouch(position, child, x, y);
-
-            // Refresh the drawable state to reflect the new pressed state,
-            // which will also update the selector state.
-            refreshDrawableState();
-
-            if (mClickAnimation != null) {
-                mClickAnimation.cancel();
-                mClickAnimation = null;
-            }
-        }
-
-        @Override
-        boolean touchModeDrawsInPressedState() {
-            return mDrawsInPressedState || super.touchModeDrawsInPressedState();
-        }
-
-        /**
-         * <p>Avoids jarring scrolling effect by ensuring that list elements
-         * made of a text view fit on a single line.</p>
-         *
-         * @param position the item index in the list to get a view for
-         * @return the view for the specified item
-         */
-        @Override
-        View obtainView(int position, boolean[] isScrap) {
-            View view = super.obtainView(position, isScrap);
-
-            if (view instanceof TextView) {
-                ((TextView) view).setHorizontallyScrolling(true);
-            }
-
-            return view;
-        }
-
-        @Override
-        public boolean isInTouchMode() {
-            // WARNING: Please read the comment where mListSelectionHidden is declared
-            return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean hasWindowFocus() {
-            return mHijackFocus || super.hasWindowFocus();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean isFocused() {
-            return mHijackFocus || super.isFocused();
-        }
-
-        /**
-         * <p>Returns the focus state in the drop down.</p>
-         *
-         * @return true always if hijacking focus
-         */
-        @Override
-        public boolean hasFocus() {
-            return mHijackFocus || super.hasFocus();
-        }
-    }
-
     private class PopupDataSetObserver extends DataSetObserver {
         @Override
         public void onChanged() {
diff --git a/core/java/android/widget/MenuPopupWindow.java b/core/java/android/widget/MenuPopupWindow.java
index 8d42c73..9e47e85 100644
--- a/core/java/android/widget/MenuPopupWindow.java
+++ b/core/java/android/widget/MenuPopupWindow.java
@@ -36,11 +36,11 @@
     }
 
     @Override
-    ListPopupWindow.DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
+    DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
         return new MenuDropDownListView(context, hijackFocus);
     }
 
-    static class MenuDropDownListView extends ListPopupWindow.DropDownListView {
+    static class MenuDropDownListView extends DropDownListView {
         private boolean mHoveredOnDisabledItem = false;
         private AccessibilityManager mAccessibilityManager;
 
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 1507dfb..3b2d60d 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -20,6 +20,7 @@
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.view.menu.MenuPopupHelper;
 import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.ShowableListMenu;
 import com.android.internal.view.menu.SubMenuBuilder;
 
 import android.annotation.MenuRes;
@@ -30,7 +31,6 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.View.OnTouchListener;
-import android.widget.ListPopupWindow.ForwardingListener;
 
 /**
  * A PopupMenu displays a {@link Menu} in a modal popup window anchored to a {@link View}.
@@ -170,7 +170,7 @@
                 }
 
                 @Override
-                public ListPopupWindow getPopup() {
+                public ShowableListMenu getPopup() {
                     // This will be null until show() is called.
                     return mPopup.getPopup();
                 }
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index f3cf61c..c79e184 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -17,6 +17,7 @@
 package android.widget;
 
 import com.android.internal.R;
+import com.android.internal.view.menu.ShowableListMenu;
 
 import android.annotation.DrawableRes;
 import android.annotation.Nullable;
@@ -44,7 +45,6 @@
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.ListPopupWindow.ForwardingListener;
 import android.widget.PopupWindow.OnDismissListener;
 
 /**
@@ -278,7 +278,7 @@
                 mPopup = popup;
                 mForwardingListener = new ForwardingListener(this) {
                     @Override
-                    public ListPopupWindow getPopup() {
+                    public ShowableListMenu getPopup() {
                         return popup;
                     }
 
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 8db363d..ce5bc90 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -29,10 +29,10 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.ActionMenuView;
+import android.widget.ForwardingListener;
 import android.widget.ListPopupWindow;
 import android.widget.TextView;
 import android.widget.Toast;
-import android.widget.ListPopupWindow.ForwardingListener;
 
 /**
  * @hide
@@ -320,7 +320,7 @@
         }
 
         @Override
-        public ListPopupWindow getPopup() {
+        public ShowableListMenu getPopup() {
             if (mPopupCallback != null) {
                 return mPopupCallback.getPopup();
             }
@@ -331,7 +331,7 @@
         protected boolean onForwardingStarted() {
             // Call the invoker, then check if the expected popup is showing.
             if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
-                final ListPopupWindow popup = getPopup();
+                final ShowableListMenu popup = getPopup();
                 return popup != null && popup.isShowing();
             }
             return false;
@@ -339,7 +339,7 @@
 
         @Override
         protected boolean onForwardingStopped() {
-            final ListPopupWindow popup = getPopup();
+            final ShowableListMenu popup = getPopup();
             if (popup != null) {
                 popup.dismiss();
                 return true;
@@ -349,6 +349,6 @@
     }
 
     public static abstract class PopupCallback {
-        public abstract ListPopupWindow getPopup();
+        public abstract ShowableListMenu getPopup();
     }
 }
diff --git a/core/java/com/android/internal/view/menu/MenuAdapter.java b/core/java/com/android/internal/view/menu/MenuAdapter.java
new file mode 100644
index 0000000..1e03b1f
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuAdapter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+public class MenuAdapter extends BaseAdapter {
+    static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
+
+    MenuBuilder mAdapterMenu;
+
+    private int mExpandedIndex = -1;
+
+    private boolean mForceShowIcon;
+    private final boolean mOverflowOnly;
+    private final LayoutInflater mInflater;
+
+    public MenuAdapter(MenuBuilder menu, LayoutInflater inflater, boolean overflowOnly) {
+        mOverflowOnly = overflowOnly;
+        mInflater = inflater;
+        mAdapterMenu = menu;
+        findExpandedIndex();
+    }
+
+    public void setForceShowIcon(boolean forceShow) {
+        mForceShowIcon = forceShow;
+    }
+
+    public int getCount() {
+        ArrayList<MenuItemImpl> items = mOverflowOnly ?
+                mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+        if (mExpandedIndex < 0) {
+            return items.size();
+        }
+        return items.size() - 1;
+    }
+
+    public MenuBuilder getAdapterMenu() {
+        return mAdapterMenu;
+    }
+
+    public MenuItemImpl getItem(int position) {
+        ArrayList<MenuItemImpl> items = mOverflowOnly ?
+                mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+        if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
+            position++;
+        }
+        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;
+        if (mForceShowIcon) {
+            ((ListMenuItemView) convertView).setForceShowIcon(true);
+        }
+        itemView.initialize(getItem(position), 0);
+        return convertView;
+    }
+
+    void findExpandedIndex() {
+        final MenuItemImpl expandedItem = mAdapterMenu.getExpandedItem();
+        if (expandedItem != null) {
+            final ArrayList<MenuItemImpl> items = mAdapterMenu.getNonActionItems();
+            final int count = items.size();
+            for (int i = 0; i < count; i++) {
+                final MenuItemImpl item = items.get(i);
+                if (item == expandedItem) {
+                    mExpandedIndex = i;
+                    return;
+                }
+            }
+        }
+        mExpandedIndex = -1;
+    }
+
+    @Override
+    public void notifyDataSetChanged() {
+        findExpandedIndex();
+        super.notifyDataSetChanged();
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 13654a6..e6bc6c3 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -89,7 +89,7 @@
         mContext = context;
         mInflater = LayoutInflater.from(context);
         mMenu = menu;
-        mAdapter = new MenuAdapter(mMenu);
+        mAdapter = new MenuAdapter(mMenu, mInflater, overflowOnly);
         mOverflowOnly = overflowOnly;
         mPopupStyleAttr = popupStyleAttr;
         mPopupStyleRes = popupStyleRes;
@@ -358,73 +358,4 @@
     @Override
     public void onRestoreInstanceState(Parcelable state) {
     }
-
-    private class MenuAdapter extends BaseAdapter {
-        private MenuBuilder mAdapterMenu;
-        private int mExpandedIndex = -1;
-
-        public MenuAdapter(MenuBuilder menu) {
-            mAdapterMenu = menu;
-            findExpandedIndex();
-        }
-
-        public int getCount() {
-            ArrayList<MenuItemImpl> items = mOverflowOnly ?
-                    mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
-            if (mExpandedIndex < 0) {
-                return items.size();
-            }
-            return items.size() - 1;
-        }
-
-        public MenuItemImpl getItem(int position) {
-            ArrayList<MenuItemImpl> items = mOverflowOnly ?
-                    mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
-            if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
-                position++;
-            }
-            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;
-            if (mForceShowIcon) {
-                ((ListMenuItemView) convertView).setForceShowIcon(true);
-            }
-            itemView.initialize(getItem(position), 0);
-            return convertView;
-        }
-
-        void findExpandedIndex() {
-            final MenuItemImpl expandedItem = mMenu.getExpandedItem();
-            if (expandedItem != null) {
-                final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
-                final int count = items.size();
-                for (int i = 0; i < count; i++) {
-                    final MenuItemImpl item = items.get(i);
-                    if (item == expandedItem) {
-                        mExpandedIndex = i;
-                        return;
-                    }
-                }
-            }
-            mExpandedIndex = -1;
-        }
-
-        @Override
-        public void notifyDataSetChanged() {
-            findExpandedIndex();
-            super.notifyDataSetChanged();
-        }
-    }
 }
diff --git a/core/java/com/android/internal/view/menu/ShowableListMenu.java b/core/java/com/android/internal/view/menu/ShowableListMenu.java
new file mode 100644
index 0000000..ca158fd
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ShowableListMenu.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 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.widget.ListView;
+
+/**
+ * A list menu which can be shown and hidden and which is internally represented by a ListView.
+ */
+public interface ShowableListMenu {
+    public void show();
+
+    public void dismiss();
+
+    public boolean isShowing();
+
+    /**
+     * @return The internal ListView for the visible menu.
+     */
+    public ListView getListView();
+}
diff --git a/core/tests/coretests/src/android/view/HandlerActionQueueTest.java b/core/tests/coretests/src/android/view/HandlerActionQueueTest.java
new file mode 100644
index 0000000..fd8f23a
--- /dev/null
+++ b/core/tests/coretests/src/android/view/HandlerActionQueueTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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 android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+public class HandlerActionQueueTest extends AndroidTestCase {
+
+    @SmallTest
+    public void testPostAndRemove() {
+        HandlerActionQueue runQueue = new HandlerActionQueue();
+        MockRunnable runnable1 = new MockRunnable();
+        MockRunnable runnable2 = new MockRunnable();
+        MockRunnable runnable3 = new MockRunnable();
+
+        runQueue.post(runnable1);
+        runQueue.post(runnable1);
+        runQueue.post(runnable2);
+        runQueue.postDelayed(runnable1, 100);
+        runQueue.postDelayed(null, 500);
+        assertEquals(5, runQueue.size());
+        assertEquals(0, runQueue.getDelay(0));
+        assertEquals(0, runQueue.getDelay(1));
+        assertEquals(0, runQueue.getDelay(2));
+        assertEquals(100, runQueue.getDelay(3));
+        assertEquals(500, runQueue.getDelay(4));
+        assertEquals(500, runQueue.getDelay(4));
+        assertEquals(runnable1, runQueue.getRunnable(0));
+        assertEquals(runnable1, runQueue.getRunnable(1));
+        assertEquals(runnable2, runQueue.getRunnable(2));
+        assertEquals(runnable1, runQueue.getRunnable(3));
+        assertEquals(null, runQueue.getRunnable(4));
+
+        runQueue.removeCallbacks(runnable1);
+        assertEquals(2, runQueue.size());
+        assertEquals(0, runQueue.getDelay(0));
+        assertEquals(500, runQueue.getDelay(1));
+        assertEquals(runnable2, runQueue.getRunnable(0));
+        assertEquals(null, runQueue.getRunnable(1));
+
+        try {
+            assertNull(runQueue.getRunnable(2));
+            assertFalse(true);
+        } catch (IndexOutOfBoundsException e) {
+            // Should throw an exception.
+        }
+
+        runQueue.removeCallbacks(runnable3);
+        assertEquals(2, runQueue.size());
+
+        runQueue.removeCallbacks(runnable2);
+        assertEquals(1, runQueue.size());
+        assertEquals(null, runQueue.getRunnable(0));
+
+        runQueue.removeCallbacks(null);
+        assertEquals(0, runQueue.size());
+    }
+
+    private static class MockRunnable implements Runnable {
+        @Override
+        public void run() {
+
+        }
+    }
+}
diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java
index 1cfccc4..1747225 100644
--- a/graphics/java/android/graphics/drawable/VectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/VectorDrawable.java
@@ -726,7 +726,7 @@
         Log.v(LOGTAG, indent + "matrix is :" + currentGroup.getLocalMatrix().toString());
         // Then print all the children groups
         for (int i = 0; i < currentGroup.mChildren.size(); i++) {
-            Object child = currentGroup.mChildren.get(i);
+            final VObject child = currentGroup.mChildren.get(i);
             if (child instanceof VGroup) {
                 printGroupTree((VGroup) child, level + 1);
             }
@@ -783,12 +783,6 @@
                 mThemeAttrs = copy.mThemeAttrs;
                 mChangingConfigurations = copy.mChangingConfigurations;
                 mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
-                if (copy.mVPathRenderer.mFillPaint != null) {
-                    mVPathRenderer.mFillPaint = new Paint(copy.mVPathRenderer.mFillPaint);
-                }
-                if (copy.mVPathRenderer.mStrokePaint != null) {
-                    mVPathRenderer.mStrokePaint = new Paint(copy.mVPathRenderer.mStrokePaint);
-                }
                 mTint = copy.mTint;
                 mTintMode = copy.mTintMode;
                 mAutoMirrored = copy.mAutoMirrored;
@@ -913,13 +907,7 @@
          */
         // Variables that only used temporarily inside the draw() call, so there
         // is no need for deep copying.
-        private final Path mPath;
-        private final Path mRenderPath;
-        private final Matrix mFinalPathMatrix = new Matrix();
-
-        private Paint mStrokePaint;
-        private Paint mFillPaint;
-        private PathMeasure mPathMeasure;
+        private final TempState mTempState = new TempState();
 
         /////////////////////////////////////////////////////
         // Variables below need to be copied (deep copy if applicable) for mutation.
@@ -935,12 +923,10 @@
 
         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
 
-        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
+        final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<>();
 
         public VPathRenderer() {
             mRootGroup = new VGroup();
-            mPath = new Path();
-            mRenderPath = new Path();
         }
 
         public void setRootAlpha(int alpha) {
@@ -964,8 +950,6 @@
 
         public VPathRenderer(VPathRenderer copy) {
             mRootGroup = new VGroup(copy.mRootGroup, mVGTargetsMap);
-            mPath = new Path(copy.mPath);
-            mRenderPath = new Path(copy.mRenderPath);
             mBaseWidth = copy.mBaseWidth;
             mBaseHeight = copy.mBaseHeight;
             mViewportWidth = copy.mViewportWidth;
@@ -981,215 +965,28 @@
         }
 
         public boolean canApplyTheme() {
-            // If one of the paths can apply theme, then return true;
-            return recursiveCanApplyTheme(mRootGroup);
-        }
-
-        private boolean recursiveCanApplyTheme(VGroup currentGroup) {
-            // We can do a tree traverse here, if there is one path return true,
-            // then we return true for the whole tree.
-            final ArrayList<Object> children = currentGroup.mChildren;
-
-            for (int i = 0; i < children.size(); i++) {
-                Object child = children.get(i);
-                if (child instanceof VGroup) {
-                    VGroup childGroup = (VGroup) child;
-                    if (childGroup.canApplyTheme()
-                            || recursiveCanApplyTheme(childGroup)) {
-                        return true;
-                    }
-                } else if (child instanceof VPath) {
-                    VPath childPath = (VPath) child;
-                    if (childPath.canApplyTheme()) {
-                        return true;
-                    }
-                }
-            }
-            return false;
+            return mRootGroup.canApplyTheme();
         }
 
         public void applyTheme(Theme t) {
-            // Apply theme to every path of the tree.
-            recursiveApplyTheme(mRootGroup, t);
-        }
-
-        private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
-            // We can do a tree traverse here, apply theme to all paths which
-            // can apply theme.
-            final ArrayList<Object> children = currentGroup.mChildren;
-            for (int i = 0; i < children.size(); i++) {
-                Object child = children.get(i);
-                if (child instanceof VGroup) {
-                    VGroup childGroup = (VGroup) child;
-                    if (childGroup.canApplyTheme()) {
-                        childGroup.applyTheme(t);
-                    }
-                    recursiveApplyTheme(childGroup, t);
-                } else if (child instanceof VPath) {
-                    VPath childPath = (VPath) child;
-                    if (childPath.canApplyTheme()) {
-                        childPath.applyTheme(t);
-                    }
-                }
-            }
-        }
-
-        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
-                Canvas canvas, int w, int h, ColorFilter filter) {
-            // Calculate current group's matrix by preConcat the parent's and
-            // and the current one on the top of the stack.
-            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
-            // Mi the local matrix at level i of the group tree.
-            currentGroup.mStackedMatrix.set(currentMatrix);
-            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
-
-            // Save the current clip information, which is local to this group.
-            canvas.save();
-            // Draw the group tree in the same order as the XML file.
-            for (int i = 0; i < currentGroup.mChildren.size(); i++) {
-                Object child = currentGroup.mChildren.get(i);
-                if (child instanceof VGroup) {
-                    VGroup childGroup = (VGroup) child;
-                    drawGroupTree(childGroup, currentGroup.mStackedMatrix,
-                            canvas, w, h, filter);
-                } else if (child instanceof VPath) {
-                    VPath childPath = (VPath) child;
-                    drawPath(currentGroup, childPath, canvas, w, h, filter);
-                }
-            }
-            canvas.restore();
+            mRootGroup.applyTheme(t);
         }
 
         public void draw(Canvas canvas, int w, int h, ColorFilter filter) {
-            // Travese the tree in pre-order to draw.
-            drawGroupTree(mRootGroup, Matrix.IDENTITY_MATRIX, canvas, w, h, filter);
-        }
-
-        private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
-                ColorFilter filter) {
             final float scaleX = w / mViewportWidth;
             final float scaleY = h / mViewportHeight;
-            final float minScale = Math.min(scaleX, scaleY);
-            final Matrix groupStackedMatrix = vGroup.mStackedMatrix;
-
-            mFinalPathMatrix.set(groupStackedMatrix);
-            mFinalPathMatrix.postScale(scaleX, scaleY);
-
-            final float matrixScale = getMatrixScale(groupStackedMatrix);
-            if (matrixScale == 0) {
-                // When either x or y is scaled to 0, we don't need to draw anything.
-                return;
-            }
-            vPath.toPath(mPath);
-            final Path path = mPath;
-
-            mRenderPath.reset();
-
-            if (vPath.isClipPath()) {
-                mRenderPath.addPath(path, mFinalPathMatrix);
-                canvas.clipPath(mRenderPath);
-            } else {
-                VFullPath fullPath = (VFullPath) vPath;
-                if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
-                    float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
-                    float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
-
-                    if (mPathMeasure == null) {
-                        mPathMeasure = new PathMeasure();
-                    }
-                    mPathMeasure.setPath(mPath, false);
-
-                    float len = mPathMeasure.getLength();
-                    start = start * len;
-                    end = end * len;
-                    path.reset();
-                    if (start > end) {
-                        mPathMeasure.getSegment(start, len, path, true);
-                        mPathMeasure.getSegment(0f, end, path, true);
-                    } else {
-                        mPathMeasure.getSegment(start, end, path, true);
-                    }
-                    path.rLineTo(0, 0); // fix bug in measure
-                }
-                mRenderPath.addPath(path, mFinalPathMatrix);
-
-                if (fullPath.mFillColor != Color.TRANSPARENT) {
-                    if (mFillPaint == null) {
-                        mFillPaint = new Paint();
-                        mFillPaint.setStyle(Paint.Style.FILL);
-                        mFillPaint.setAntiAlias(true);
-                    }
-
-                    final Paint fillPaint = mFillPaint;
-                    fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
-                    fillPaint.setColorFilter(filter);
-                    canvas.drawPath(mRenderPath, fillPaint);
-                }
-
-                if (fullPath.mStrokeColor != Color.TRANSPARENT) {
-                    if (mStrokePaint == null) {
-                        mStrokePaint = new Paint();
-                        mStrokePaint.setStyle(Paint.Style.STROKE);
-                        mStrokePaint.setAntiAlias(true);
-                    }
-
-                    final Paint strokePaint = mStrokePaint;
-                    if (fullPath.mStrokeLineJoin != null) {
-                        strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
-                    }
-
-                    if (fullPath.mStrokeLineCap != null) {
-                        strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
-                    }
-
-                    strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
-                    strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
-                    strokePaint.setColorFilter(filter);
-                    final float finalStrokeScale = minScale * matrixScale;
-                    strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
-                    canvas.drawPath(mRenderPath, strokePaint);
-                }
-            }
-        }
-
-        private float getMatrixScale(Matrix groupStackedMatrix) {
-            // Given unit vectors A = (0, 1) and B = (1, 0).
-            // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
-            // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
-            // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
-            // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
-            //
-            // For non-skew case, which is most of the cases, matrix scale is computing exactly the
-            // scale on x and y axis, and take the minimal of these two.
-            // For skew case, an unit square will mapped to a parallelogram. And this function will
-            // return the minimal height of the 2 bases.
-            float[] unitVectors = new float[] {0, 1, 1, 0};
-            groupStackedMatrix.mapVectors(unitVectors);
-            float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]);
-            float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]);
-            float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1],
-                    unitVectors[2], unitVectors[3]);
-            float maxScale = MathUtils.max(scaleX, scaleY);
-
-            float matrixScale = 0;
-            if (maxScale > 0) {
-                matrixScale = MathUtils.abs(crossProduct) / maxScale;
-            }
-            if (DBG_VECTOR_DRAWABLE) {
-                Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
-            }
-            return matrixScale;
+            mRootGroup.draw(canvas, mTempState, Matrix.IDENTITY_MATRIX, filter, scaleX, scaleY);
         }
     }
 
-    private static class VGroup {
+    private static class VGroup implements VObject {
         // mStackedMatrix is only used temporarily when drawing, it combines all
         // the parents' local matrices with the current one.
         private final Matrix mStackedMatrix = new Matrix();
 
         /////////////////////////////////////////////////////
         // Variables below need to be copied (deep copy if applicable) for mutation.
-        final ArrayList<Object> mChildren = new ArrayList<Object>();
+        final ArrayList<VObject> mChildren = new ArrayList<>();
 
         private float mRotate = 0;
         private float mPivotX = 0;
@@ -1223,14 +1020,14 @@
 
             mLocalMatrix.set(copy.mLocalMatrix);
 
-            final ArrayList<Object> children = copy.mChildren;
+            final ArrayList<VObject> children = copy.mChildren;
             for (int i = 0; i < children.size(); i++) {
-                Object copyChild = children.get(i);
+                final VObject copyChild = children.get(i);
                 if (copyChild instanceof VGroup) {
-                    VGroup copyGroup = (VGroup) copyChild;
+                    final VGroup copyGroup = (VGroup) copyChild;
                     mChildren.add(new VGroup(copyGroup, targetsMap));
                 } else {
-                    VPath newPath = null;
+                    final VPath newPath;
                     if (copyChild instanceof VFullPath) {
                         newPath = new VFullPath((VFullPath) copyChild);
                     } else if (copyChild instanceof VClipPath) {
@@ -1257,6 +1054,30 @@
             return mLocalMatrix;
         }
 
+        @Override
+        public void draw(Canvas canvas, TempState temp, Matrix currentMatrix,
+                ColorFilter filter, float scaleX, float scaleY) {
+            // Calculate current group's matrix by preConcat the parent's and
+            // and the current one on the top of the stack.
+            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
+            // Mi the local matrix at level i of the group tree.
+            mStackedMatrix.set(currentMatrix);
+            mStackedMatrix.preConcat(mLocalMatrix);
+
+            // Save the current clip information, which is local to this group.
+            canvas.save();
+
+            // Draw the group tree in the same order as the XML file.
+            for (int i = 0, count = mChildren.size(); i < count; i++) {
+                final VObject child = mChildren.get(i);
+                child.draw(canvas, temp, mStackedMatrix, filter, scaleX, scaleY);
+            }
+
+            // Restore the previous clip information.
+            canvas.restore();
+        }
+
+        @Override
         public void inflate(Resources res, AttributeSet attrs, Theme theme) {
             final TypedArray a = obtainAttributes(res, theme, attrs,
                     R.styleable.VectorDrawableGroup);
@@ -1287,18 +1108,39 @@
             updateLocalMatrix();
         }
 
+        @Override
         public boolean canApplyTheme() {
-            return mThemeAttrs != null;
-        }
-
-        public void applyTheme(Theme t) {
-            if (mThemeAttrs == null) {
-                return;
+            if (mThemeAttrs != null) {
+                return true;
             }
 
-            final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.VectorDrawableGroup);
-            updateStateFromTypedArray(a);
-            a.recycle();
+            final ArrayList<VObject> children = mChildren;
+            for (int i = 0, count = children.size(); i < count; i++) {
+                final VObject child = children.get(i);
+                if (child.canApplyTheme()) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        @Override
+        public void applyTheme(Theme t) {
+            if (mThemeAttrs != null) {
+                final TypedArray a = t.resolveAttributes(mThemeAttrs,
+                        R.styleable.VectorDrawableGroup);
+                updateStateFromTypedArray(a);
+                a.recycle();
+            }
+
+            final ArrayList<VObject> children = mChildren;
+            for (int i = 0, count = children.size(); i < count; i++) {
+                final VObject child = children.get(i);
+                if (child.canApplyTheme()) {
+                    child.applyTheme(t);
+                }
+            }
         }
 
         private void updateLocalMatrix() {
@@ -1407,7 +1249,7 @@
     /**
      * Common Path information for clip path and normal path.
      */
-    private static class VPath {
+    private static abstract class VPath implements VObject {
         protected PathParser.PathDataNode[] mNodes = null;
         String mPathName;
         int mChangingConfigurations;
@@ -1422,24 +1264,10 @@
             mNodes = PathParser.deepCopyNodes(copy.mNodes);
         }
 
-        public void toPath(Path path) {
-            path.reset();
-            if (mNodes != null) {
-                PathParser.PathDataNode.nodesToPath(mNodes, path);
-            }
-        }
-
         public String getPathName() {
             return mPathName;
         }
 
-        public boolean canApplyTheme() {
-            return false;
-        }
-
-        public void applyTheme(Theme t) {
-        }
-
         public boolean isClipPath() {
             return false;
         }
@@ -1459,6 +1287,79 @@
                 PathParser.updateNodes(mNodes, nodes);
             }
         }
+
+        @Override
+        public final void draw(Canvas canvas, TempState temp, Matrix groupStackedMatrix,
+                ColorFilter filter, float scaleX, float scaleY) {
+            final float matrixScale = VPath.getMatrixScale(groupStackedMatrix);
+            if (matrixScale == 0) {
+                // When either x or y is scaled to 0, we don't need to draw anything.
+                return;
+            }
+
+            final Path path = temp.path;
+            path.reset();
+            toPath(temp, path);
+
+            final Matrix pathMatrix = temp.pathMatrix;
+            pathMatrix.set(groupStackedMatrix);
+            pathMatrix.postScale(scaleX, scaleY);
+
+            final Path renderPath = temp.renderPath;
+            renderPath.reset();
+            renderPath.addPath(path, pathMatrix);
+
+            final float minScale = Math.min(scaleX, scaleY);
+            final float strokeScale = minScale * matrixScale;
+            drawPath(temp, renderPath, canvas, filter, strokeScale);
+        }
+
+        /**
+         * Writes the path's nodes to an output Path for rendering.
+         *
+         * @param temp temporary state variables
+         * @param outPath the output path
+         */
+        protected void toPath(TempState temp, Path outPath) {
+            if (mNodes != null) {
+                PathParser.PathDataNode.nodesToPath(mNodes, outPath);
+            }
+        }
+
+        /**
+         * Draws the specified path into the supplied canvas.
+         */
+        protected abstract void drawPath(TempState temp, Path path, Canvas canvas,
+                ColorFilter filter, float strokeScale);
+
+        private static float getMatrixScale(Matrix groupStackedMatrix) {
+            // Given unit vectors A = (0, 1) and B = (1, 0).
+            // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
+            // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
+            // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
+            // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
+            //
+            // For non-skew case, which is most of the cases, matrix scale is computing exactly the
+            // scale on x and y axis, and take the minimal of these two.
+            // For skew case, an unit square will mapped to a parallelogram. And this function will
+            // return the minimal height of the 2 bases.
+            float[] unitVectors = new float[] {0, 1, 1, 0};
+            groupStackedMatrix.mapVectors(unitVectors);
+            float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]);
+            float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]);
+            float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1],
+                    unitVectors[2], unitVectors[3]);
+            float maxScale = MathUtils.max(scaleX, scaleY);
+
+            float matrixScale = 0;
+            if (maxScale > 0) {
+                matrixScale = MathUtils.abs(crossProduct) / maxScale;
+            }
+            if (DBG_VECTOR_DRAWABLE) {
+                Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
+            }
+            return matrixScale;
+        }
     }
 
     /**
@@ -1473,6 +1374,13 @@
             super(copy);
         }
 
+        @Override
+        protected void drawPath(TempState temp, Path renderPath, Canvas canvas, ColorFilter filter,
+                float strokeScale) {
+            canvas.clipPath(renderPath);
+        }
+
+        @Override
         public void inflate(Resources r, AttributeSet attrs, Theme theme) {
             final TypedArray a = obtainAttributes(r, theme, attrs,
                     R.styleable.VectorDrawableClipPath);
@@ -1480,6 +1388,16 @@
             a.recycle();
         }
 
+        @Override
+        public boolean canApplyTheme() {
+            return false;
+        }
+
+        @Override
+        public void applyTheme(Theme theme) {
+            // No-op.
+        }
+
         private void updateStateFromTypedArray(TypedArray a) {
             // Account for any configuration changes.
             mChangingConfigurations |= a.getChangingConfigurations();
@@ -1574,10 +1492,104 @@
         }
 
         @Override
-        public boolean canApplyTheme() {
-            return mThemeAttrs != null;
+        public void toPath(TempState temp, Path path) {
+            super.toPath(temp, path);
+
+            if (mTrimPathStart != 0.0f || mTrimPathEnd != 1.0f) {
+                VFullPath.applyTrim(temp, path, mTrimPathStart, mTrimPathEnd, mTrimPathOffset);
+            }
         }
 
+        @Override
+        protected void drawPath(TempState temp, Path path, Canvas canvas, ColorFilter filter,
+                float strokeScale) {
+            drawPathFill(temp, path, canvas, filter);
+            drawPathStroke(temp, path, canvas, filter, strokeScale);
+        }
+
+        /**
+         * Draws this path's fill, if necessary.
+         */
+        private void drawPathFill(TempState temp, Path path, Canvas canvas, ColorFilter filter) {
+            if (mFillColor == Color.TRANSPARENT) {
+                return;
+            }
+
+            if (temp.mFillPaint == null) {
+                temp.mFillPaint = new Paint();
+                temp.mFillPaint.setStyle(Paint.Style.FILL);
+                temp.mFillPaint.setAntiAlias(true);
+            }
+
+            final Paint fillPaint = temp.mFillPaint;
+            fillPaint.setColor(applyAlpha(mFillColor, mFillAlpha));
+            fillPaint.setColorFilter(filter);
+            canvas.drawPath(path, fillPaint);
+        }
+
+        /**
+         * Draws this path's stroke, if necessary.
+         */
+        private void drawPathStroke(TempState temp, Path path, Canvas canvas, ColorFilter filter,
+                float strokeScale) {
+            if (mStrokeColor == Color.TRANSPARENT) {
+                return;
+            }
+
+            if (temp.mStrokePaint == null) {
+                temp.mStrokePaint = new Paint();
+                temp.mStrokePaint.setStyle(Paint.Style.STROKE);
+                temp.mStrokePaint.setAntiAlias(true);
+            }
+
+            final Paint strokePaint = temp.mStrokePaint;
+            if (mStrokeLineJoin != null) {
+                strokePaint.setStrokeJoin(mStrokeLineJoin);
+            }
+
+            if (mStrokeLineCap != null) {
+                strokePaint.setStrokeCap(mStrokeLineCap);
+            }
+
+            strokePaint.setStrokeMiter(mStrokeMiterlimit);
+            strokePaint.setColor(applyAlpha(mStrokeColor, mStrokeAlpha));
+            strokePaint.setColorFilter(filter);
+            strokePaint.setStrokeWidth(mStrokeWidth * strokeScale);
+            canvas.drawPath(path, strokePaint);
+        }
+
+        /**
+         * Applies trimming to the specified path.
+         */
+        private static void applyTrim(TempState temp, Path path, float mTrimPathStart,
+                float mTrimPathEnd, float mTrimPathOffset) {
+            if (mTrimPathStart == 0.0f && mTrimPathEnd == 1.0f) {
+                // No trimming necessary.
+                return;
+            }
+
+            if (temp.mPathMeasure == null) {
+                temp.mPathMeasure = new PathMeasure();
+            }
+            final PathMeasure pathMeasure = temp.mPathMeasure;
+            pathMeasure.setPath(path, false);
+
+            final float len = pathMeasure.getLength();
+            final float start = len * ((mTrimPathStart + mTrimPathOffset) % 1.0f);
+            final float end = len * ((mTrimPathEnd + mTrimPathOffset) % 1.0f);
+            path.reset();
+            if (start > end) {
+                pathMeasure.getSegment(start, len, path, true);
+                pathMeasure.getSegment(0, end, path, true);
+            } else {
+                pathMeasure.getSegment(start, end, path, true);
+            }
+
+            // Fix bug in measure.
+            path.rLineTo(0, 0);
+        }
+
+        @Override
         public void inflate(Resources r, AttributeSet attrs, Theme theme) {
             final TypedArray a = obtainAttributes(r, theme, attrs,
                     R.styleable.VectorDrawablePath);
@@ -1627,6 +1639,11 @@
         }
 
         @Override
+        public boolean canApplyTheme() {
+            return mThemeAttrs != null;
+        }
+
+        @Override
         public void applyTheme(Theme t) {
             if (mThemeAttrs == null) {
                 return;
@@ -1718,4 +1735,22 @@
             mTrimPathOffset = trimPathOffset;
         }
     }
+
+    static class TempState {
+        final Matrix pathMatrix = new Matrix();
+        final Path path = new Path();
+        final Path renderPath = new Path();
+
+        PathMeasure mPathMeasure;
+        Paint mFillPaint;
+        Paint mStrokePaint;
+    }
+
+    interface VObject {
+        void draw(Canvas canvas, TempState temp, Matrix currentMatrix,
+                ColorFilter filter, float scaleX, float scaleY);
+        void inflate(Resources r, AttributeSet attrs, Theme theme);
+        boolean canApplyTheme();
+        void applyTheme(Theme t);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index 95a8d39..6475cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -421,8 +421,9 @@
                         }
                     }
                 } else {
-                    final boolean vmute = row.ss.level == 0;
-                    mController.setStreamVolume(stream, vmute ? row.lastAudibleLevel : 0);
+                    final boolean vmute = row.ss.level == row.ss.levelMin;
+                    mController.setStreamVolume(stream,
+                            vmute ? row.lastAudibleLevel : row.ss.levelMin);
                 }
                 row.userAttempt = 0;  // reset the grace period, slider should update immediately
             }
@@ -1024,6 +1025,7 @@
                 final int minProgress = mRow.ss.levelMin * 100;
                 if (progress < minProgress) {
                     seekBar.setProgress(minProgress);
+                    progress = minProgress;
                 }
             }
             final int userLevel = getImpliedLevel(seekBar, progress);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0a12d5a..cbe61c3 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -46,7 +46,6 @@
 import android.service.notification.ZenModeConfig.EventInfo;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
 import android.service.notification.ZenModeConfig.ZenRule;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -149,6 +148,7 @@
             mAudioManager.setRingerModeDelegate(mRingerModeDelegate);
         }
         mHandler.postMetricsTimer();
+        evaluateZenMode("onSystemReady", true);
     }
 
     public void onUserSwitched(int user) {
@@ -330,13 +330,14 @@
         }
         mConditions.evaluateConfig(config, false /*processSubscriptions*/);  // may modify config
         mConfigs.put(config.user, config);
-        if (config.equals(mConfig)) return true;
         if (DEBUG) Log.d(TAG, "setConfig reason=" + reason, new Throwable());
         ZenLog.traceConfig(reason, mConfig, config);
         final boolean policyChanged = !Objects.equals(getNotificationPolicy(mConfig),
                 getNotificationPolicy(config));
         mConfig = config;
-        dispatchOnConfigChanged();
+        if (config.equals(mConfig)) {
+            dispatchOnConfigChanged();
+        }
         if (policyChanged){
             dispatchOnPolicyChanged();
         }
@@ -370,9 +371,7 @@
 
     private boolean evaluateZenMode(String reason, boolean setRingerMode) {
         if (DEBUG) Log.d(TAG, "evaluateZenMode");
-        final ArraySet<ZenRule> automaticRules = new ArraySet<ZenRule>();
-        final int zen = computeZenMode(automaticRules);
-        if (zen == mZenMode) return false;
+        final int zen = computeZenMode();
         ZenLog.traceSetZenMode(zen, reason);
         mZenMode = zen;
         updateRingerModeAffectedStreams();
@@ -381,7 +380,9 @@
             applyZenToRingerMode();
         }
         applyRestrictions();
-        mHandler.postDispatchOnZenModeChanged();
+        if (zen != mZenMode) {
+            mHandler.postDispatchOnZenModeChanged();
+        }
         return true;
     }
 
@@ -391,7 +392,7 @@
         }
     }
 
-    private int computeZenMode(ArraySet<ZenRule> automaticRulesOut) {
+    private int computeZenMode() {
         if (mConfig == null) return Global.ZEN_MODE_OFF;
         if (mConfig.manualRule != null) return mConfig.manualRule.zenMode;
         int zen = Global.ZEN_MODE_OFF;
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index d9df9f8..f892a68 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -14,11 +14,13 @@
   ~ limitations under the License
   -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="com.android.test.uibench">
+    xmlns:tools="http://schemas.android.com/tools"
+    package="com.android.test.uibench">
 
     <application
         android:allowBackup="false"
-        android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
+        android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
+        tools:ignore="MissingApplicationIcon">
         <uses-library android:name="android.test.runner" />
 
         <!-- Root navigation activity -->
@@ -34,6 +36,14 @@
 
         <!-- General -->
         <activity
+            android:name=".DialogListActivity"
+            android:label="General/Dialog List" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name=".GlTextureViewActivity"
             android:label="General/GL TextureView" >
             <intent-filter>
@@ -74,10 +84,28 @@
             </intent-filter>
         </activity>
 
-        <!-- GPU -->
+        <!-- Rendering -->
         <activity
             android:name=".BitmapUploadActivity"
-            android:label="GPU/Bitmap Upload" >
+            android:label="Rendering/Bitmap Upload" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".ShadowGridActivity"
+            android:label="Rendering/Shadow Grid" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="com.android.test.uibench.TEST" />
+            </intent-filter>
+        </activity>
+
+        <!-- Inflation -->
+        <activity
+            android:name=".InflatingListActivity"
+            android:label="Inflation/Inflating ListView" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="com.android.test.uibench.TEST" />
diff --git a/tests/UiBench/res/layout/card_row.xml b/tests/UiBench/res/layout/card_row.xml
new file mode 100644
index 0000000..215f9df
--- /dev/null
+++ b/tests/UiBench/res/layout/card_row.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2015 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
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="100dp"
+    android:paddingStart="10dp"
+    android:paddingEnd="10dp"
+    android:paddingTop="5dp"
+    android:paddingBottom="5dp"
+    android:clipToPadding="false"
+    android:background="@null">
+    <android.support.v7.widget.CardView
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1">
+        <TextView
+            android:id="@+id/card_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"/>
+    </android.support.v7.widget.CardView>
+
+    <android.support.v4.widget.Space
+        android:layout_height="match_parent"
+        android:layout_width="10dp" />
+
+    <android.support.v7.widget.CardView
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/UiBench/src/com/android/test/uibench/DialogListActivity.java b/tests/UiBench/src/com/android/test/uibench/DialogListActivity.java
new file mode 100644
index 0000000..7b579a1
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/DialogListActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 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.test.uibench;
+
+import android.os.Bundle;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+public class DialogListActivity extends AppCompatActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        ListView listView = new ListView(this);
+        listView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
+                TrivialListActivity.buildStringList()));
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        builder.setTitle("Dialog");
+        builder.setView(listView);
+        builder.create().show();
+    }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/InflatingListActivity.java b/tests/UiBench/src/com/android/test/uibench/InflatingListActivity.java
new file mode 100644
index 0000000..798c226
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/InflatingListActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 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.test.uibench;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListAdapter;
+
+public class InflatingListActivity extends AppCompatActivity {
+    private ListAdapter createListAdapter() {
+        return new ArrayAdapter<String>(this,
+                android.R.layout.simple_list_item_1, TrivialListActivity.buildStringList()) {
+            @Override
+            public View getView(int position, View convertView, ViewGroup parent) {
+                // pathological getView behavior: drop convertView on the floor to force inflation
+                return super.getView(position, null, parent);
+            }
+        };
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FragmentManager fm = getSupportFragmentManager();
+        if (fm.findFragmentById(android.R.id.content) == null) {
+            ListFragment listFragment = new ListFragment();
+            listFragment.setListAdapter(createListAdapter());
+            fm.beginTransaction().add(android.R.id.content, listFragment).commit();
+        }
+    }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/ShadowGridActivity.java b/tests/UiBench/src/com/android/test/uibench/ShadowGridActivity.java
new file mode 100644
index 0000000..e39ec03
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/ShadowGridActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 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.test.uibench;
+
+import android.os.Bundle;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.AppCompatActivity;
+import android.view.View;
+import android.widget.ArrayAdapter;
+
+public class ShadowGridActivity extends AppCompatActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        FragmentManager fm = getSupportFragmentManager();
+        if (fm.findFragmentById(android.R.id.content) == null) {
+            ListFragment listFragment = new ListFragment() {
+                @Override
+                public void onViewCreated(View view, Bundle savedInstanceState) {
+                    super.onViewCreated(view, savedInstanceState);
+                    getListView().setDivider(null);
+                }
+            };
+
+            listFragment.setListAdapter(new ArrayAdapter<>(this,
+                    R.layout.card_row, R.id.card_text, TrivialListActivity.buildStringList()));
+            fm.beginTransaction().add(android.R.id.content, listFragment).commit();
+        }
+    }
+}
diff --git a/tests/UiBench/src/com/android/test/uibench/TrivialListActivity.java b/tests/UiBench/src/com/android/test/uibench/TrivialListActivity.java
index 0af3471..9c8ae9b 100644
--- a/tests/UiBench/src/com/android/test/uibench/TrivialListActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/TrivialListActivity.java
@@ -47,7 +47,7 @@
         FragmentManager fm = getSupportFragmentManager();
         if (fm.findFragmentById(android.R.id.content) == null) {
             ListFragment listFragment = new ListFragment();
-            listFragment.setListAdapter(new ArrayAdapter<>(TrivialListActivity.this,
+            listFragment.setListAdapter(new ArrayAdapter<>(this,
                     android.R.layout.simple_list_item_1, buildStringList()));
             fm.beginTransaction().add(android.R.id.content, listFragment).commit();
         }
diff --git a/tools/aidl/Android.mk b/tools/aidl/Android.mk
index b478a4d..354563a 100644
--- a/tools/aidl/Android.mk
+++ b/tools/aidl/Android.mk
@@ -56,13 +56,14 @@
 LOCAL_CFLAGS := -g -DUNIT_TEST -Wall -Werror
 LOCAL_SRC_FILES := \
     options_unittest.cpp \
+    test_main.cpp \
     tests/test.cpp \
 
 LOCAL_STATIC_LIBRARIES := \
     libaidl-common \
     libgmock_host \
     libgtest_host \
-    libBionicGtestMain
+
 LOCAL_LDLIBS := -lrt
 include $(BUILD_HOST_NATIVE_TEST)
 
diff --git a/tools/aidl/test_main.cpp b/tools/aidl/test_main.cpp
new file mode 100644
index 0000000..4d820af7
--- /dev/null
+++ b/tools/aidl/test_main.cpp
@@ -0,0 +1,6 @@
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}