Merge "Use themed context to inflate preference views" into mnc-ub-dev
diff --git a/design/api/current.txt b/design/api/current.txt
index 6c8592e..a5feb3d 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -12,7 +12,7 @@
     method public void setTargetElevation(float);
   }
 
-  public static class AppBarLayout.Behavior extends android.support.design.widget.ViewOffsetBehavior {
+  public static class AppBarLayout.Behavior extends android.support.design.widget.HeaderBehavior {
     ctor public AppBarLayout.Behavior();
     ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
     method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.MotionEvent);
@@ -56,14 +56,14 @@
     method public abstract void onOffsetChanged(android.support.design.widget.AppBarLayout, int);
   }
 
-  public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
+  public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
     ctor public AppBarLayout.ScrollingViewBehavior();
     ctor public AppBarLayout.ScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
+    method protected android.view.View findFirstDependency(java.util.List<android.view.View>);
     method public int getOverlayTop();
     method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
     method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.view.View, int);
-    method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
     method public void setOverlayTop(int);
   }
 
@@ -203,6 +203,42 @@
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, int);
   }
 
+   abstract class HeaderBehavior extends android.support.design.widget.ViewOffsetBehavior {
+    ctor public HeaderBehavior();
+    ctor public HeaderBehavior(android.content.Context, android.util.AttributeSet);
+    method protected boolean fling(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, float);
+    method protected int getTopBottomOffsetForScrollingSibling();
+    method protected int scroll(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int);
+    method protected int setHeaderTopBottomOffset(android.support.design.widget.CoordinatorLayout, android.view.View, int);
+    method protected int setHeaderTopBottomOffset(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int);
+  }
+
+   abstract class HeaderScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
+    ctor public HeaderScrollingViewBehavior();
+    ctor public HeaderScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
+    method protected abstract android.view.View findFirstDependency(java.util.List<android.view.View>);
+    method protected int getScrollRange(android.view.View);
+    method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
+  }
+
+  public final class NavigationHeaderBehavior extends android.support.design.widget.HeaderBehavior {
+    ctor public NavigationHeaderBehavior();
+    ctor public NavigationHeaderBehavior(android.content.Context, android.util.AttributeSet);
+    method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+    method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, float, float, boolean);
+    method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, int, int, int[]);
+    method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, int, int, int, int);
+    method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View, android.view.View, int);
+  }
+
+  public static class NavigationHeaderBehavior.MenuViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
+    ctor public NavigationHeaderBehavior.MenuViewBehavior();
+    ctor public NavigationHeaderBehavior.MenuViewBehavior(android.content.Context, android.util.AttributeSet);
+    method protected android.view.View findFirstDependency(java.util.List<android.view.View>);
+    method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+    method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+  }
+
   public class NavigationView extends android.widget.FrameLayout {
     ctor public NavigationView(android.content.Context);
     ctor public NavigationView(android.content.Context, android.util.AttributeSet);
diff --git a/design/res/layout/design_navigation_item_header.xml b/design/res/layout/design_navigation_item_header.xml
index 33fd199..8d03f69 100644
--- a/design/res/layout/design_navigation_item_header.xml
+++ b/design/res/layout/design_navigation_item_header.xml
@@ -15,6 +15,7 @@
   ~ limitations under the License.
 -->
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+      android:id="@+id/navigation_header_container"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical"
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index f238bcb..8184d16 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -23,7 +23,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcelable;
-import android.support.annotation.LayoutRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.StyleRes;
@@ -40,7 +39,6 @@
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -54,7 +52,6 @@
     private static final String STATE_ADAPTER = "android:menu:adapter";
 
     private NavigationMenuView mMenuView;
-    private LinearLayout mHeaderLayout;
 
     private Callback mCallback;
     private MenuBuilder mMenu;
@@ -99,9 +96,6 @@
             if (mAdapter == null) {
                 mAdapter = new NavigationMenuAdapter();
             }
-            mHeaderLayout = (LinearLayout) mLayoutInflater
-                    .inflate(R.layout.design_navigation_item_header,
-                            mMenuView, false);
             mMenuView.setAdapter(mAdapter);
         }
         return mMenuView;
@@ -186,21 +180,14 @@
         mAdapter.setCheckedItem(item);
     }
 
-    public View inflateHeaderView(@LayoutRes int res) {
-        View view = mLayoutInflater.inflate(res, mHeaderLayout, false);
-        addHeaderView(view);
-        return view;
-    }
-
     public void addHeaderView(@NonNull View view) {
-        mHeaderLayout.addView(view);
-        // The padding on top should be cleared.
+        // The padding on top should be cleared when there's a header.
         mMenuView.setPadding(0, 0, 0, mMenuView.getPaddingBottom());
     }
 
-    public void removeHeaderView(@NonNull View view) {
-        mHeaderLayout.removeView(view);
-        if (mHeaderLayout.getChildCount() == 0) {
+    public void removeHeaderView(@NonNull View view, boolean hasHeaders) {
+        if (!hasHeaders) {
+            // Padding should be added when there are no headers.
             mMenuView.setPadding(0, mPaddingTopDefault, 0, mMenuView.getPaddingBottom());
         }
     }
@@ -279,14 +266,6 @@
 
     }
 
-    private static class HeaderViewHolder extends ViewHolder {
-
-        public HeaderViewHolder(View itemView) {
-            super(itemView);
-        }
-
-    }
-
     /**
      * Handles click events for the menu items. The items has to be {@link NavigationMenuItemView}.
      */
@@ -315,7 +294,6 @@
         private static final int VIEW_TYPE_NORMAL = 0;
         private static final int VIEW_TYPE_SUBHEADER = 1;
         private static final int VIEW_TYPE_SEPARATOR = 2;
-        private static final int VIEW_TYPE_HEADER = 3;
 
         private final ArrayList<NavigationMenuItem> mItems = new ArrayList<>();
         private MenuItemImpl mCheckedItem;
@@ -341,8 +319,6 @@
             NavigationMenuItem item = mItems.get(position);
             if (item instanceof NavigationMenuSeparatorItem) {
                 return VIEW_TYPE_SEPARATOR;
-            } else if (item instanceof NavigationMenuHeaderItem) {
-                return VIEW_TYPE_HEADER;
             } else if (item instanceof NavigationMenuTextItem) {
                 NavigationMenuTextItem textItem = (NavigationMenuTextItem) item;
                 if (textItem.getMenuItem().hasSubMenu()) {
@@ -363,8 +339,6 @@
                     return new SubheaderViewHolder(mLayoutInflater, parent);
                 case VIEW_TYPE_SEPARATOR:
                     return new SeparatorViewHolder(mLayoutInflater, parent);
-                case VIEW_TYPE_HEADER:
-                    return new HeaderViewHolder(mHeaderLayout);
             }
             return null;
         }
@@ -400,9 +374,6 @@
                             item.getPaddingBottom());
                     break;
                 }
-                case VIEW_TYPE_HEADER: {
-                    break;
-                }
             }
 
         }
@@ -429,7 +400,6 @@
             }
             mUpdateSuspended = true;
             mItems.clear();
-            mItems.add(new NavigationMenuHeaderItem());
 
             int currentGroupId = -1;
             int currentGroupStart = 0;
@@ -622,12 +592,4 @@
         }
 
     }
-
-    /**
-     * Header (not subheader) items.
-     */
-    private static class NavigationMenuHeaderItem implements NavigationMenuItem {
-        // The actual content is hold by NavigationMenuPresenter#mHeaderLayout.
-    }
-
 }
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index acfd28b..23cd9702 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -25,7 +25,6 @@
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowInsetsCompat;
-import android.support.v4.widget.ScrollerCompat;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
@@ -645,7 +644,7 @@
      * The default {@link Behavior} for {@link AppBarLayout}. Implements the necessary nested
      * scroll handling with offsetting.
      */
-    public static class Behavior extends ViewOffsetBehavior<AppBarLayout> {
+    public static class Behavior extends HeaderBehavior<AppBarLayout> {
         private static final int INVALID_POINTER = -1;
         private static final int INVALID_POSITION = -1;
 
@@ -653,8 +652,6 @@
 
         private boolean mSkipNestedPreScroll;
         private boolean mWasFlung;
-        private Runnable mFlingRunnable;
-        private ScrollerCompat mScroller;
 
         private ValueAnimatorCompat mAnimator;
 
@@ -901,7 +898,7 @@
                 mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
                     @Override
                     public void onAnimationUpdate(ValueAnimatorCompat animator) {
-                        setAppBarTopBottomOffset(coordinatorLayout, child,
+                        setHeaderTopBottomOffset(coordinatorLayout, child,
                                 animator.getAnimatedIntValue());
                     }
                 });
@@ -913,31 +910,6 @@
             mAnimator.start();
         }
 
-        private boolean fling(CoordinatorLayout coordinatorLayout, AppBarLayout layout, int minOffset,
-                int maxOffset, float velocityY) {
-            if (mFlingRunnable != null) {
-                layout.removeCallbacks(mFlingRunnable);
-            }
-
-            if (mScroller == null) {
-                mScroller = ScrollerCompat.create(layout.getContext());
-            }
-
-            mScroller.fling(
-                    0, getTopBottomOffsetForScrollingSibling(), // curr
-                    0, Math.round(velocityY), // velocity.
-                    0, 0, // x
-                    minOffset, maxOffset); // y
-
-            if (mScroller.computeScrollOffset()) {
-                mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
-                ViewCompat.postOnAnimation(layout, mFlingRunnable);
-                return true;
-            } else {
-                mFlingRunnable = null;
-                return false;
-            }
-        }
 
         private View getChildOnOffset(AppBarLayout abl, final int offset) {
             for (int i = 0, count = abl.getChildCount(); i < count; i++) {
@@ -964,26 +936,6 @@
             }
         }
 
-        private class FlingRunnable implements Runnable {
-            private final CoordinatorLayout mParent;
-            private final AppBarLayout mLayout;
-
-            FlingRunnable(CoordinatorLayout parent, AppBarLayout layout) {
-                mParent = parent;
-                mLayout = layout;
-            }
-
-            @Override
-            public void run() {
-                if (mLayout != null && mScroller != null && mScroller.computeScrollOffset()) {
-                    setAppBarTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
-
-                    // Post ourselves so that we run on the next animation
-                    ViewCompat.postOnAnimation(mLayout, this);
-                }
-            }
-        }
-
         @Override
         public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl,
                 int layoutDirection) {
@@ -997,13 +949,13 @@
                     if (animate) {
                         animateOffsetTo(parent, abl, offset);
                     } else {
-                        setAppBarTopBottomOffset(parent, abl, offset);
+                        setHeaderTopBottomOffset(parent, abl, offset);
                     }
                 } else if ((pendingAction & PENDING_ACTION_EXPANDED) != 0) {
                     if (animate) {
                         animateOffsetTo(parent, abl, 0);
                     } else {
-                        setAppBarTopBottomOffset(parent, abl, 0);
+                        setHeaderTopBottomOffset(parent, abl, 0);
                     }
                 }
             } else if (mOffsetToChildIndexOnLayout >= 0) {
@@ -1027,12 +979,6 @@
             return handled;
         }
 
-        private int scroll(CoordinatorLayout coordinatorLayout, AppBarLayout appBarLayout,
-                int dy, int minOffset, int maxOffset) {
-            return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout,
-                    getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset);
-        }
-
         private boolean canDragAppBarLayout() {
             if (mLastNestedScrollingChildRef != null) {
                 final View view = mLastNestedScrollingChildRef.get();
@@ -1041,22 +987,18 @@
             return false;
         }
 
-        final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
-                AppBarLayout appBarLayout, int newOffset) {
-            return setAppBarTopBottomOffset(coordinatorLayout, appBarLayout, newOffset,
-                    Integer.MIN_VALUE, Integer.MAX_VALUE);
-        }
-
-        final int setAppBarTopBottomOffset(CoordinatorLayout coordinatorLayout,
-                AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
+        @Override
+        protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
+                View header, int newOffset, int minOffset, int maxOffset) {
             final int curOffset = getTopBottomOffsetForScrollingSibling();
             int consumed = 0;
 
-            if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
+            if ((header instanceof AppBarLayout) && minOffset != 0 && curOffset >= minOffset
+                    && curOffset <= maxOffset) {
                 // If we have some scrolling range, and we're currently within the min and max
                 // offsets, calculate a new offset
                 newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
-
+                AppBarLayout appBarLayout = (AppBarLayout) header;
                 if (curOffset != newOffset) {
                     final int interpolatedOffset = appBarLayout.hasChildWithInterpolator()
                             ? interpolateOffset(appBarLayout, newOffset)
@@ -1145,7 +1087,8 @@
             return offset;
         }
 
-        final int getTopBottomOffsetForScrollingSibling() {
+        @Override
+        protected int getTopBottomOffsetForScrollingSibling() {
             return getTopAndBottomOffset() + mOffsetDelta;
         }
 
@@ -1231,7 +1174,7 @@
      * Behavior which should be used by {@link View}s which can scroll vertically and support
      * nested scrolling to automatically scroll any {@link AppBarLayout} siblings.
      */
-    public static class ScrollingViewBehavior extends ViewOffsetBehavior<View> {
+    public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
         private int mOverlayTop;
 
         public ScrollingViewBehavior() {}
@@ -1253,65 +1196,6 @@
         }
 
         @Override
-        public boolean onMeasureChild(CoordinatorLayout parent, final View child,
-                int parentWidthMeasureSpec, int widthUsed,
-                int parentHeightMeasureSpec, int heightUsed) {
-            final int childLpHeight = child.getLayoutParams().height;
-            if (childLpHeight == LayoutParams.MATCH_PARENT
-                    || childLpHeight == LayoutParams.WRAP_CONTENT) {
-                // If the child's height is set to match_parent/wrap_content then measure it
-                // with the maximum visible height
-
-                final List<View> dependencies = parent.getDependencies(child);
-                if (dependencies.isEmpty()) {
-                    // If we don't have any dependencies, return false
-                    return false;
-                }
-
-                final AppBarLayout appBar = findFirstAppBarLayout(dependencies);
-                if (appBar != null && appBar.getVisibility() == View.VISIBLE) {
-                    if (appBar.getMeasuredHeight() <= 0 && appBar.getMeasuredWidth() <= 0) {
-                        // The AppBar hasn't been measured yet, so we'll post a Runnable to delay
-                        // our measure until after the ABLs has happened
-                        child.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                child.requestLayout();
-                            }
-                        });
-                        return false;
-                    }
-
-                    if (ViewCompat.getFitsSystemWindows(appBar)) {
-                        // If the AppBarLayout is fitting system windows then we need to also,
-                        // otherwise we'll get CoL's compatible layout functionality
-                        ViewCompat.setFitsSystemWindows(child, true);
-                    }
-
-                    int availableHeight = MeasureSpec.getSize(parentHeightMeasureSpec);
-                    if (availableHeight == 0) {
-                        // If the measure spec doesn't specify a size, use the current height
-                        availableHeight = parent.getHeight();
-                    }
-
-                    final int height = availableHeight - appBar.getMeasuredHeight()
-                            + appBar.getTotalScrollRange();
-                    final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
-                            childLpHeight == LayoutParams.MATCH_PARENT
-                                    ? MeasureSpec.EXACTLY
-                                    : MeasureSpec.AT_MOST);
-
-                    // Now measure the scrolling child with the correct height
-                    parent.onMeasureChild(child, parentWidthMeasureSpec,
-                            widthUsed, heightMeasureSpec, heightUsed);
-
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        @Override
         public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
             // First lay out the child as normal
             super.onLayoutChild(parent, child, layoutDirection);
@@ -1386,7 +1270,8 @@
             return mOverlayTop;
         }
 
-        private static AppBarLayout findFirstAppBarLayout(List<View> views) {
+        @Override
+        protected View findFirstDependency(List<View> views) {
             for (int i = 0, z = views.size(); i < z; i++) {
                 View view = views.get(i);
                 if (view instanceof AppBarLayout) {
@@ -1395,5 +1280,14 @@
             }
             return null;
         }
+
+        @Override
+        protected int getScrollRange(View v) {
+            if (v instanceof AppBarLayout) {
+                return ((AppBarLayout) v).getTotalScrollRange();
+            } else {
+                return super.getScrollRange(v);
+            }
+        }
     }
 }
diff --git a/design/src/android/support/design/widget/HeaderBehavior.java b/design/src/android/support/design/widget/HeaderBehavior.java
new file mode 100644
index 0000000..ae61b62
--- /dev/null
+++ b/design/src/android/support/design/widget/HeaderBehavior.java
@@ -0,0 +1,124 @@
+/*
+ * 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.support.design.widget;
+
+import android.content.Context;
+import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ScrollerCompat;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * The {@link Behavior} for a view that sits vertically above scrolling a view.
+ * See {@link HeaderScrollingViewBehavior}.
+ */
+abstract class HeaderBehavior<V extends View> extends ViewOffsetBehavior<V> {
+
+    private Runnable mFlingRunnable;
+    private ScrollerCompat mScroller;
+
+    public HeaderBehavior() {
+        super();
+    }
+
+    public HeaderBehavior(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
+            View header, int newOffset) {
+        return setHeaderTopBottomOffset(coordinatorLayout, header, newOffset,
+                Integer.MIN_VALUE, Integer.MAX_VALUE);
+    }
+
+    protected int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
+            View header, int newOffset, int minOffset, int maxOffset) {
+        final int curOffset = getTopAndBottomOffset();
+        int consumed = 0;
+
+        if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
+            // If we have some scrolling range, and we're currently within the min and max
+            // offsets, calculate a new offset
+            newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
+
+            if (curOffset != newOffset) {
+                setTopAndBottomOffset(newOffset);
+                // Update how much dy we have consumed
+                consumed = curOffset - newOffset;
+            }
+        }
+
+        return consumed;
+    }
+
+    protected int getTopBottomOffsetForScrollingSibling() {
+        return getTopAndBottomOffset();
+    }
+
+    protected int scroll(CoordinatorLayout coordinatorLayout, View header,
+            int dy, int minOffset, int maxOffset) {
+        return setHeaderTopBottomOffset(coordinatorLayout, header,
+                getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset);
+    }
+
+    protected boolean fling(CoordinatorLayout coordinatorLayout, View layout, int minOffset,
+            int maxOffset, float velocityY) {
+        if (mFlingRunnable != null) {
+            layout.removeCallbacks(mFlingRunnable);
+        }
+
+        if (mScroller == null) {
+            mScroller = ScrollerCompat.create(layout.getContext());
+        }
+
+        mScroller.fling(
+                0, getTopAndBottomOffset(), // curr
+                0, Math.round(velocityY), // velocity.
+                0, 0, // x
+                minOffset, maxOffset); // y
+
+        if (mScroller.computeScrollOffset()) {
+            mFlingRunnable = new FlingRunnable(coordinatorLayout, layout);
+            ViewCompat.postOnAnimation(layout, mFlingRunnable);
+            return true;
+        } else {
+            mFlingRunnable = null;
+            return false;
+        }
+    }
+
+    private class FlingRunnable implements Runnable {
+        private final CoordinatorLayout mParent;
+        private final View mLayout;
+
+        FlingRunnable(CoordinatorLayout parent, View layout) {
+            mParent = parent;
+            mLayout = layout;
+        }
+
+        @Override
+        public void run() {
+            if (mLayout != null && mScroller != null && mScroller.computeScrollOffset()) {
+                setHeaderTopBottomOffset(mParent, mLayout, mScroller.getCurrY());
+
+                // Post ourselves so that we run on the next animation
+                ViewCompat.postOnAnimation(mLayout, this);
+            }
+        }
+    }
+}
diff --git a/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java b/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
new file mode 100644
index 0000000..3b43a91
--- /dev/null
+++ b/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
@@ -0,0 +1,94 @@
+/*
+ * 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.support.design.widget;
+
+import android.content.Context;
+import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.List;
+
+/**
+ * The {@link Behavior} for a scrolling view that is positioned vertically below another view.
+ * See {@link HeaderBehavior}.
+ */
+abstract class HeaderScrollingViewBehavior extends ViewOffsetBehavior<View> {
+
+    public HeaderScrollingViewBehavior() {
+        super();
+    }
+
+    public HeaderScrollingViewBehavior(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    protected abstract View findFirstDependency(List<View> views);
+
+    @Override
+    public boolean onMeasureChild(CoordinatorLayout parent, View child,
+            int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
+            int heightUsed) {
+        final int childLpHeight = child.getLayoutParams().height;
+        if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
+                || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
+            // If the menu's height is set to match_parent/wrap_content then measure it
+            // with the maximum visible height
+
+            final List<View> dependencies = parent.getDependencies(child);
+            if (dependencies.isEmpty()) {
+                // If we don't have any dependencies, return false
+                return false;
+            }
+
+            final View header = findFirstDependency(dependencies);
+            if (header != null && ViewCompat.isLaidOut(header)) {
+                if (ViewCompat.getFitsSystemWindows(header)) {
+                    // If the header is fitting system windows then we need to also,
+                    // otherwise we'll get CoL's compatible layout functionality
+                    ViewCompat.setFitsSystemWindows(child, true);
+                }
+
+                int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
+                if (availableHeight == 0) {
+                    // If the measure spec doesn't specify a size, use the current height
+                    availableHeight = parent.getHeight();
+                }
+
+                final int height = availableHeight - header.getMeasuredHeight()
+                        + getScrollRange(header);
+                final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
+                        childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT
+                                ? View.MeasureSpec.EXACTLY
+                                : View.MeasureSpec.AT_MOST);
+
+                // Now measure the scrolling menu with the correct height
+                parent.onMeasureChild(child, parentWidthMeasureSpec,
+                        widthUsed, heightMeasureSpec, heightUsed);
+
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected int getScrollRange(View v) {
+        return v.getMeasuredHeight();
+    }
+}
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/NavigationHeaderBehavior.java b/design/src/android/support/design/widget/NavigationHeaderBehavior.java
new file mode 100644
index 0000000..4b38146
--- /dev/null
+++ b/design/src/android/support/design/widget/NavigationHeaderBehavior.java
@@ -0,0 +1,132 @@
+/*
+ * 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.support.design.widget;
+
+import android.content.Context;
+import android.support.design.R;
+import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * The {@link Behavior} for the {@link NavigationView} header. This handles scrolling the
+ * header view with the list that displays the menu in {@link NavigationView}.
+ */
+public final class NavigationHeaderBehavior extends HeaderBehavior<View> {
+
+    public NavigationHeaderBehavior() {
+        super();
+    }
+
+    public NavigationHeaderBehavior(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public boolean layoutDependsOn(CoordinatorLayout parent, View header, View dependency) {
+        return dependency instanceof RecyclerView;
+    }
+
+    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View header,
+            View directTargetChild, View target, int nestedScrollAxes) {
+        // Return true if we're nested scrolling vertically, and the scrolling view is big enough
+        // to scroll.
+        boolean canTargetScroll = ViewCompat.canScrollVertically(target, -1)
+                || ViewCompat.canScrollVertically(target,1);
+        final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
+                && coordinatorLayout.getHeight() - target.getHeight() <= header.getHeight()
+                && canTargetScroll;
+        return started;
+    }
+
+    @Override
+    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View header, View target,
+            int dx, int dy, int[] consumed) {
+        if (dy > 0) {
+            // We're scrolling up
+            consumed[1] = scroll(coordinatorLayout, header, dy, -header.getMeasuredHeight(), 0);
+        }
+    }
+
+    @Override
+    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View header, View target,
+            int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
+        if (dyUnconsumed < 0) {
+            // If the scrolling view is scrolling down but not consuming, it's probably at
+            // the top of it's content, scroll the header down.
+            scroll(coordinatorLayout, header, dyUnconsumed, -header.getMeasuredHeight(), 0);
+        }
+    }
+
+    @Override
+    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View header, View target,
+            float velocityX, float velocityY, boolean consumed) {
+        if (!consumed) {
+            // It hasn't been consumed so let's fling ourselves
+            return fling(coordinatorLayout, header, -header.getMeasuredHeight(), 0, -velocityY);
+        }
+        return false;
+    }
+
+    /**
+     * Defines the behavior for the scrolling view used to back {@link NavigationView}.
+     */
+    public static class MenuViewBehavior extends HeaderScrollingViewBehavior {
+
+        public MenuViewBehavior() {
+        }
+
+        public MenuViewBehavior(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public boolean layoutDependsOn(CoordinatorLayout parent, View menu, View dependency) {
+            // We depend on the header container
+            return (dependency.getId() == R.id.navigation_header_container);
+        }
+
+        @Override
+        public boolean onDependentViewChanged(CoordinatorLayout parent, View menu,
+                View dependency) {
+            final CoordinatorLayout.Behavior behavior =
+                    ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
+            if (behavior instanceof NavigationHeaderBehavior) {
+                // Offset the menu so that it is below the header.
+                final int headerOffset = ((NavigationHeaderBehavior) behavior)
+                        .getTopAndBottomOffset();
+                setTopAndBottomOffset(dependency.getHeight() + headerOffset);
+            }
+            return false;
+        }
+
+        @Override
+        protected View findFirstDependency(List<View> views) {
+            for (int i = 0, z = views.size(); i < z; i++) {
+                View view = views.get(i);
+                if (view.getId() == R.id.navigation_header_container) {
+                    return view;
+                }
+            }
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/NavigationView.java b/design/src/android/support/design/widget/NavigationView.java
index 45fd088..6f606d4 100644
--- a/design/src/android/support/design/widget/NavigationView.java
+++ b/design/src/android/support/design/widget/NavigationView.java
@@ -32,6 +32,7 @@
 import android.support.design.R;
 import android.support.design.internal.NavigationMenu;
 import android.support.design.internal.NavigationMenuPresenter;
+import android.support.design.internal.NavigationMenuView;
 import android.support.design.internal.ScrimInsetsFrameLayout;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ViewCompat;
@@ -40,10 +41,12 @@
 import android.support.v7.internal.view.menu.MenuItemImpl;
 import android.util.AttributeSet;
 import android.util.TypedValue;
+import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.widget.LinearLayout;
 
 /**
  * Represents a standard navigation menu for application. The menu contents can be populated
@@ -79,10 +82,14 @@
     private final NavigationMenu mMenu;
     private final NavigationMenuPresenter mPresenter = new NavigationMenuPresenter();
 
+    private final CoordinatorLayout mCoordinatorLayout;
+    private final LinearLayout mHeaderContainer;
+
     private OnNavigationItemSelectedListener mListener;
     private int mMaxWidth;
 
     private MenuInflater mMenuInflater;
+    private LayoutInflater mLayoutInflater;
 
     public NavigationView(Context context) {
         this(context, null);
@@ -160,7 +167,32 @@
         mPresenter.setItemTextColor(itemTextColor);
         mPresenter.setItemBackground(itemBackground);
         mMenu.addMenuPresenter(mPresenter);
-        addView((View) mPresenter.getMenuView(this));
+
+        // Create the coordinator layout and add the header and menu view.
+        mCoordinatorLayout = new CoordinatorLayout(context);
+        mLayoutInflater = LayoutInflater.from(context);
+
+        mHeaderContainer = (LinearLayout) mLayoutInflater.inflate(R.layout.design_navigation_item_header,
+                this, false);
+        mCoordinatorLayout.addView(mHeaderContainer);
+
+        NavigationMenuView menuView = (NavigationMenuView) mPresenter.getMenuView(this);
+        mCoordinatorLayout.addView(menuView);
+
+        // Set the header behavior
+        CoordinatorLayout.LayoutParams headerParams =
+                (CoordinatorLayout.LayoutParams) mHeaderContainer.getLayoutParams();
+        headerParams.setBehavior(new NavigationHeaderBehavior());
+        mHeaderContainer.setLayoutParams(headerParams);
+
+        // Set the menu behavior
+        CoordinatorLayout.LayoutParams menuParams =
+                (CoordinatorLayout.LayoutParams) menuView.getLayoutParams();
+        menuParams.setBehavior(new NavigationHeaderBehavior.MenuViewBehavior());
+        menuView.setLayoutParams(menuParams);
+
+        // Add coordinator to hierarchy
+        addView(mCoordinatorLayout);
 
         if (a.hasValue(R.styleable.NavigationView_menu)) {
             inflateMenu(a.getResourceId(R.styleable.NavigationView_menu, 0));
@@ -245,7 +277,9 @@
      * @return a newly inflated View.
      */
     public View inflateHeaderView(@LayoutRes int res) {
-        return mPresenter.inflateHeaderView(res);
+        View view = mLayoutInflater.inflate(res, mHeaderContainer, false);
+        addHeaderView(view);
+        return view;
     }
 
     /**
@@ -255,6 +289,7 @@
      */
     public void addHeaderView(@NonNull View view) {
         mPresenter.addHeaderView(view);
+        mHeaderContainer.addView(view);
     }
 
     /**
@@ -263,7 +298,8 @@
      * @param view The view to remove
      */
     public void removeHeaderView(@NonNull View view) {
-        mPresenter.removeHeaderView(view);
+        mPresenter.removeHeaderView(view, (mHeaderContainer.getChildCount() > 0) /* hasHeaders */);
+        mHeaderContainer.removeView(view);
     }
 
     /**
diff --git a/v4/api/current.txt b/v4/api/current.txt
index b0be7a0..669b4a4 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -1071,6 +1071,11 @@
     method public void unregisterReceiver(android.content.BroadcastReceiver);
   }
 
+  public class ParallelExecutorCompat {
+    ctor public ParallelExecutorCompat();
+    method public static java.util.concurrent.Executor getParallelExecutor();
+  }
+
   public final class PermissionChecker {
     method public static int checkCallingOrSelfPermission(android.content.Context, java.lang.String);
     method public static int checkCallingPermission(android.content.Context, java.lang.String, java.lang.String);
@@ -1093,11 +1098,6 @@
     method public static android.support.v4.content.SharedPreferencesCompat.EditorCompat getInstance();
   }
 
-  public class ParallelExecutorCompat {
-    ctor public ParallelExecutorCompat();
-    method public static java.util.concurrent.Executor getParallelExecutor();
-  }
-
   public abstract class WakefulBroadcastReceiver extends android.content.BroadcastReceiver {
     ctor public WakefulBroadcastReceiver();
     method public static boolean completeWakefulIntent(android.content.Intent);
diff --git a/v7/mediarouter/jellybean/android/support/v7/media/SeekBarJellybean.java b/v7/mediarouter/jellybean/android/support/v7/media/SeekBarJellybean.java
deleted file mode 100644
index 76bf6fd..0000000
--- a/v7/mediarouter/jellybean/android/support/v7/media/SeekBarJellybean.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.support.v7.media;
-
-import android.graphics.drawable.Drawable;
-import android.widget.SeekBar;
-
-/**
- * @hide
- */
-public class SeekBarJellybean {
-    public static Drawable getThumb(SeekBar seekBar) {
-        return seekBar.getThumb();
-    }
-}
diff --git a/v7/mediarouter/res/drawable/mr_seekbar_thumb.xml b/v7/mediarouter/res/drawable/mr_seekbar_thumb.xml
new file mode 100644
index 0000000..dc1a194
--- /dev/null
+++ b/v7/mediarouter/res/drawable/mr_seekbar_thumb.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:constantSize="true">
+    <!-- Place fake item to keep the ripple animation size which is determined by thumb's size. -->
+    <item android:state_enabled="false" android:state_pressed="true">
+        <bitmap android:src="@drawable/abc_scrubber_control_off_mtrl_alpha"
+            android:gravity="center"/>
+    </item>
+    <item android:state_pressed="true">
+        <bitmap android:src="@drawable/abc_scrubber_control_to_pressed_mtrl_005"
+            android:gravity="center" />
+    </item>
+    <item>
+        <bitmap android:src="@drawable/abc_scrubber_control_to_pressed_mtrl_000"
+            android:gravity="center" />
+    </item>
+</selector>
\ No newline at end of file
diff --git a/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml b/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
index d58a451..0a07a92 100644
--- a/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
+++ b/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
@@ -80,6 +80,9 @@
             <ListView android:id="@+id/mr_volume_group_list"
                       android:layout_width="fill_parent"
                       android:layout_height="wrap_content"
+                      android:paddingTop="@dimen/mr_controller_volume_group_list_padding_top"
+                      android:scrollbarStyle="outsideInset"
+                      android:clipToPadding="false"
                       android:background="?attr/colorPrimaryDark"
                       android:visibility="gone" />
         </LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_controller_volume_item.xml b/v7/mediarouter/res/layout/mr_controller_volume_item.xml
index dc539eb..6cd0fd6 100644
--- a/v7/mediarouter/res/layout/mr_controller_volume_item.xml
+++ b/v7/mediarouter/res/layout/mr_controller_volume_item.xml
@@ -14,29 +14,31 @@
      limitations under the License.
 -->
 
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="fill_parent"
               android:layout_height="@dimen/mr_controller_volume_group_list_item_height"
               android:paddingLeft="24dp"
               android:paddingRight="60dp"
-              android:paddingTop="16dp" >
+              android:paddingBottom="8dp"
+              android:orientation="vertical" >
     <TextView android:id="@+id/mr_name"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
-              android:layout_alignParentTop="true"
-              android:layout_alignParentLeft="true"
               android:textAppearance="?attr/mediaRouteControllerSecondaryTextStyle"
               android:singleLine="true" />
-    <ImageView android:id="@+id/mr_volume_item_icon"
-               android:layout_width="24dp"
-               android:layout_height="24dp"
-               android:layout_marginTop="12dp"
-               android:layout_below="@id/mr_name"
-               android:src="?attr/mediaRouteAudioTrackDrawable" />
-    <SeekBar android:id="@+id/mr_volume_slider"
-             android:layout_width="fill_parent"
-             android:layout_height="wrap_content"
-             android:layout_marginTop="8dp"
-             android:layout_below="@id/mr_name"
-             android:layout_toRightOf="@id/mr_volume_item_icon" />
-</RelativeLayout>
+    <LinearLayout android:layout_width="fill_parent"
+                  android:layout_height="wrap_content"
+                  android:orientation="horizontal"
+                  android:gravity="center_vertical">
+        <ImageView android:id="@+id/mr_volume_item_icon"
+                   android:layout_width="@dimen/mr_controller_volume_group_list_item_icon_size"
+                   android:layout_height="@dimen/mr_controller_volume_group_list_item_icon_size"
+                   android:layout_marginTop="8dp"
+                   android:layout_marginBottom="8dp"
+                   android:scaleType="fitCenter"
+                   android:src="?attr/mediaRouteAudioTrackDrawable" />
+        <android.support.v7.app.MediaRouteVolumeSlider android:id="@+id/mr_volume_slider"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content" />
+    </LinearLayout>
+</LinearLayout>
diff --git a/v7/mediarouter/res/layout/mr_volume_control.xml b/v7/mediarouter/res/layout/mr_volume_control.xml
index 4e0abb8..996a69f 100644
--- a/v7/mediarouter/res/layout/mr_volume_control.xml
+++ b/v7/mediarouter/res/layout/mr_volume_control.xml
@@ -27,7 +27,7 @@
                android:src="?attr/mediaRouteAudioTrackDrawable"
                android:gravity="center"
                android:scaleType="center" />
-    <SeekBar android:id="@+id/mr_volume_slider"
+    <android.support.v7.app.MediaRouteVolumeSlider android:id="@+id/mr_volume_slider"
              android:layout_width="0dp"
              android:layout_height="wrap_content"
              android:layout_weight="1" />
diff --git a/v7/mediarouter/res/values-land/dimens.xml b/v7/mediarouter/res/values-land/dimens.xml
index 0f0e5a1..ee606aa 100644
--- a/v7/mediarouter/res/values-land/dimens.xml
+++ b/v7/mediarouter/res/values-land/dimens.xml
@@ -15,6 +15,12 @@
 -->
 
 <resources>
-    <!-- Maximum height of MediaRouteController's volume group list. -->
+    <!-- MediaRouteController's volume group list -->
+    <eat-comment />
+    <!-- Maximum height of volume group list. -->
     <dimen name="mr_controller_volume_group_list_max_height">132dp</dimen>
+    <!-- Height of volume group item. -->
+    <dimen name="mr_controller_volume_group_list_item_height">61dp</dimen>
+    <!-- Size of an item's icon. -->
+    <dimen name="mr_controller_volume_group_list_item_icon_size">18dp</dimen>
 </resources>
diff --git a/v7/mediarouter/res/values/dimens.xml b/v7/mediarouter/res/values/dimens.xml
index 77656f4..af413a3 100644
--- a/v7/mediarouter/res/values/dimens.xml
+++ b/v7/mediarouter/res/values/dimens.xml
@@ -15,6 +15,8 @@
 -->
 
 <resources>
+    <!-- Dialog size -->
+    <eat-comment />
     <!-- The platform's desired fixed width for a dialog along the major axis
          (the screen is in landscape). This may be either a fraction or a dimension.-->
     <dimen name="mr_dialog_fixed_width_major">320dp</dimen>
@@ -22,11 +24,14 @@
          (the screen is in portrait). This may be either a fraction or a dimension.-->
     <dimen name="mr_dialog_fixed_width_minor">320dp</dimen>
 
-    <!-- Maximum height of MediaRouteController's volume group list. -->
+    <!-- MediaRouteController's volume group list -->
+    <eat-comment />
+    <!-- Maximum height of volume group list. -->
     <dimen name="mr_controller_volume_group_list_max_height">256dp</dimen>
-    <!-- Height of MediaRouteController's volume group item.
-         TODO: Define value for landscape once we know how to adjust SeekBar height. -->
-    <dimen name="mr_controller_volume_group_list_item_height">75dp</dimen>
+    <!-- Height of volume group item. -->
+    <dimen name="mr_controller_volume_group_list_item_height">67dp</dimen>
+    <!-- Size of an item's icon. -->
+    <dimen name="mr_controller_volume_group_list_item_icon_size">24dp</dimen>
 
-    <dimen name="mr_controller_volume_group_list_padding_bottom">8dp</dimen>
+    <dimen name="mr_controller_volume_group_list_padding_top">16dp</dimen>
 </resources>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 5b00156..80b3de1 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -20,16 +20,15 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.support.v4.media.MediaDescriptionCompat;
@@ -40,7 +39,6 @@
 import android.support.v7.graphics.Palette;
 import android.support.v7.media.MediaRouteSelector;
 import android.support.v7.media.MediaRouter;
-import android.support.v7.media.SeekBarJellybean;
 import android.support.v7.mediarouter.R;
 import android.text.TextUtils;
 import android.util.Log;
@@ -85,6 +83,8 @@
     // to allow the route provider time to propagate the change and publish a new
     // route descriptor.
     private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
+    private static final int VOLUME_SLIDER_TAG_MASTER = 0;
+    private static final int VOLUME_SLIDER_TAG_BASE = 100;
 
     private static final int BUTTON_NEUTRAL_RES_ID = android.R.id.button3;
     private static final int BUTTON_DISCONNECT_RES_ID = android.R.id.button2;
@@ -126,11 +126,12 @@
 
     private ListView mVolumeGroupList;
     private SeekBar mVolumeSlider;
+    private VolumeChangeListener mVolumeChangeListener;
     private boolean mVolumeSliderTouched;
-    private int mVolumeSliderColor;
-    private final int mVolumeGroupListItemHeight;
-    private final int mVolumeGroupListMaxHeight;
-    private final int mVolumeGroupListPaddingBottom;
+    private int mVolumeGroupListItemIconSize;
+    private int mVolumeGroupListItemHeight;
+    private int mVolumeGroupListMaxHeight;
+    private final int mVolumeGroupListPaddingTop;
 
     private MediaControllerCompat mMediaController;
     private MediaControllerCallback mControllerCallback;
@@ -155,12 +156,8 @@
         mCallback = new MediaRouterCallback();
         mRoute = mRouter.getSelectedRoute();
         setMediaSession(mRouter.getMediaSessionToken());
-        mVolumeGroupListItemHeight = context.getResources().getDimensionPixelSize(
-                R.dimen.mr_controller_volume_group_list_item_height);
-        mVolumeGroupListMaxHeight = context.getResources().getDimensionPixelSize(
-                R.dimen.mr_controller_volume_group_list_max_height);
-        mVolumeGroupListPaddingBottom = context.getResources().getDimensionPixelSize(
-                R.dimen.mr_controller_volume_group_list_padding_bottom);
+        mVolumeGroupListPaddingTop = context.getResources().getDimensionPixelSize(
+                R.dimen.mr_controller_volume_group_list_padding_top);
     }
 
     /**
@@ -256,9 +253,9 @@
     }
 
     /**
-     * Gets the description being used by the default UI.
+     * Gets the associated session's token.
      *
-     * @return The current description.
+     * @return session token.
      */
     public MediaSessionCompat.Token getMediaSession() {
         return mMediaController == null ? null : mMediaController.getSessionToken();
@@ -308,43 +305,9 @@
 
         mVolumeControl = (LinearLayout) findViewById(R.id.mr_volume_control);
         mVolumeSlider = (SeekBar) findViewById(R.id.mr_volume_slider);
-        mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            private final Runnable mStopTrackingTouch = new Runnable() {
-                @Override
-                public void run() {
-                    if (mVolumeSliderTouched) {
-                        mVolumeSliderTouched = false;
-                        updateVolumeControl();
-                    }
-                }
-            };
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-                if (mVolumeSliderTouched) {
-                    mVolumeSlider.removeCallbacks(mStopTrackingTouch);
-                } else {
-                    mVolumeSliderTouched = true;
-                }
-            }
-
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-                // Defer resetting mVolumeSliderTouched to allow the media route provider
-                // a little time to settle into its new state and publish the final
-                // volume update.
-                mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
-            }
-
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (fromUser) {
-                    mRoute.requestSetVolume(progress);
-                }
-            }
-        });
-        mVolumeSliderColor = MediaRouterThemeHelper.getVolumeSliderColor(getContext());
-        setVolumeSliderColor(getContext(), mVolumeSlider, mVolumeSliderColor);
+        mVolumeSlider.setTag(VOLUME_SLIDER_TAG_MASTER);
+        mVolumeChangeListener = new VolumeChangeListener();
+        mVolumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
 
         TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
                 R.attr.mediaRouteExpandGroupDrawable,
@@ -400,6 +363,14 @@
         View decorView = getWindow().getDecorView();
         mDialogContentWidth = width - decorView.getPaddingLeft() - decorView.getPaddingRight();
 
+        Resources res = getContext().getResources();
+        mVolumeGroupListItemIconSize = res.getDimensionPixelSize(
+                R.dimen.mr_controller_volume_group_list_item_icon_size);
+        mVolumeGroupListItemHeight = res.getDimensionPixelSize(
+                R.dimen.mr_controller_volume_group_list_item_height);
+        mVolumeGroupListMaxHeight = res.getDimensionPixelSize(
+                R.dimen.mr_controller_volume_group_list_max_height);
+
         // Ensure the mArtView is updated.
         mArtIconBitmap = null;
         mArtIconUri = null;
@@ -498,7 +469,7 @@
         mDividerView.setVisibility((mVolumeControl.getVisibility() == View.VISIBLE
                 && showPlaybackControl) ? View.VISIBLE : View.GONE);
         mMediaControlLayout.setVisibility((mVolumeControl.getVisibility() == View.GONE
-                && showPlaybackControl) ? View.GONE : View.VISIBLE);
+                && !showPlaybackControl) ? View.GONE : View.VISIBLE);
     }
 
     private void updateLayoutHeight() {
@@ -539,8 +510,17 @@
         int mainControllerHeight = getMainControllerHeight(isPlaybackControlAvailable());
         int volumeGroupListCount = mVolumeGroupList.getVisibility() == View.VISIBLE
                 ? mVolumeGroupList.getAdapter().getCount() : 0;
+        // Scale down volume group list items in landscape mode.
+        for (int i = 0; i < volumeGroupListCount; i++) {
+            View item = mVolumeGroupList.getChildAt(i);
+            if (item != null) {
+                setLayoutHeight(item, mVolumeGroupListItemHeight);
+                setLayoutHeight(item.findViewById(R.id.mr_volume_item_icon),
+                        mVolumeGroupListItemIconSize);
+            }
+        }
         int volumeGroupHeight = (volumeGroupListCount == 0) ? 0
-                : mVolumeGroupListItemHeight * volumeGroupListCount + mVolumeGroupListPaddingBottom;
+                : mVolumeGroupListItemHeight * volumeGroupListCount + mVolumeGroupListPaddingTop;
         volumeGroupHeight = Math.min(volumeGroupHeight, mVolumeGroupListMaxHeight);
 
         int desiredControlLayoutHeight =
@@ -589,7 +569,7 @@
 
     private void updateVolumeControl() {
         if (!mVolumeSliderTouched) {
-            if (isVolumeControlAvailable()) {
+            if (isVolumeControlAvailable(mRoute)) {
                 mVolumeControl.setVisibility(View.VISIBLE);
                 mVolumeSlider.setMax(mRoute.getVolumeMax());
                 mVolumeSlider.setProgress(mRoute.getVolume());
@@ -609,6 +589,18 @@
                 mVolumeControl.setVisibility(View.GONE);
             }
             updateLayoutHeight();
+        } else if (mVolumeControl.getVisibility() == View.VISIBLE) {
+            mVolumeSlider.setProgress(mRoute.getVolume());
+            if (mIsGroupExpanded) {
+                for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+                    MediaRouter.RouteInfo route = getGroup().getRouteAt(i);
+                    if (isVolumeControlAvailable(route)) {
+                        SeekBar volumeSlider = (SeekBar) mVolumeGroupList.getChildAt(i)
+                                .findViewById(R.id.mr_volume_slider);
+                        volumeSlider.setProgress(route.getVolume());
+                    }
+                }
+            }
         }
     }
 
@@ -673,9 +665,9 @@
         updateLayoutHeight();
     }
 
-    private boolean isVolumeControlAvailable() {
-        return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
-                MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+    private boolean isVolumeControlAvailable(MediaRouter.RouteInfo route) {
+        return mVolumeControlEnabled && route.getVolumeHandling()
+                == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
     }
 
     private static void setLayoutHeight(View view, int height) {
@@ -684,19 +676,6 @@
         view.setLayoutParams(lp);
     }
 
-    private static void setVolumeSliderColor(Context context, SeekBar volumeSlider, int color) {
-        volumeSlider.getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
-        if (Build.VERSION.SDK_INT >= 16) {
-            SeekBarJellybean.getThumb(volumeSlider).setColorFilter(color, PorterDuff.Mode.SRC_IN);
-        } else {
-            // In case getThumb() isn't available, use the thumb drawable from AppCompat.
-            Drawable thumb =
-                    context.getResources().getDrawable(R.drawable.abc_seekbar_thumb_material);
-            thumb.setColorFilter(color, PorterDuff.Mode.SRC_IN);
-            volumeSlider.setThumb(thumb);
-        }
-    }
-
     /**
      * Returns desired art height to fit into controller dialog.
      */
@@ -775,29 +754,51 @@
         }
     }
 
-    private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
-        final static float DISABLED_ALPHA = .3f;
-
-        final OnSeekBarChangeListener mOnSeekBarChangeListener = new OnSeekBarChangeListener() {
+    private class VolumeChangeListener implements OnSeekBarChangeListener {
+        private final Runnable mStopTrackingTouch = new Runnable() {
             @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (fromUser) {
-                    int position = (int) seekBar.getTag();
-                    getGroup().getRouteAt(position).requestSetVolume(progress);
+            public void run() {
+                if (mVolumeSliderTouched) {
+                    mVolumeSliderTouched = false;
+                    updateVolumeControl();
                 }
             }
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {
-                // TODO: Implement
-            }
-
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {
-                // TODO: Implement
-            }
         };
 
+        @Override
+        public void onStartTrackingTouch(SeekBar seekBar) {
+            if (mVolumeSliderTouched) {
+                mVolumeSlider.removeCallbacks(mStopTrackingTouch);
+            } else {
+                mVolumeSliderTouched = true;
+            }
+        }
+
+        @Override
+        public void onStopTrackingTouch(SeekBar seekBar) {
+            // Defer resetting mVolumeSliderTouched to allow the media route provider
+            // a little time to settle into its new state and publish the final
+            // volume update.
+            mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
+        }
+
+        @Override
+        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            if (fromUser) {
+                int tag = (int) seekBar.getTag();
+                if (tag == VOLUME_SLIDER_TAG_MASTER) {
+                    mRoute.requestSetVolume(progress);
+                } else if (tag - VOLUME_SLIDER_TAG_BASE >= 0
+                        && tag - VOLUME_SLIDER_TAG_BASE < getGroup().getRouteCount()) {
+                    getGroup().getRouteAt(tag - VOLUME_SLIDER_TAG_BASE).requestSetVolume(progress);
+                }
+            }
+        }
+    }
+
+    private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
+        final static float DISABLED_ALPHA = .3f;
+
         public VolumeGroupAdapter(Context context, List<MediaRouter.RouteInfo> objects) {
             super(context, 0, objects);
         }
@@ -808,10 +809,7 @@
             if (v == null) {
                 v = LayoutInflater.from(getContext()).inflate(
                         R.layout.mr_controller_volume_item, parent, false);
-                setVolumeSliderColor(getContext(), (SeekBar) v.findViewById(R.id.mr_volume_slider),
-                        mVolumeSliderColor);
             }
-            setViewPaddingBottom(v, position == getCount() - 1 ? mVolumeGroupListPaddingBottom : 0);
 
             MediaRouter.RouteInfo route = getItem(position);
             if (route != null) {
@@ -821,25 +819,22 @@
                 routeName.setEnabled(isEnabled);
                 routeName.setText(route.getName());
 
-                SeekBar volumeSlider = (SeekBar) v.findViewById(R.id.mr_volume_slider);
-                volumeSlider.setEnabled(isEnabled);
-                volumeSlider.setTag(position);
+                MediaRouteVolumeSlider volumeSlider =
+                        (MediaRouteVolumeSlider) v.findViewById(R.id.mr_volume_slider);
+                volumeSlider.setTag(VOLUME_SLIDER_TAG_BASE + position);
+                volumeSlider.setShowThumb(isEnabled);
                 if (isEnabled) {
-                    if (route.getVolumeHandling()
-                            == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
+                    if (isVolumeControlAvailable(route)) {
                         volumeSlider.setMax(route.getVolumeMax());
                         volumeSlider.setProgress(route.getVolume());
-                        volumeSlider.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
+                        volumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
+                        volumeSlider.setEnabled(true);
                     } else {
                         volumeSlider.setMax(100);
                         volumeSlider.setProgress(100);
                         volumeSlider.setEnabled(false);
                     }
                 }
-                if (Build.VERSION.SDK_INT >= 16) {
-                    SeekBarJellybean.getThumb(volumeSlider).mutate().setAlpha(isEnabled ? 255 : 0);
-                    // TODO: Still see an artifact even though the thumb is transparent. Remove it.
-                }
 
                 ImageView volumeItemIcon =
                         (ImageView) v.findViewById(R.id.mr_volume_item_icon);
@@ -849,21 +844,6 @@
         }
     }
 
-    private static void setViewPaddingBottom(View view, int newBottom) {
-        int left = view.getPaddingLeft();
-        int top = view.getPaddingTop();
-        int right = view.getPaddingRight();
-        int bottom = view.getPaddingBottom();
-        view.setPadding(left, top, right, newBottom);
-
-        ViewGroup.LayoutParams lp = view.getLayoutParams();
-        if (lp.height != ViewGroup.LayoutParams.FILL_PARENT
-                && lp.height != ViewGroup.LayoutParams.WRAP_CONTENT) {
-            lp.height = lp.height - bottom + newBottom;
-            view.setLayoutParams(lp);
-        }
-    }
-
     private class FetchArtTask extends AsyncTask<Void, Void, Bitmap> {
         final Bitmap mIconBitmap;
         final Uri mIconUri;
@@ -937,7 +917,7 @@
                 }
             }
             if (art != null && art.getWidth() < art.getHeight()) {
-                // Portrait art requires background color.
+                // Portrait art requires dominant color as background color.
                 Palette palette = new Palette.Builder(art).maximumColorCount(1).generate();
                 mBackgroundColor = palette.getSwatches().isEmpty()
                         ? 0 : palette.getSwatches().get(0).getRgb();
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
new file mode 100644
index 0000000..9e650fc
--- /dev/null
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
@@ -0,0 +1,77 @@
+/*
+ * 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.support.v7.app;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.mediarouter.R;
+import android.support.v7.widget.AppCompatSeekBar;
+import android.util.AttributeSet;
+
+/**
+ * Volume slider with showing, hiding, and applying alpha supports to the thumb.
+ */
+class MediaRouteVolumeSlider extends AppCompatSeekBar {
+    private boolean mShowThumb = true;
+    private Drawable mThumb;
+    private float mDisabledAlpha;
+
+    public MediaRouteVolumeSlider(Context context) {
+        super(context, null);
+    }
+
+    public MediaRouteVolumeSlider(Context context, AttributeSet attrs) {
+        this(context, attrs, android.support.v7.appcompat.R.attr.seekBarStyle);
+    }
+
+    public MediaRouteVolumeSlider(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mThumb = ContextCompat.getDrawable(context, R.drawable.mr_seekbar_thumb);
+        setThumb(mThumb);
+        int color = MediaRouterThemeHelper.getVolumeSliderColor(context);
+        ColorFilter colorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN);
+        getProgressDrawable().setColorFilter(colorFilter);
+        mThumb.setColorFilter(colorFilter);
+        TypedArray ta = context.obtainStyledAttributes(
+                attrs, new int[] {android.R.attr.disabledAlpha}, defStyleAttr, 0);
+        mDisabledAlpha = ta.getFloat(0, 0.5f);
+        ta.recycle();
+    }
+
+    /**
+     * Sets whether to show/hide thumb.
+     */
+    public void setShowThumb(boolean showThumb) {
+        if (mShowThumb == showThumb) {
+            return;
+        }
+        mShowThumb = showThumb;
+        setThumb(mShowThumb ? mThumb : null);
+    }
+
+    @Override
+    protected void drawableStateChanged() {
+        mThumb.setAlpha(isEnabled() ? 0xFF : (int)(0xFF * mDisabledAlpha));
+        super.drawableStateChanged();
+    }
+}