Bring AlertDialog up to date with N framework

Test: all existing tests pass

Change-Id: I8fb64f443ad8e0b742e2242dec7fc50f992d6e18
diff --git a/api/current.txt b/api/current.txt
index 4ea0b76..a000fdf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9755,6 +9755,7 @@
     method public int getBaselineAlignedChildIndex();
     method public android.graphics.drawable.Drawable getDividerDrawable();
     method public int getDividerPadding();
+    method public int getGravity();
     method public int getOrientation();
     method public int getShowDividers();
     method public float getWeightSum();
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
index 08adfd1..f747278 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
@@ -15,44 +15,50 @@
      limitations under the License.
 -->
 
-<android.support.v7.widget.ButtonBarLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/buttonPanel"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layoutDirection="locale"
-    android:orientation="horizontal"
-    android:paddingLeft="12dp"
-    android:paddingRight="12dp"
-    android:paddingTop="4dp"
-    android:paddingBottom="4dp"
-    android:gravity="bottom"
-    style="?attr/buttonBarStyle">
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/buttonPanel"
+            style="?attr/buttonBarStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:fillViewport="true"
+            android:scrollIndicators="top|bottom">
 
-    <Button
-        android:id="@android:id/button3"
-        style="?attr/buttonBarNeutralButtonStyle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
+    <android.support.v7.widget.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="bottom"
+        android:layoutDirection="locale"
+        android:orientation="horizontal"
+        android:paddingBottom="4dp"
+        android:paddingLeft="12dp"
+        android:paddingRight="12dp"
+        android:paddingTop="4dp">
 
-    <android.support.v4.widget.Space
-        android:id="@+id/spacer"
-        android:layout_width="0dp"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:visibility="invisible" />
+        <Button
+            android:id="@android:id/button3"
+            style="?attr/buttonBarNeutralButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
 
-    <Button
-        android:id="@android:id/button2"
-        style="?attr/buttonBarNegativeButtonStyle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
+        <android.support.v4.widget.Space
+            android:id="@+id/spacer"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:visibility="invisible"/>
 
-    <Button
-        android:id="@android:id/button1"
-        style="?attr/buttonBarPositiveButtonStyle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
+        <Button
+            android:id="@android:id/button2"
+            style="?attr/buttonBarNegativeButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
 
-</android.support.v7.widget.ButtonBarLayout>
+        <Button
+            android:id="@android:id/button1"
+            style="?attr/buttonBarPositiveButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+    </android.support.v7.widget.ButtonBarLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_material.xml
index 3237533..40aee7f 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_material.xml
@@ -15,113 +15,85 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/parentPanel"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical">
+<android.support.v7.widget.AlertDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="start|left|top"
+    android:orientation="vertical">
 
-    <LinearLayout
-            android:id="@+id/topPanel"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical">
-
-        <LinearLayout
-                android:id="@+id/title_template"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal"
-                android:gravity="center_vertical"
-                android:paddingLeft="?attr/dialogPreferredPadding"
-                android:paddingRight="?attr/dialogPreferredPadding"
-                android:paddingTop="@dimen/abc_dialog_padding_top_material">
-
-            <ImageView
-                    android:id="@android:id/icon"
-                    android:layout_width="32dip"
-                    android:layout_height="32dip"
-                    android:scaleType="fitCenter"
-                    android:src="@null"
-                    style="@style/RtlOverlay.Widget.AppCompat.DialogTitle.Icon"/>
-
-            <android.support.v7.widget.DialogTitle
-                    android:id="@+id/alertTitle"
-                    style="?attr/android:windowTitleStyle"
-                    android:singleLine="true"
-                    android:ellipsize="end"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:textAlignment="viewStart" />
-
-        </LinearLayout>
-        <!-- If the client uses a customTitle, it will be added here. -->
-    </LinearLayout>
+    <include layout="@layout/abc_alert_dialog_title_material"/>
 
     <FrameLayout
-            android:id="@+id/contentPanel"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:minHeight="48dp">
+        android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
 
         <View android:id="@+id/scrollIndicatorUp"
-              android:visibility="gone"
               android:layout_width="match_parent"
               android:layout_height="1dp"
               android:layout_gravity="top"
-              android:background="?attr/colorControlHighlight"/>
+              android:background="?attr/colorControlHighlight"
+              android:visibility="gone"/>
 
         <android.support.v4.widget.NestedScrollView
-                android:id="@+id/scrollView"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:clipToPadding="false">
+            android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
 
             <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <android.support.v4.widget.Space
+                    android:id="@+id/textSpacerNoTitle"
                     android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:orientation="vertical">
+                    android:layout_height="@dimen/abc_dialog_padding_top_material"
+                    android:visibility="gone"/>
 
                 <TextView
-                        android:id="@android:id/message"
-                        style="@style/TextAppearance.AppCompat.Subhead"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:paddingLeft="?attr/dialogPreferredPadding"
-                        android:paddingTop="@dimen/abc_dialog_padding_top_material"
-                        android:paddingRight="?attr/dialogPreferredPadding"/>
+                    android:id="@android:id/message"
+                    style="@style/TextAppearance.AppCompat.Subhead"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingLeft="?attr/dialogPreferredPadding"
+                    android:paddingRight="?attr/dialogPreferredPadding"/>
 
-                <View
-                        android:id="@+id/textSpacerNoButtons"
-                        android:visibility="gone"
-                        android:layout_width="0dp"
-                        android:layout_height="@dimen/abc_dialog_padding_top_material"/>
+                <android.support.v4.widget.Space
+                    android:id="@+id/textSpacerNoButtons"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/abc_dialog_padding_top_material"
+                    android:visibility="gone"/>
             </LinearLayout>
         </android.support.v4.widget.NestedScrollView>
 
         <View android:id="@+id/scrollIndicatorDown"
-              android:visibility="gone"
               android:layout_width="match_parent"
               android:layout_height="1dp"
               android:layout_gravity="bottom"
-              android:background="?attr/colorControlHighlight"/>
+              android:background="?attr/colorControlHighlight"
+              android:visibility="gone"/>
 
     </FrameLayout>
 
     <FrameLayout
-            android:id="@+id/customPanel"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:minHeight="48dp">
+        android:id="@+id/customPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
 
         <FrameLayout
-                android:id="@+id/custom"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"/>
+            android:id="@+id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"/>
     </FrameLayout>
 
-    <include layout="@layout/abc_alert_dialog_button_bar_material" />
+    <include layout="@layout/abc_alert_dialog_button_bar_material"
+             android:layout_width="match_parent"
+             android:layout_height="wrap_content"/>
 
-</LinearLayout>
\ No newline at end of file
+</android.support.v7.widget.AlertDialogLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_alert_dialog_title_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_title_material.xml
new file mode 100644
index 0000000..0b8b14e
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_alert_dialog_title_material.xml
@@ -0,0 +1,61 @@
+<?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:id="@+id/topPanel"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical">
+
+    <!-- If the client uses a customTitle, it will be added here. -->
+
+    <LinearLayout
+        android:id="@+id/title_template"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical|start|left"
+        android:orientation="horizontal"
+        android:paddingLeft="?attr/dialogPreferredPadding"
+        android:paddingRight="?attr/dialogPreferredPadding"
+        android:paddingTop="@dimen/abc_dialog_padding_top_material">
+
+        <ImageView
+            android:id="@android:id/icon"
+            android:layout_width="32dip"
+            android:layout_height="32dip"
+            android:layout_marginEnd="8dip"
+            android:layout_marginRight="8dip"
+            android:scaleType="fitCenter"
+            android:src="@null"/>
+
+        <android.support.v7.widget.DialogTitle
+            android:id="@+id/alertTitle"
+            style="?android:attr/windowTitleStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:singleLine="true"
+            android:textAlignment="viewStart"/>
+
+    </LinearLayout>
+
+    <android.support.v4.widget.Space
+        android:id="@+id/titleDividerNoCustom"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/abc_dialog_title_divider_material"
+        android:visibility="gone"/>
+</LinearLayout>
diff --git a/v7/appcompat/res/layout/abc_select_dialog_material.xml b/v7/appcompat/res/layout/abc_select_dialog_material.xml
index 12bcbf1..ae4b268 100644
--- a/v7/appcompat/res/layout/abc_select_dialog_material.xml
+++ b/v7/appcompat/res/layout/abc_select_dialog_material.xml
@@ -20,16 +20,19 @@
     This layout file is inflated and used as the ListView to display the items.
     Assign an ID so its state will be saved/restored.
 -->
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+<view xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto"
       android:id="@+id/select_dialog_listview"
+      style="@style/Widget.AppCompat.ListView"
+      class="android.support.v7.app.AlertController$RecycleListView"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:cacheColorHint="@null"
-      android:divider="?attr/listDividerAlertDialog"
-      android:scrollbars="vertical"
-      android:overScrollMode="ifContentScrolls"
-      android:fadingEdge="none"
-      android:paddingTop="@dimen/abc_dialog_list_padding_vertical_material"
-      android:paddingBottom="@dimen/abc_dialog_list_padding_vertical_material"
       android:clipToPadding="false"
-      style="@style/Widget.AppCompat.ListView" />
\ No newline at end of file
+      android:divider="?attr/listDividerAlertDialog"
+      android:fadingEdge="none"
+      android:overScrollMode="ifContentScrolls"
+      android:scrollbars="vertical"
+      android:textAlignment="viewStart"
+      app:paddingBottomNoButtons="@dimen/abc_dialog_list_padding_bottom_no_buttons"
+      app:paddingTopNoTitle="@dimen/abc_dialog_list_padding_top_no_title"/>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index f9990db..03ddeec 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -1038,6 +1038,7 @@
         <attr name="multiChoiceItemLayout" format="reference" />
         <attr name="singleChoiceItemLayout" format="reference" />
         <attr name="listItemLayout" format="reference" />
+        <attr name="showTitle" format="boolean" />
     </declare-styleable>
 
     <!-- @hide -->
@@ -1101,4 +1102,11 @@
         <attr name="android:textAppearance" />
     </declare-styleable>
 
+    <declare-styleable name="RecycleListView">
+        <!-- Bottom padding to use when no buttons are present. -->
+        <attr name="paddingBottomNoButtons" format="dimension" />
+        <!-- Top padding to use when no title is present. -->
+        <attr name="paddingTopNoTitle" format="dimension" />
+    </declare-styleable>
+
 </resources>
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 7467beb..e055eed 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -72,13 +72,13 @@
 
     <dimen name="abc_dialog_padding_material">24dp</dimen>
     <dimen name="abc_dialog_padding_top_material">18dp</dimen>
+    <dimen name="abc_dialog_title_divider_material">8dp</dimen>
+    <dimen name="abc_dialog_list_padding_top_no_title">8dp</dimen>
+    <dimen name="abc_dialog_list_padding_bottom_no_buttons">8dp</dimen>
 
     <!-- Dialog button bar height -->
     <dimen name="abc_alert_dialog_button_bar_height">48dp</dimen>
 
-    <!-- Padding above and below selection dialog lists. -->
-    <dimen name="abc_dialog_list_padding_vertical_material">8dp</dimen>
-
     <!-- Dialog padding minus control padding, used to fix alignment. -->
     <dimen name="abc_select_dialog_padding_start_material">20dp</dimen>
 
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index b8c1f29..a760b78 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -468,7 +468,6 @@
 
     <style name="Base.Widget.AppCompat.Button.ButtonBar.AlertDialog" parent="Widget.AppCompat.Button.Borderless.Colored">
         <item name="android:minWidth">64dp</item>
-        <item name="android:maxLines">2</item>
         <item name="android:minHeight">@dimen/abc_alert_dialog_button_bar_height</item>
     </style>
 
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index df3473d..92e7bce 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -272,10 +272,10 @@
 
         <!-- Define these here; ContextThemeWrappers around themes that define them should
              always clear these values. -->
-        <item name="windowFixedWidthMajor">0dp</item>
-        <item name="windowFixedWidthMinor">0dp</item>
-        <item name="windowFixedHeightMajor">0dp</item>
-        <item name="windowFixedHeightMinor">0dp</item>
+        <item name="windowFixedWidthMajor">@null</item>
+        <item name="windowFixedWidthMinor">@null</item>
+        <item name="windowFixedHeightMajor">@null</item>
+        <item name="windowFixedHeightMinor">@null</item>
     </style>
 
     <!-- Base platform-dependent theme providing an action bar in a light-themed activity. -->
@@ -433,10 +433,10 @@
 
         <!-- Define these here; ContextThemeWrappers around themes that define them should
              always clear these values. -->
-        <item name="windowFixedWidthMajor">0dp</item>
-        <item name="windowFixedWidthMinor">0dp</item>
-        <item name="windowFixedHeightMajor">0dp</item>
-        <item name="windowFixedHeightMinor">0dp</item>
+        <item name="windowFixedWidthMajor">@null</item>
+        <item name="windowFixedWidthMinor">@null</item>
+        <item name="windowFixedHeightMajor">@null</item>
+        <item name="windowFixedHeightMinor">@null</item>
     </style>
 
     <style name="Base.Theme.AppCompat" parent="Base.V7.Theme.AppCompat">
@@ -675,6 +675,11 @@
         <item name="listPreferredItemPaddingRight">24dip</item>
 
         <item name="android:listDivider">@null</item>
+
+        <item name="windowFixedWidthMajor">@null</item>
+        <item name="windowFixedWidthMinor">@null</item>
+        <item name="windowFixedHeightMajor">@null</item>
+        <item name="windowFixedHeightMinor">@null</item>
     </style>
 
 </resources>
diff --git a/v7/appcompat/src/android/support/v7/app/AlertController.java b/v7/appcompat/src/android/support/v7/app/AlertController.java
index c7aad2d..ee7ea8e 100644
--- a/v7/appcompat/src/android/support/v7/app/AlertController.java
+++ b/v7/appcompat/src/android/support/v7/app/AlertController.java
@@ -29,7 +29,9 @@
 import android.support.v4.widget.NestedScrollView;
 import android.support.v7.appcompat.R;
 import android.text.TextUtils;
+import android.util.AttributeSet;
 import android.util.TypedValue;
+import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -109,6 +111,8 @@
     int mSingleChoiceItemLayout;
     int mListItemLayout;
 
+    private boolean mShowTitle;
+
     private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE;
 
     Handler mHandler;
@@ -163,6 +167,12 @@
         }
     }
 
+    private static boolean shouldCenterSingleButton(Context context) {
+        final TypedValue outValue = new TypedValue();
+        context.getTheme().resolveAttribute(R.attr.alertDialogCenterButtons, outValue, true);
+        return outValue.data != 0;
+    }
+
     public AlertController(Context context, AppCompatDialog di, Window window) {
         mContext = context;
         mDialog = di;
@@ -180,6 +190,7 @@
         mSingleChoiceItemLayout = a
                 .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0);
         mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
+        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
 
         a.recycle();
 
@@ -486,6 +497,29 @@
             if (mScrollView != null) {
                 mScrollView.setClipToPadding(true);
             }
+
+            // Only show the divider if we have a title.
+            View divider = null;
+            if (mMessage != null || mListView != null || hasCustomPanel) {
+                if (!hasCustomPanel) {
+                    divider = topPanel.findViewById(R.id.titleDividerNoCustom);
+                }
+            }
+
+            if (divider != null) {
+                divider.setVisibility(View.VISIBLE);
+            }
+        } else {
+            if (contentPanel != null) {
+                final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
+                if (spacer != null) {
+                    spacer.setVisibility(View.VISIBLE);
+                }
+            }
+        }
+
+        if (mListView instanceof RecycleListView) {
+            ((RecycleListView) mListView).setHasDecor(hasTopPanel, hasButtonPanel);
         }
 
         // Update scroll indicators as needed.
@@ -640,7 +674,7 @@
             mIconView = (ImageView) mWindow.findViewById(android.R.id.icon);
 
             final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
-            if (hasTextTitle) {
+            if (hasTextTitle && mShowTitle) {
                 // Display the title if a title is supplied, else hide it.
                 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle);
                 mTitleView.setText(mTitle);
@@ -751,12 +785,63 @@
             whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
         }
 
+        if (shouldCenterSingleButton(mContext)) {
+            /*
+             * If we only have 1 button it should be centered on the layout and
+             * expand to fill 50% of the available space.
+             */
+            if (whichButtons == BIT_BUTTON_POSITIVE) {
+                centerButton(mButtonPositive);
+            } else if (whichButtons == BIT_BUTTON_NEGATIVE) {
+                centerButton(mButtonNegative);
+            } else if (whichButtons == BIT_BUTTON_NEUTRAL) {
+                centerButton(mButtonNeutral);
+            }
+        }
+
         final boolean hasButtons = whichButtons != 0;
         if (!hasButtons) {
             buttonPanel.setVisibility(View.GONE);
         }
     }
 
+    private void centerButton(Button button) {
+        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams();
+        params.gravity = Gravity.CENTER_HORIZONTAL;
+        params.weight = 0.5f;
+        button.setLayoutParams(params);
+    }
+
+    public static class RecycleListView extends ListView {
+        private final int mPaddingTopNoTitle;
+        private final int mPaddingBottomNoButtons;
+
+        public RecycleListView(Context context) {
+            this(context, null);
+        }
+
+        public RecycleListView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+
+            final TypedArray ta = context.obtainStyledAttributes(
+                    attrs, R.styleable.RecycleListView);
+            mPaddingBottomNoButtons = ta.getDimensionPixelOffset(
+                    R.styleable.RecycleListView_paddingBottomNoButtons, -1);
+            mPaddingTopNoTitle = ta.getDimensionPixelOffset(
+                    R.styleable.RecycleListView_paddingTopNoTitle, -1);
+        }
+
+        public void setHasDecor(boolean hasTitle, boolean hasButtons) {
+            if (!hasButtons || !hasTitle) {
+                final int paddingLeft = getPaddingLeft();
+                final int paddingTop = hasTitle ? getPaddingTop() : mPaddingTopNoTitle;
+                final int paddingRight = getPaddingRight();
+                final int paddingBottom = hasButtons ? getPaddingBottom() : mPaddingBottomNoButtons;
+                setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+            }
+        }
+    }
+
     public static class AlertParams {
         public final Context mContext;
         public final LayoutInflater mInflater;
@@ -877,7 +962,8 @@
         }
 
         private void createListView(final AlertController dialog) {
-            final ListView listView = (ListView) mInflater.inflate(dialog.mListLayout, null);
+            final RecycleListView listView =
+                    (RecycleListView) mInflater.inflate(dialog.mListLayout, null);
             final ListAdapter adapter;
 
             if (mIsMultiChoice) {
diff --git a/v7/appcompat/src/android/support/v7/widget/AlertDialogLayout.java b/v7/appcompat/src/android/support/v7/widget/AlertDialogLayout.java
new file mode 100644
index 0000000..e7600e9
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/AlertDialogLayout.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2016 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.widget;
+
+import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Special implementation of linear layout that's capable of laying out alert
+ * dialog components.
+ * <p>
+ * A dialog consists of up to three panels. All panels are optional, and a
+ * dialog may contain only a single panel. The panels are laid out according
+ * to the following guidelines:
+ * <ul>
+ *     <li>topPanel: exactly wrap_content</li>
+ *     <li>contentPanel OR customPanel: at most fill_parent, first priority for
+ *         extra space</li>
+ *     <li>buttonPanel: at least minHeight, at most wrap_content, second
+ *         priority for extra space</li>
+ * </ul>
+ *
+ * @hide
+ */
+@RestrictTo(GROUP_ID)
+public class AlertDialogLayout extends LinearLayoutCompat {
+
+    public AlertDialogLayout(@Nullable Context context) {
+        super(context);
+    }
+
+    public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!tryOnMeasure(widthMeasureSpec, heightMeasureSpec)) {
+            // Failed to perform custom measurement, let superclass handle it.
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    private boolean tryOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        View topPanel = null;
+        View buttonPanel = null;
+        View middlePanel = null;
+
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+
+            final int id = child.getId();
+            if (id == R.id.topPanel) {
+                topPanel = child;
+            } else if (id == R.id.buttonPanel) {
+                buttonPanel = child;
+            } else if (id == R.id.contentPanel || id == R.id.customPanel) {
+                if (middlePanel != null) {
+                    // Both the content and custom are visible. Abort!
+                    return false;
+                }
+                middlePanel = child;
+            } else {
+                // Unknown top-level child. Abort!
+                return false;
+            }
+        }
+
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+
+        int childState = 0;
+        int usedHeight = getPaddingTop() + getPaddingBottom();
+
+        if (topPanel != null) {
+            topPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+
+            usedHeight += topPanel.getMeasuredHeight();
+            childState = ViewCompat.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(topPanel));
+        }
+
+        int buttonHeight = 0;
+        int buttonWantsHeight = 0;
+        if (buttonPanel != null) {
+            buttonPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+            buttonHeight = resolveMinimumHeight(buttonPanel);
+            buttonWantsHeight = buttonPanel.getMeasuredHeight() - buttonHeight;
+
+            usedHeight += buttonHeight;
+            childState = ViewCompat.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(buttonPanel));
+        }
+
+        int middleHeight = 0;
+        if (middlePanel != null) {
+            final int childHeightSpec;
+            if (heightMode == MeasureSpec.UNSPECIFIED) {
+                childHeightSpec = MeasureSpec.UNSPECIFIED;
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(
+                        Math.max(0, heightSize - usedHeight), heightMode);
+            }
+
+            middlePanel.measure(widthMeasureSpec, childHeightSpec);
+            middleHeight = middlePanel.getMeasuredHeight();
+
+            usedHeight += middleHeight;
+            childState = ViewCompat.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(middlePanel));
+        }
+
+        int remainingHeight = heightSize - usedHeight;
+
+        // Time for the "real" button measure pass. If we have remaining space,
+        // make the button pane bigger up to its target height. Otherwise,
+        // just remeasure the button at whatever height it needs.
+        if (buttonPanel != null) {
+            usedHeight -= buttonHeight;
+
+            final int heightToGive = Math.min(remainingHeight, buttonWantsHeight);
+            if (heightToGive > 0) {
+                remainingHeight -= heightToGive;
+                buttonHeight += heightToGive;
+            }
+
+            final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+                    buttonHeight, MeasureSpec.EXACTLY);
+            buttonPanel.measure(widthMeasureSpec, childHeightSpec);
+
+            usedHeight += buttonPanel.getMeasuredHeight();
+            childState = ViewCompat.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(buttonPanel));
+        }
+
+        // If we still have remaining space, make the middle pane bigger up
+        // to the maximum height.
+        if (middlePanel != null && remainingHeight > 0) {
+            usedHeight -= middleHeight;
+
+            final int heightToGive = remainingHeight;
+            remainingHeight -= heightToGive;
+            middleHeight += heightToGive;
+
+            // Pass the same height mode as we're using for the dialog itself.
+            // If it's EXACTLY, then the middle pane MUST use the entire
+            // height.
+            final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+                    middleHeight, heightMode);
+            middlePanel.measure(widthMeasureSpec, childHeightSpec);
+
+            usedHeight += middlePanel.getMeasuredHeight();
+            childState = ViewCompat.combineMeasuredStates(childState,
+                    ViewCompat.getMeasuredState(middlePanel));
+        }
+
+        // Compute desired width as maximum child width.
+        int maxWidth = 0;
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != View.GONE) {
+                maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+            }
+        }
+
+        maxWidth += getPaddingLeft() + getPaddingRight();
+
+        final int widthSizeAndState = ViewCompat.resolveSizeAndState(
+                maxWidth, widthMeasureSpec, childState);
+        final int heightSizeAndState = ViewCompat.resolveSizeAndState(
+                usedHeight, heightMeasureSpec, 0);
+        setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+
+        // If the children weren't already measured EXACTLY, we need to run
+        // another measure pass to for MATCH_PARENT widths.
+        if (widthMode != MeasureSpec.EXACTLY) {
+            forceUniformWidth(count, heightMeasureSpec);
+        }
+
+        return true;
+    }
+
+    /**
+     * Remeasures child views to exactly match the layout's measured width.
+     *
+     * @param count the number of child views
+     * @param heightMeasureSpec the original height measure spec
+     */
+    private void forceUniformWidth(int count, int heightMeasureSpec) {
+        // Pretend that the linear layout has an exact size.
+        final int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(
+                getMeasuredWidth(), MeasureSpec.EXACTLY);
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.width == LayoutParams.MATCH_PARENT) {
+                    // Temporarily force children to reuse their old measured
+                    // height.
+                    final int oldHeight = lp.height;
+                    lp.height = child.getMeasuredHeight();
+
+                    // Remeasure with new dimensions.
+                    measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
+                    lp.height = oldHeight;
+                }
+            }
+        }
+    }
+
+    /**
+     * Attempts to resolve the minimum height of a view.
+     * <p>
+     * If the view doesn't have a minimum height set and only contains a single
+     * child, attempts to resolve the minimum height of the child view.
+     *
+     * @param v the view whose minimum height to resolve
+     * @return the minimum height
+     */
+    private static int resolveMinimumHeight(View v) {
+        final int minHeight = ViewCompat.getMinimumHeight(v);
+        if (minHeight > 0) {
+            return minHeight;
+        }
+
+        if (v instanceof ViewGroup) {
+            final ViewGroup vg = (ViewGroup) v;
+            if (vg.getChildCount() == 1) {
+                return resolveMinimumHeight(vg.getChildAt(0));
+            }
+        }
+
+        return 0;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int paddingLeft = getPaddingLeft();
+
+        // Where right end of child should go
+        final int width = right - left;
+        final int childRight = width - getPaddingRight();
+
+        // Space available for child
+        final int childSpace = width - paddingLeft - getPaddingRight();
+
+        final int totalLength = getMeasuredHeight();
+        final int count = getChildCount();
+        final int gravity = getGravity();
+        final int majorGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int minorGravity = gravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+        int childTop;
+        switch (majorGravity) {
+            case Gravity.BOTTOM:
+                // totalLength contains the padding already
+                childTop = getPaddingTop() + bottom - top - totalLength;
+                break;
+
+            // totalLength contains the padding already
+            case Gravity.CENTER_VERTICAL:
+                childTop = getPaddingTop() + (bottom - top - totalLength) / 2;
+                break;
+
+            case Gravity.TOP:
+            default:
+                childTop = getPaddingTop();
+                break;
+        }
+
+        final Drawable dividerDrawable = getDividerDrawable();
+        final int dividerHeight = dividerDrawable == null ?
+                0 : dividerDrawable.getIntrinsicHeight();
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child != null && child.getVisibility() != GONE) {
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+
+                final LinearLayoutCompat.LayoutParams lp =
+                        (LinearLayoutCompat.LayoutParams) child.getLayoutParams();
+
+                int layoutGravity = lp.gravity;
+                if (layoutGravity < 0) {
+                    layoutGravity = minorGravity;
+                }
+                final int layoutDirection = ViewCompat.getLayoutDirection(this);
+                final int absoluteGravity = GravityCompat.getAbsoluteGravity(
+                        layoutGravity, layoutDirection);
+
+                final int childLeft;
+                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+                                + lp.leftMargin - lp.rightMargin;
+                        break;
+
+                    case Gravity.RIGHT:
+                        childLeft = childRight - childWidth - lp.rightMargin;
+                        break;
+
+                    case Gravity.LEFT:
+                    default:
+                        childLeft = paddingLeft + lp.leftMargin;
+                        break;
+                }
+
+                if (hasDividerBeforeChildAt(i)) {
+                    childTop += dividerHeight;
+                }
+
+                childTop += lp.topMargin;
+                setChildFrame(child, childLeft, childTop, childWidth, childHeight);
+                childTop += childHeight + lp.bottomMargin;
+            }
+        }
+    }
+
+    private void setChildFrame(View child, int left, int top, int width, int height) {
+        child.layout(left, top, left + width, top + height);
+    }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
index 4f08f90..6c97ee1 100644
--- a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
+++ b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
@@ -37,12 +37,15 @@
  */
 @RestrictTo(GROUP_ID)
 public class ButtonBarLayout extends LinearLayout {
-    // Whether to allow vertically stacked button bars. This is disabled for
-    // configurations with a small (e.g. less than 320dp) screen height. -->
+    /** Minimum screen height required for button stacking. */
     private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
 
+    /** Amount of the second button to "peek" above the fold when stacked. */
+    private static final int PEEK_BUTTON_DP = 16;
+
     /** Whether the current configuration allows stacking. */
     private boolean mAllowStacking;
+
     private int mLastWidthSize = -1;
 
     public ButtonBarLayout(Context context, AttributeSet attrs) {
@@ -69,26 +72,33 @@
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
         if (mAllowStacking) {
             if (widthSize > mLastWidthSize && isStacked()) {
                 // We're being measured wider this time, try un-stacking.
                 setStacked(false);
             }
+
             mLastWidthSize = widthSize;
         }
+
         boolean needsRemeasure = false;
+
         // If we're not stacked, make sure the measure spec is AT_MOST rather
         // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
         // know to stack the buttons.
         final int initialWidthMeasureSpec;
         if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
             initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+
             // We'll need to remeasure again to fill excess space.
             needsRemeasure = true;
         } else {
             initialWidthMeasureSpec = widthMeasureSpec;
         }
+
         super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
+
         if (mAllowStacking && !isStacked()) {
             final boolean stack;
 
@@ -113,18 +123,54 @@
                 needsRemeasure = true;
             }
         }
+
         if (needsRemeasure) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         }
+
+        // Compute minimum height such that, when stacked, some portion of the
+        // second button is visible.
+        int minHeight = 0;
+        final int firstVisible = getNextVisibleChildIndex(0);
+        if (firstVisible >= 0) {
+            final View firstButton = getChildAt(firstVisible);
+            final LayoutParams firstParams = (LayoutParams) firstButton.getLayoutParams();
+            minHeight += getPaddingTop() + firstButton.getMeasuredHeight()
+                    + firstParams.topMargin + firstParams.bottomMargin;
+            if (isStacked()) {
+                final int secondVisible = getNextVisibleChildIndex(firstVisible + 1);
+                if (secondVisible >= 0) {
+                    minHeight += getChildAt(secondVisible).getPaddingTop()
+                            + PEEK_BUTTON_DP * getResources().getDisplayMetrics().density;
+                }
+            } else {
+                minHeight += getPaddingBottom();
+            }
+        }
+
+        if (ViewCompat.getMinimumHeight(this) != minHeight) {
+            setMinimumHeight(minHeight);
+        }
+    }
+
+    private int getNextVisibleChildIndex(int index) {
+        for (int i = index, count = getChildCount(); i < count; i++) {
+            if (getChildAt(i).getVisibility() == View.VISIBLE) {
+                return i;
+            }
+        }
+        return -1;
     }
 
     private void setStacked(boolean stacked) {
         setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
         setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM);
+
         final View spacer = findViewById(R.id.spacer);
         if (spacer != null) {
             spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
         }
+
         // Reverse the child order. This is specific to the Material button
         // bar's layout XML and will probably not generalize.
         final int childCount = getChildCount();
diff --git a/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java b/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java
index eacd17d..ce6f140 100644
--- a/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java
+++ b/v7/appcompat/src/android/support/v7/widget/LinearLayoutCompat.java
@@ -1690,6 +1690,16 @@
         }
     }
 
+    /**
+     * Returns the current gravity. See {@link android.view.Gravity}
+     *
+     * @return the current gravity.
+     * @see #setGravity
+     */
+    public int getGravity() {
+        return mGravity;
+    }
+
     public void setHorizontalGravity(int horizontalGravity) {
         final int gravity = horizontalGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK;
         if ((mGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) != gravity) {