NavigationView now supports app:actionLayout

The specified layout is inflated as an extra item in the menu row.

Bug: 22837324
Change-Id: Iaa0921c45395dd779429052019761e59d66c34be
diff --git a/design/res/layout/design_menu_item_action_area.xml b/design/res/layout/design_menu_item_action_area.xml
new file mode 100644
index 0000000..ba8141d
--- /dev/null
+++ b/design/res/layout/design_menu_item_action_area.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="wrap_content"
+             android:layout_height="match_parent"/>
diff --git a/design/res/layout/design_navigation_item.xml b/design/res/layout/design_navigation_item.xml
index 3fcd74a..14c1be3 100644
--- a/design/res/layout/design_navigation_item.xml
+++ b/design/res/layout/design_navigation_item.xml
@@ -19,8 +19,4 @@
         android:layout_width="match_parent"
         android:layout_height="?attr/listPreferredItemHeightSmall"
         android:paddingLeft="?attr/listPreferredItemPaddingLeft"
-        android:paddingRight="?attr/listPreferredItemPaddingRight"
-        android:drawablePadding="@dimen/design_navigation_icon_padding"
-        android:gravity="center_vertical|start"
-        android:maxLines="1"
-        android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
+        android:paddingRight="?attr/listPreferredItemPaddingRight"/>
diff --git a/design/res/layout/design_navigation_menu_item.xml b/design/res/layout/design_navigation_menu_item.xml
new file mode 100644
index 0000000..91104bb1
--- /dev/null
+++ b/design/res/layout/design_navigation_menu_item.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <CheckedTextView
+            android:id="@+id/design_menu_item_text"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:drawablePadding="@dimen/design_navigation_icon_padding"
+            android:gravity="center_vertical|start"
+            android:maxLines="1"
+            android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
+
+    <ViewStub
+            android:id="@+id/design_menu_item_action_area_stub"
+            android:inflatedId="@+id/design_menu_item_action_area"
+            android:layout="@layout/design_menu_item_action_area"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"/>
+
+</merge>
diff --git a/design/src/android/support/design/internal/NavigationMenuItemView.java b/design/src/android/support/design/internal/NavigationMenuItemView.java
index 7813163..1d819df 100644
--- a/design/src/android/support/design/internal/NavigationMenuItemView.java
+++ b/design/src/android/support/design/internal/NavigationMenuItemView.java
@@ -27,19 +27,30 @@
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.internal.view.menu.MenuItemImpl;
 import android.support.v7.internal.view.menu.MenuView;
+import android.support.v7.widget.LinearLayoutCompat;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.widget.TextView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.CheckedTextView;
+import android.widget.FrameLayout;
 
 /**
  * @hide
  */
-public class NavigationMenuItemView extends TextView implements MenuView.ItemView {
+public class NavigationMenuItemView extends LinearLayoutCompat implements MenuView.ItemView {
 
     private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};
 
-    private int mIconSize;
+    private final int mIconSize;
+
+    private final CheckedTextView mTextView;
+
+    private FrameLayout mActionArea;
+
     private MenuItemImpl mItemData;
+
     private ColorStateList mIconTintList;
 
     public NavigationMenuItemView(Context context) {
@@ -52,8 +63,14 @@
 
     public NavigationMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        setOrientation(HORIZONTAL);
+        LayoutInflater.from(context).inflate(R.layout.design_navigation_menu_item, this, true);
         mIconSize = context.getResources().getDimensionPixelSize(
                 R.dimen.design_navigation_icon_size);
+        mTextView = (CheckedTextView) findViewById(R.id.design_menu_item_text);
+        mTextView.setDuplicateParentStateEnabled(true);
+        // Prevent the action view from stealing the event on the item row.
+        setDescendantFocusability(FOCUS_BLOCK_DESCENDANTS);
     }
 
     @Override
@@ -71,6 +88,18 @@
         setEnabled(itemData.isEnabled());
         setTitle(itemData.getTitle());
         setIcon(itemData.getIcon());
+        setActionView(itemData.getActionView());
+    }
+
+    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) {
+            mActionArea.addView(actionView);
+        }
     }
 
     private StateListDrawable createDefaultBackground() {
@@ -91,7 +120,7 @@
 
     @Override
     public void setTitle(CharSequence title) {
-        setText(title);
+        mTextView.setText(title);
     }
 
     @Override
@@ -102,6 +131,7 @@
     @Override
     public void setChecked(boolean checked) {
         refreshDrawableState();
+        mTextView.setChecked(checked);
     }
 
     @Override
@@ -115,7 +145,7 @@
             icon.setBounds(0, 0, mIconSize, mIconSize);
             DrawableCompat.setTintList(icon, mIconTintList);
         }
-        TextViewCompat.setCompoundDrawablesRelative(this, icon, null, null, null);
+        TextViewCompat.setCompoundDrawablesRelative(mTextView, icon, null, null, null);
     }
 
     @Override
@@ -144,4 +174,13 @@
             setIcon(mItemData.getIcon());
         }
     }
+
+    public void setTextAppearance(Context context, int textAppearance) {
+        mTextView.setTextAppearance(context, textAppearance);
+    }
+
+    public void setTextColor(ColorStateList colors) {
+        mTextView.setTextColor(colors);
+    }
+
 }
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index e523c38..41b361b 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -266,6 +266,7 @@
 
         private static final String STATE_CHECKED_ITEM = "android:menu:checked";
 
+        private static final String STATE_ACTION_VIEWS = "android:menu:action_views";
         private static final int VIEW_TYPE_NORMAL = 0;
         private static final int VIEW_TYPE_SUBHEADER = 1;
         private static final int VIEW_TYPE_SEPARATOR = 2;
@@ -470,6 +471,18 @@
             if (mCheckedItem != null) {
                 state.putInt(STATE_CHECKED_ITEM, mCheckedItem.getItemId());
             }
+            // Store the states of the action views.
+            SparseArray<ParcelableSparseArray> actionViewStates = new SparseArray<>();
+            for (NavigationMenuItem navigationMenuItem : mItems) {
+                MenuItemImpl item = navigationMenuItem.getMenuItem();
+                View actionView = item != null ? item.getActionView() : null;
+                if (actionView != null) {
+                    ParcelableSparseArray container = new ParcelableSparseArray();
+                    actionView.saveHierarchyState(container);
+                    actionViewStates.put(item.getItemId(), container);
+                }
+            }
+            state.putSparseParcelableArray(STATE_ACTION_VIEWS, actionViewStates);
             return state;
         }
 
@@ -479,7 +492,7 @@
                 mUpdateSuspended = true;
                 for (NavigationMenuItem item : mItems) {
                     MenuItemImpl menuItem = item.getMenuItem();
-                    if (menuItem !=  null && menuItem.getItemId() == checkedItem) {
+                    if (menuItem != null && menuItem.getItemId() == checkedItem) {
                         setCheckedItem(menuItem);
                         break;
                     }
@@ -487,6 +500,16 @@
                 mUpdateSuspended = false;
                 prepareMenuItems();
             }
+            // Restore the states of the action views.
+            SparseArray<ParcelableSparseArray> actionViewStates = state
+                    .getSparseParcelableArray(STATE_ACTION_VIEWS);
+            for (NavigationMenuItem navigationMenuItem : mItems) {
+                MenuItemImpl item = navigationMenuItem.getMenuItem();
+                View actionView = item != null ? item.getActionView() : null;
+                if (actionView != null) {
+                    actionView.restoreHierarchyState(actionViewStates.get(item.getItemId()));
+                }
+            }
         }
 
         public void setUpdateSuspended(boolean updateSuspended) {
diff --git a/design/src/android/support/design/internal/ParcelableSparseArray.java b/design/src/android/support/design/internal/ParcelableSparseArray.java
new file mode 100644
index 0000000..74abcc2
--- /dev/null
+++ b/design/src/android/support/design/internal/ParcelableSparseArray.java
@@ -0,0 +1,76 @@
+/*
+ * 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.internal;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+/**
+ * @hide
+ */
+public class ParcelableSparseArray extends SparseArray<Parcelable> implements Parcelable {
+
+    public ParcelableSparseArray() {
+        super();
+    }
+
+    public ParcelableSparseArray(Parcel source) {
+        super();
+        int size = source.readInt();
+        int[] keys = new int[size];
+        source.readIntArray(keys);
+        Parcelable[] values = source.readParcelableArray(
+                ParcelableSparseArray.class.getClassLoader());
+        for (int i = 0; i < size; ++i) {
+            put(keys[i], values[i]);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        int size = size();
+        int[] keys = new int[size];
+        Parcelable[] values = new Parcelable[size];
+        for (int i = 0; i < size; ++i) {
+            keys[i] = keyAt(i);
+            values[i] = valueAt(i);
+        }
+        parcel.writeInt(size);
+        parcel.writeIntArray(keys);
+        parcel.writeParcelableArray(values, flags);
+    }
+
+    public static final Parcelable.Creator<ParcelableSparseArray> CREATOR
+            = new Creator<ParcelableSparseArray>() {
+        @Override
+        public ParcelableSparseArray createFromParcel(Parcel source) {
+            return new ParcelableSparseArray(source);
+        }
+
+        @Override
+        public ParcelableSparseArray[] newArray(int size) {
+            return new ParcelableSparseArray[size];
+        }
+    };
+
+}