NavigationView supports full-width custom views

Bug: 28992790
Change-Id: I5dacd8e859badabdc7f35c40d4cd82c017a868b9
diff --git a/design/res/drawable/navigation_empty_icon.xml b/design/res/drawable/navigation_empty_icon.xml
new file mode 100644
index 0000000..6cd938d
--- /dev/null
+++ b/design/res/drawable/navigation_empty_icon.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+-->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <size
+        android:width="@dimen/design_navigation_icon_size"
+        android:height="@dimen/design_navigation_icon_size" />
+</shape>
diff --git a/design/src/android/support/design/internal/NavigationMenuItemView.java b/design/src/android/support/design/internal/NavigationMenuItemView.java
index 1e2c928..1f70a04 100644
--- a/design/src/android/support/design/internal/NavigationMenuItemView.java
+++ b/design/src/android/support/design/internal/NavigationMenuItemView.java
@@ -23,6 +23,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
 import android.support.design.R;
+import android.support.v4.content.res.ResourcesCompat;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.view.menu.MenuItemImpl;
@@ -44,6 +45,8 @@
 
     private final int mIconSize;
 
+    private boolean mNeedsEmptyIcon;
+
     private final CheckedTextView mTextView;
 
     private FrameLayout mActionArea;
@@ -52,6 +55,8 @@
 
     private ColorStateList mIconTintList;
 
+    private Drawable mEmptyDrawable;
+
     public NavigationMenuItemView(Context context) {
         this(context, null);
     }
@@ -86,6 +91,32 @@
         setTitle(itemData.getTitle());
         setIcon(itemData.getIcon());
         setActionView(itemData.getActionView());
+        adjustAppearance();
+    }
+
+    private boolean shouldExpandActionArea() {
+        return mItemData.getTitle() == null &&
+                mItemData.getIcon() == null &&
+                mItemData.getActionView() != null;
+    }
+
+    private void adjustAppearance() {
+        if (shouldExpandActionArea()) {
+            // Expand the actionView area
+            mTextView.setVisibility(View.GONE);
+            if (mActionArea != null) {
+                LayoutParams params = (LayoutParams) mActionArea.getLayoutParams();
+                params.width = LayoutParams.MATCH_PARENT;
+                mActionArea.setLayoutParams(params);
+            }
+        } else {
+            mTextView.setVisibility(View.VISIBLE);
+            if (mActionArea != null) {
+                LayoutParams params = (LayoutParams) mActionArea.getLayoutParams();
+                params.width = LayoutParams.WRAP_CONTENT;
+                mActionArea.setLayoutParams(params);
+            }
+        }
     }
 
     public void recycle() {
@@ -96,12 +127,12 @@
     }
 
     private void setActionView(View actionView) {
-        if (mActionArea == null) {
-            mActionArea = (FrameLayout) ((ViewStub) findViewById(
-                    R.id.design_menu_item_action_area_stub)).inflate();
-        }
-        mActionArea.removeAllViews();
         if (actionView != null) {
+            if (mActionArea == null) {
+                mActionArea = (FrameLayout) ((ViewStub) findViewById(
+                        R.id.design_menu_item_action_area_stub)).inflate();
+            }
+            mActionArea.removeAllViews();
             mActionArea.addView(actionView);
         }
     }
@@ -150,6 +181,15 @@
             icon = DrawableCompat.wrap(state == null ? icon : state.newDrawable()).mutate();
             icon.setBounds(0, 0, mIconSize, mIconSize);
             DrawableCompat.setTintList(icon, mIconTintList);
+        } else if (mNeedsEmptyIcon) {
+            if (mEmptyDrawable == null) {
+                mEmptyDrawable = ResourcesCompat.getDrawable(getResources(),
+                        R.drawable.navigation_empty_icon, getContext().getTheme());
+                if (mEmptyDrawable != null) {
+                    mEmptyDrawable.setBounds(0, 0, mIconSize, mIconSize);
+                }
+            }
+            icon = mEmptyDrawable;
         }
         TextViewCompat.setCompoundDrawablesRelative(mTextView, icon, null, null, null);
     }
@@ -189,4 +229,8 @@
         mTextView.setTextColor(colors);
     }
 
+    public void setNeedsEmptyIcon(boolean needsEmptyIcon) {
+        mNeedsEmptyIcon = needsEmptyIcon;
+    }
+
 }
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index 609795f..273c36d 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -19,8 +19,6 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Parcelable;
@@ -37,7 +35,6 @@
 import android.support.v7.widget.RecyclerView;
 import android.util.SparseArray;
 import android.view.LayoutInflater;
-import android.view.MenuItem;
 import android.view.SubMenu;
 import android.view.View;
 import android.view.ViewGroup;
@@ -337,7 +334,6 @@
 
         private final ArrayList<NavigationMenuItem> mItems = new ArrayList<>();
         private MenuItemImpl mCheckedItem;
-        private ColorDrawable mTransparentIcon;
         private boolean mUpdateSuspended;
 
         NavigationMenuAdapter() {
@@ -402,6 +398,7 @@
                     itemView.setBackgroundDrawable(mItemBackground != null ?
                             mItemBackground.getConstantState().newDrawable() : null);
                     NavigationMenuTextItem item = (NavigationMenuTextItem) mItems.get(position);
+                    itemView.setNeedsEmptyIcon(item.needsEmptyIcon);
                     itemView.initialize(item.getMenuItem(), 0);
                     break;
                 }
@@ -502,10 +499,9 @@
                         currentGroupHasIcon = true;
                         appendTransparentIconIfMissing(currentGroupStart, mItems.size());
                     }
-                    if (currentGroupHasIcon && item.getIcon() == null) {
-                        item.setIcon(android.R.color.transparent);
-                    }
-                    mItems.add(new NavigationMenuTextItem(item));
+                    NavigationMenuTextItem textItem = new NavigationMenuTextItem(item);
+                    textItem.needsEmptyIcon = currentGroupHasIcon;
+                    mItems.add(textItem);
                     currentGroupId = groupId;
                 }
             }
@@ -515,13 +511,7 @@
         private void appendTransparentIconIfMissing(int startIndex, int endIndex) {
             for (int i = startIndex; i < endIndex; i++) {
                 NavigationMenuTextItem textItem = (NavigationMenuTextItem) mItems.get(i);
-                MenuItem item = textItem.getMenuItem();
-                if (item.getIcon() == null) {
-                    if (mTransparentIcon == null) {
-                        mTransparentIcon = new ColorDrawable(Color.TRANSPARENT);
-                    }
-                    item.setIcon(mTransparentIcon);
-                }
+                textItem.needsEmptyIcon = true;
             }
         }
 
@@ -607,6 +597,8 @@
 
         private final MenuItemImpl mMenuItem;
 
+        boolean needsEmptyIcon;
+
         private NavigationMenuTextItem(MenuItemImpl item) {
             mMenuItem = item;
         }
diff --git a/design/tests/res/layout/action_layout_custom.xml b/design/tests/res/layout/action_layout_custom.xml
new file mode 100644
index 0000000..10a3268
--- /dev/null
+++ b/design/tests/res/layout/action_layout_custom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/lilac_default"
+    android:text="@string/navigate_custom"/>
diff --git a/design/tests/res/menu/navigation_view_content.xml b/design/tests/res/menu/navigation_view_content.xml
index 758334f..a8fb464 100644
--- a/design/tests/res/menu/navigation_view_content.xml
+++ b/design/tests/res/menu/navigation_view_content.xml
@@ -29,5 +29,7 @@
               app:actionLayout="@layout/action_layout" />
         <item android:id="@+id/destination_settings"
               android:title="@string/navigate_settings" />
+        <item android:id="@+id/desitination_custom"
+              app:actionLayout="@layout/action_layout_custom" />
     </group>
 </menu>
diff --git a/design/tests/res/values/strings.xml b/design/tests/res/values/strings.xml
index a431e2b..380cbe3 100644
--- a/design/tests/res/values/strings.xml
+++ b/design/tests/res/values/strings.xml
@@ -19,6 +19,7 @@
     <string name="navigate_home">Home</string>
     <string name="navigate_profile">Profile</string>
     <string name="navigate_people">People</string>
+    <string name="navigate_custom">Custom</string>
     <string name="navigate_settings">Settings</string>
 
     <string name="snackbar_text">This is a test message</string>
diff --git a/design/tests/src/android/support/design/widget/NavigationViewTest.java b/design/tests/src/android/support/design/widget/NavigationViewTest.java
index 2abce52..97c43c3 100755
--- a/design/tests/src/android/support/design/widget/NavigationViewTest.java
+++ b/design/tests/src/android/support/design/widget/NavigationViewTest.java
@@ -30,6 +30,8 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.widget.TextView;
+
 import org.hamcrest.Matcher;
 import org.junit.Before;
 import org.junit.Test;
@@ -90,7 +92,7 @@
         // Check the contents of the Menu object
         final Menu menu = mNavigationView.getMenu();
         assertNotNull("Menu should not be null", menu);
-        assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length,
+        assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length + 1,
                 menu.size());
         for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
             final MenuItem currItem = menu.getItem(i);
@@ -549,5 +551,16 @@
         // makes our matcher actually run. If for some reason NavigationView fails to inflate and
         // display our SwitchCompat action layout, the next line will fail in the matcher pass.
         onView(menuItemMatcher).perform(click());
+
+        // Check that the full custom view is displayed without title and icon.
+        final Resources res = mActivityTestRule.getActivity().getResources();
+        Matcher customItemMatcher = allOf(
+                isDescendantOfA(withId(R.id.start_drawer)),
+                isChildOfA(isAssignableFrom(RecyclerView.class)),
+                hasDescendant(withText(res.getString(R.string.navigate_custom))),
+                hasDescendant(allOf(
+                        isAssignableFrom(TextView.class),
+                        withEffectiveVisibility(Visibility.GONE))));
+        onView(customItemMatcher).perform(click());
     }
 }
diff --git a/samples/SupportDesignDemos/res/layout/action_layout_custom.xml b/samples/SupportDesignDemos/res/layout/action_layout_custom.xml
new file mode 100644
index 0000000..8ed1c8c
--- /dev/null
+++ b/samples/SupportDesignDemos/res/layout/action_layout_custom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<TextView
+    android:id="@+id/text"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="?attr/colorPrimary"
+    android:gravity="center"
+    android:text="@string/navigation_sub_item_3"
+    android:textAppearance="@style/TextAppearance.AppCompat.Widget.Button.Inverse"/>
diff --git a/samples/SupportDesignDemos/res/menu/navigation.xml b/samples/SupportDesignDemos/res/menu/navigation.xml
index 6425b18..de17967 100644
--- a/samples/SupportDesignDemos/res/menu/navigation.xml
+++ b/samples/SupportDesignDemos/res/menu/navigation.xml
@@ -57,6 +57,9 @@
                     android:id="@+id/navigation_sub_item_2"
                     android:icon="@drawable/ic_android"
                     android:title="@string/navigation_sub_item_2"/>
+            <item
+                    android:id="@+id/navigation_sub_item_3"
+                    app:actionLayout="@layout/action_layout_custom"/>
         </menu>
     </item>
 
diff --git a/samples/SupportDesignDemos/res/values/strings.xml b/samples/SupportDesignDemos/res/values/strings.xml
index 8f21310..da855f8 100644
--- a/samples/SupportDesignDemos/res/values/strings.xml
+++ b/samples/SupportDesignDemos/res/values/strings.xml
@@ -51,6 +51,7 @@
     <string name="navigation_subheader">Subheader</string>
     <string name="navigation_sub_item_1">Subitem 1</string>
     <string name="navigation_sub_item_2">Subitem 2</string>
+    <string name="navigation_sub_item_3">Subitme 3</string>
 
     <string name="tabs_fixed">Fixed</string>
     <string name="tabs_scrollable">Scrollable</string>
diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/NavigationViewUsageBase.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/NavigationViewUsageBase.java
index 49080c0..fc3fefb 100644
--- a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/NavigationViewUsageBase.java
+++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/NavigationViewUsageBase.java
@@ -65,6 +65,9 @@
             case R.id.navigation_sub_item_2:
                 showToast(R.string.navigation_sub_item_2);
                 return true;
+            case R.id.navigation_sub_item_3:
+                showToast(R.string.navigation_sub_item_3);
+                return true;
             case R.id.navigation_with_icon:
                 showToast(R.string.navigation_item_with_icon);
                 return true;