1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.internal.view.menu;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.os.Bundle;
23 import android.os.Parcelable;
24 import android.util.SparseArray;
25 import android.view.ContextThemeWrapper;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.AdapterView;
30 import android.widget.BaseAdapter;
31 import android.widget.ListAdapter;
32 
33 import java.util.ArrayList;
34 
35 /**
36  * MenuPresenter for list-style menus.
37  */
38 public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
39     private static final String TAG = "ListMenuPresenter";
40 
41     Context mContext;
42     LayoutInflater mInflater;
43     MenuBuilder mMenu;
44 
45     ExpandedMenuView mMenuView;
46 
47     private int mItemIndexOffset;
48     int mThemeRes;
49     int mItemLayoutRes;
50 
51     private Callback mCallback;
52     MenuAdapter mAdapter;
53 
54     private int mId;
55 
56     public static final String VIEWS_TAG = "android:menu:list";
57 
58     /**
59      * Construct a new ListMenuPresenter.
60      * @param context Context to use for theming. This will supersede the context provided
61      *                to initForMenu when this presenter is added.
62      * @param itemLayoutRes Layout resource for individual item views.
63      */
ListMenuPresenter(Context context, int itemLayoutRes)64     public ListMenuPresenter(Context context, int itemLayoutRes) {
65         this(itemLayoutRes, 0);
66         mContext = context;
67         mInflater = LayoutInflater.from(mContext);
68     }
69 
70     /**
71      * Construct a new ListMenuPresenter.
72      * @param itemLayoutRes Layout resource for individual item views.
73      * @param themeRes Resource ID of a theme to use for views.
74      */
ListMenuPresenter(int itemLayoutRes, int themeRes)75     public ListMenuPresenter(int itemLayoutRes, int themeRes) {
76         mItemLayoutRes = itemLayoutRes;
77         mThemeRes = themeRes;
78     }
79 
80     @Override
initForMenu(@onNull Context context, @Nullable MenuBuilder menu)81     public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
82         if (mThemeRes != 0) {
83             mContext = new ContextThemeWrapper(context, mThemeRes);
84             mInflater = LayoutInflater.from(mContext);
85         } else if (mContext != null) {
86             mContext = context;
87             if (mInflater == null) {
88                 mInflater = LayoutInflater.from(mContext);
89             }
90         }
91         mMenu = menu;
92         if (mAdapter != null) {
93             mAdapter.notifyDataSetChanged();
94         }
95     }
96 
97     @Override
getMenuView(ViewGroup root)98     public MenuView getMenuView(ViewGroup root) {
99         if (mMenuView == null) {
100             mMenuView = (ExpandedMenuView) mInflater.inflate(
101                     com.android.internal.R.layout.expanded_menu_layout, root, false);
102             if (mAdapter == null) {
103                 mAdapter = new MenuAdapter();
104             }
105             mMenuView.setAdapter(mAdapter);
106             mMenuView.setOnItemClickListener(this);
107         }
108         return mMenuView;
109     }
110 
111     /**
112      * Call this instead of getMenuView if you want to manage your own ListView.
113      * For proper operation, the ListView hosting this adapter should add
114      * this presenter as an OnItemClickListener.
115      *
116      * @return A ListAdapter containing the items in the menu.
117      */
getAdapter()118     public ListAdapter getAdapter() {
119         if (mAdapter == null) {
120             mAdapter = new MenuAdapter();
121         }
122         return mAdapter;
123     }
124 
125     @Override
updateMenuView(boolean cleared)126     public void updateMenuView(boolean cleared) {
127         if (mAdapter != null) mAdapter.notifyDataSetChanged();
128     }
129 
130     @Override
setCallback(Callback cb)131     public void setCallback(Callback cb) {
132         mCallback = cb;
133     }
134 
135     @Override
onSubMenuSelected(SubMenuBuilder subMenu)136     public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
137         if (!subMenu.hasVisibleItems()) return false;
138 
139         // The window manager will give us a token.
140         new MenuDialogHelper(subMenu).show(null);
141         if (mCallback != null) {
142             mCallback.onOpenSubMenu(subMenu);
143         }
144         return true;
145     }
146 
147     @Override
onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing)148     public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
149         if (mCallback != null) {
150             mCallback.onCloseMenu(menu, allMenusAreClosing);
151         }
152     }
153 
getItemIndexOffset()154     int getItemIndexOffset() {
155         return mItemIndexOffset;
156     }
157 
setItemIndexOffset(int offset)158     public void setItemIndexOffset(int offset) {
159         mItemIndexOffset = offset;
160         if (mMenuView != null) {
161             updateMenuView(false);
162         }
163     }
164 
165     @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)166     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
167         mMenu.performItemAction(mAdapter.getItem(position), this, 0);
168     }
169 
170     @Override
flagActionItems()171     public boolean flagActionItems() {
172         return false;
173     }
174 
expandItemActionView(MenuBuilder menu, MenuItemImpl item)175     public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
176         return false;
177     }
178 
collapseItemActionView(MenuBuilder menu, MenuItemImpl item)179     public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
180         return false;
181     }
182 
saveHierarchyState(Bundle outState)183     public void saveHierarchyState(Bundle outState) {
184         SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
185         if (mMenuView != null) {
186             ((View) mMenuView).saveHierarchyState(viewStates);
187         }
188         outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
189     }
190 
restoreHierarchyState(Bundle inState)191     public void restoreHierarchyState(Bundle inState) {
192         SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
193         if (viewStates != null) {
194             ((View) mMenuView).restoreHierarchyState(viewStates);
195         }
196     }
197 
setId(int id)198     public void setId(int id) {
199         mId = id;
200     }
201 
202     @Override
getId()203     public int getId() {
204         return mId;
205     }
206 
207     @Override
onSaveInstanceState()208     public Parcelable onSaveInstanceState() {
209         if (mMenuView == null) {
210             return null;
211         }
212 
213         Bundle state = new Bundle();
214         saveHierarchyState(state);
215         return state;
216     }
217 
218     @Override
onRestoreInstanceState(Parcelable state)219     public void onRestoreInstanceState(Parcelable state) {
220         restoreHierarchyState((Bundle) state);
221     }
222 
223     private class MenuAdapter extends BaseAdapter {
224         private int mExpandedIndex = -1;
225 
MenuAdapter()226         public MenuAdapter() {
227             findExpandedIndex();
228         }
229 
getCount()230         public int getCount() {
231             ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
232             int count = items.size() - mItemIndexOffset;
233             if (mExpandedIndex < 0) {
234                 return count;
235             }
236             return count - 1;
237         }
238 
getItem(int position)239         public MenuItemImpl getItem(int position) {
240             ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
241             position += mItemIndexOffset;
242             if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
243                 position++;
244             }
245             return items.get(position);
246         }
247 
getItemId(int position)248         public long getItemId(int position) {
249             // Since a menu item's ID is optional, we'll use the position as an
250             // ID for the item in the AdapterView
251             return position;
252         }
253 
getView(int position, View convertView, ViewGroup parent)254         public View getView(int position, View convertView, ViewGroup parent) {
255             if (convertView == null) {
256                 convertView = mInflater.inflate(mItemLayoutRes, parent, false);
257             }
258 
259             MenuView.ItemView itemView = (MenuView.ItemView) convertView;
260             itemView.initialize(getItem(position), 0);
261             return convertView;
262         }
263 
findExpandedIndex()264         void findExpandedIndex() {
265             final MenuItemImpl expandedItem = mMenu.getExpandedItem();
266             if (expandedItem != null) {
267                 final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
268                 final int count = items.size();
269                 for (int i = 0; i < count; i++) {
270                     final MenuItemImpl item = items.get(i);
271                     if (item == expandedItem) {
272                         mExpandedIndex = i;
273                         return;
274                     }
275                 }
276             }
277             mExpandedIndex = -1;
278         }
279 
280         @Override
notifyDataSetChanged()281         public void notifyDataSetChanged() {
282             findExpandedIndex();
283             super.notifyDataSetChanged();
284         }
285     }
286 }
287