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 android.support.v4.view;
18 
19 import android.support.v4.internal.view.SupportMenuItem;
20 import android.util.Log;
21 import android.view.MenuItem;
22 import android.view.View;
23 
24 /**
25  * Helper for accessing features in {@link android.view.MenuItem}
26  * introduced after API level 4 in a backwards compatible fashion.
27  * <p class="note"><strong>Note:</strong> You cannot get an instance of this class. Instead,
28  * it provides <em>static</em> methods that correspond to the methods in {@link
29  * android.view.MenuItem}, but take a {@link android.view.MenuItem} object as an additional
30  * argument.</p>
31  */
32 public final class MenuItemCompat {
33     private static final String TAG = "MenuItemCompat";
34 
35     /**
36      * Never show this item as a button in an Action Bar.
37      */
38     public static final int SHOW_AS_ACTION_NEVER = 0;
39 
40     /**
41      * Show this item as a button in an Action Bar if the system
42      * decides there is room for it.
43      */
44     public static final int SHOW_AS_ACTION_IF_ROOM = 1;
45 
46     /**
47      * Always show this item as a button in an Action Bar. Use sparingly!
48      * If too many items are set to always show in the Action Bar it can
49      * crowd the Action Bar and degrade the user experience on devices with
50      * smaller screens. A good rule of thumb is to have no more than 2
51      * items set to always show at a time.
52      */
53     public static final int SHOW_AS_ACTION_ALWAYS = 2;
54 
55     /**
56      * When this item is in the action bar, always show it with a
57      * text label even if it also has an icon specified.
58      */
59     public static final int SHOW_AS_ACTION_WITH_TEXT = 4;
60 
61     /**
62      * This item's action view collapses to a normal menu item.
63      * When expanded, the action view temporarily takes over
64      * a larger segment of its container.
65      */
66     public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8;
67 
68     /**
69      * Interface for the full API.
70      */
71     interface MenuVersionImpl {
setShowAsAction(MenuItem item, int actionEnum)72         void setShowAsAction(MenuItem item, int actionEnum);
setActionView(MenuItem item, View view)73         MenuItem setActionView(MenuItem item, View view);
setActionView(MenuItem item, int resId)74         MenuItem setActionView(MenuItem item, int resId);
getActionView(MenuItem item)75         View getActionView(MenuItem item);
expandActionView(MenuItem item)76         boolean expandActionView(MenuItem item);
collapseActionView(MenuItem item)77         boolean collapseActionView(MenuItem item);
isActionViewExpanded(MenuItem item)78         boolean isActionViewExpanded(MenuItem item);
setOnActionExpandListener(MenuItem item, OnActionExpandListener listener)79         MenuItem setOnActionExpandListener(MenuItem item, OnActionExpandListener listener);
80     }
81 
82     /**
83      * Interface definition for a callback to be invoked when a menu item marked with {@link
84      * #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW} is expanded or collapsed.
85      *
86      * @see #expandActionView(android.view.MenuItem)
87      * @see #collapseActionView(android.view.MenuItem)
88      * @see #setShowAsAction(android.view.MenuItem, int)
89      */
90     public interface OnActionExpandListener {
91 
92         /**
93          * Called when a menu item with {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
94          * is expanded.
95          *
96          * @param item Item that was expanded
97          * @return true if the item should expand, false if expansion should be suppressed.
98          */
onMenuItemActionExpand(MenuItem item)99         public boolean onMenuItemActionExpand(MenuItem item);
100 
101         /**
102          * Called when a menu item with {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}
103          * is collapsed.
104          *
105          * @param item Item that was collapsed
106          * @return true if the item should collapse, false if collapsing should be suppressed.
107          */
onMenuItemActionCollapse(MenuItem item)108         public boolean onMenuItemActionCollapse(MenuItem item);
109     }
110 
111     /**
112      * Interface implementation that doesn't use anything about v4 APIs.
113      */
114     static class BaseMenuVersionImpl implements MenuVersionImpl {
115         @Override
setShowAsAction(MenuItem item, int actionEnum)116         public void setShowAsAction(MenuItem item, int actionEnum) {
117         }
118 
119         @Override
setActionView(MenuItem item, View view)120         public MenuItem setActionView(MenuItem item, View view) {
121             return item;
122         }
123 
124         @Override
setActionView(MenuItem item, int resId)125         public MenuItem setActionView(MenuItem item, int resId) {
126             return item;
127         }
128 
129         @Override
getActionView(MenuItem item)130         public View getActionView(MenuItem item) {
131             return null;
132         }
133 
134         @Override
expandActionView(MenuItem item)135         public boolean expandActionView(MenuItem item) {
136             return false;
137         }
138 
139         @Override
collapseActionView(MenuItem item)140         public boolean collapseActionView(MenuItem item) {
141             return false;
142         }
143 
144         @Override
isActionViewExpanded(MenuItem item)145         public boolean isActionViewExpanded(MenuItem item) {
146             return false;
147         }
148 
149         @Override
setOnActionExpandListener(MenuItem item, OnActionExpandListener listener)150         public MenuItem setOnActionExpandListener(MenuItem item, OnActionExpandListener listener) {
151             return item;
152         }
153     }
154 
155     /**
156      * Interface implementation for devices with at least v11 APIs.
157      */
158     static class HoneycombMenuVersionImpl implements MenuVersionImpl {
159         @Override
setShowAsAction(MenuItem item, int actionEnum)160         public void setShowAsAction(MenuItem item, int actionEnum) {
161             MenuItemCompatHoneycomb.setShowAsAction(item, actionEnum);
162         }
163 
164         @Override
setActionView(MenuItem item, View view)165         public MenuItem setActionView(MenuItem item, View view) {
166             return MenuItemCompatHoneycomb.setActionView(item, view);
167         }
168 
169         @Override
setActionView(MenuItem item, int resId)170         public MenuItem setActionView(MenuItem item, int resId) {
171             return MenuItemCompatHoneycomb.setActionView(item, resId);
172         }
173 
174         @Override
getActionView(MenuItem item)175         public View getActionView(MenuItem item) {
176             return MenuItemCompatHoneycomb.getActionView(item);
177         }
178 
179         @Override
expandActionView(MenuItem item)180         public boolean expandActionView(MenuItem item) {
181             return false;
182         }
183 
184         @Override
collapseActionView(MenuItem item)185         public boolean collapseActionView(MenuItem item) {
186             return false;
187         }
188 
189         @Override
isActionViewExpanded(MenuItem item)190         public boolean isActionViewExpanded(MenuItem item) {
191             return false;
192         }
193 
194         @Override
setOnActionExpandListener(MenuItem item, OnActionExpandListener listener)195         public MenuItem setOnActionExpandListener(MenuItem item, OnActionExpandListener listener) {
196             return item;
197         }
198     }
199 
200     static class IcsMenuVersionImpl extends HoneycombMenuVersionImpl {
201         @Override
expandActionView(MenuItem item)202         public boolean expandActionView(MenuItem item) {
203             return MenuItemCompatIcs.expandActionView(item);
204         }
205 
206         @Override
collapseActionView(MenuItem item)207         public boolean collapseActionView(MenuItem item) {
208             return MenuItemCompatIcs.collapseActionView(item);
209         }
210 
211         @Override
isActionViewExpanded(MenuItem item)212         public boolean isActionViewExpanded(MenuItem item) {
213             return MenuItemCompatIcs.isActionViewExpanded(item);
214         }
215 
216         @Override
setOnActionExpandListener(MenuItem item, final OnActionExpandListener listener)217         public MenuItem setOnActionExpandListener(MenuItem item,
218                 final OnActionExpandListener listener) {
219             if (listener == null) {
220                 return MenuItemCompatIcs.setOnActionExpandListener(item, null);
221             }
222             /*
223              * MenuItemCompatIcs is a dependency of this segment of the support lib
224              * but not the other way around, so we need to take an extra step here to proxy
225              * to the right types.
226              */
227             return MenuItemCompatIcs.setOnActionExpandListener(item,
228                     new MenuItemCompatIcs.SupportActionExpandProxy() {
229                 @Override
230                 public boolean onMenuItemActionExpand(MenuItem item) {
231                     return listener.onMenuItemActionExpand(item);
232                 }
233 
234                 @Override
235                 public boolean onMenuItemActionCollapse(MenuItem item) {
236                     return listener.onMenuItemActionCollapse(item);
237                 }
238             });
239         }
240     }
241 
242     /**
243      * Select the correct implementation to use for the current platform.
244      */
245     static final MenuVersionImpl IMPL;
246     static {
247         final int version = android.os.Build.VERSION.SDK_INT;
248         if (version >= 14) {
249             IMPL = new IcsMenuVersionImpl();
250         } else if (version >= 11) {
251             IMPL = new HoneycombMenuVersionImpl();
252         } else {
253             IMPL = new BaseMenuVersionImpl();
254         }
255     }
256 
257     // -------------------------------------------------------------------
258 
259     /**
260      * Sets how this item should display in the presence of a compatible Action Bar. If the given
261      * item is compatible, this will call the item's supported implementation of
262      * {@link MenuItem#setShowAsAction(int)}.
263      *
264      * @param item - the item to change
265      * @param actionEnum - How the item should display.
266      */
267     public static void setShowAsAction(MenuItem item, int actionEnum) {
268         if (item instanceof SupportMenuItem) {
269             ((SupportMenuItem) item).setShowAsAction(actionEnum);
270         } else {
271             IMPL.setShowAsAction(item, actionEnum);
272         }
273     }
274 
275     /**
276      * Set an action view for this menu item. An action view will be displayed in place
277      * of an automatically generated menu item element in the UI when this item is shown
278      * as an action within a parent.
279      *
280      * @param item the item to change
281      * @param view View to use for presenting this item to the user.
282      * @return This Item so additional setters can be called.
283      *
284      * @see #setShowAsAction(MenuItem, int)
285      */
286     public static MenuItem setActionView(MenuItem item, View view) {
287         if (item instanceof SupportMenuItem) {
288             return ((SupportMenuItem) item).setActionView(view);
289         }
290         return IMPL.setActionView(item, view);
291     }
292 
293     /**
294      * Set an action view for this menu item. An action view will be displayed in place
295      * of an automatically generated menu item element in the UI when this item is shown
296      * as an action within a parent.
297      * <p>
298      *   <strong>Note:</strong> Setting an action view overrides the action provider
299      *           set via {@link #setActionProvider(MenuItem, ActionProvider)}.
300      * </p>
301      *
302      * @param item the item to change
303      * @param resId Layout resource to use for presenting this item to the user.
304      * @return This Item so additional setters can be called.
305      *
306      * @see #setShowAsAction(MenuItem, int)
307      */
308     public static MenuItem setActionView(MenuItem item, int resId) {
309         if (item instanceof SupportMenuItem) {
310             return ((SupportMenuItem) item).setActionView(resId);
311         }
312         return IMPL.setActionView(item, resId);
313     }
314 
315     /**
316      * Returns the currently set action view for this menu item.
317      *
318      * @param item the item to query
319      * @return This item's action view
320      */
321     public static View getActionView(MenuItem item) {
322         if (item instanceof SupportMenuItem) {
323             return ((SupportMenuItem) item).getActionView();
324         }
325         return IMPL.getActionView(item);
326     }
327 
328     /**
329      * Sets the {@link ActionProvider} responsible for creating an action view if
330      * the item is placed on the action bar. The provider also provides a default
331      * action invoked if the item is placed in the overflow menu.
332      * <p>
333      *   <strong>Note:</strong> Setting an action provider overrides the action view
334      *           set via {@link #setActionView(MenuItem, View)}.
335      * </p>
336      *
337      * @param item item to change
338      * @param provider The action provider.
339      * @return This Item so additional setters can be called.
340      *
341      * @see ActionProvider
342      */
343     public static MenuItem setActionProvider(MenuItem item, ActionProvider provider) {
344         if (item instanceof SupportMenuItem) {
345             return ((SupportMenuItem) item).setSupportActionProvider(provider);
346         }
347         // TODO Wrap the support ActionProvider and assign it
348         Log.w(TAG, "setActionProvider: item does not implement SupportMenuItem; ignoring");
349         return item;
350     }
351 
352     /**
353      * Gets the {@link ActionProvider}.
354      *
355      * @return The action provider.
356      *
357      * @see ActionProvider
358      * @see #setActionProvider(MenuItem, ActionProvider)
359      */
360     public static ActionProvider getActionProvider(MenuItem item) {
361         if (item instanceof SupportMenuItem) {
362             return ((SupportMenuItem) item).getSupportActionProvider();
363         }
364 
365         // TODO Wrap the framework ActionProvider and return it
366         Log.w(TAG, "getActionProvider: item does not implement SupportMenuItem; returning null");
367         return null;
368     }
369 
370     /**
371      * Expand the action view associated with this menu item.
372      * The menu item must have an action view set, as well as
373      * the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
374      * If a listener has been set using
375      * {@link #setOnActionExpandListener(MenuItem, OnActionExpandListener)}
376      * it will have its {@link OnActionExpandListener#onMenuItemActionExpand(MenuItem)}
377      * method invoked. The listener may return false from this method to prevent expanding
378      * the action view.
379      *
380      * @return true if the action view was expanded, false otherwise.
381      */
382     public static boolean expandActionView(MenuItem item) {
383         if (item instanceof SupportMenuItem) {
384             return ((SupportMenuItem) item).expandActionView();
385         }
386         return IMPL.expandActionView(item);
387     }
388 
389     /**
390      * Collapse the action view associated with this menu item. The menu item must have an action
391      * view set, as well as the showAsAction flag {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}. If a
392      * listener has been set using {@link #setOnActionExpandListener(MenuItem,
393      * android.support.v4.view.MenuItemCompat.OnActionExpandListener)}
394      * it will have its {@link
395      * android.support.v4.view.MenuItemCompat.OnActionExpandListener#onMenuItemActionCollapse(MenuItem)}
396      * method invoked. The listener may return false from this method to prevent collapsing
397      * the action view.
398      *
399      * @return true if the action view was collapsed, false otherwise.
400      */
401     public static boolean collapseActionView(MenuItem item) {
402         if (item instanceof SupportMenuItem) {
403             return ((SupportMenuItem) item).collapseActionView();
404         }
405         return IMPL.collapseActionView(item);
406     }
407 
408     /**
409      * Returns true if this menu item's action view has been expanded.
410      *
411      * @return true if the item's action view is expanded, false otherwise.
412      * @see #expandActionView(MenuItem)
413      * @see #collapseActionView(MenuItem)
414      * @see #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
415      * @see android.support.v4.view.MenuItemCompat.OnActionExpandListener
416      */
417     public static boolean isActionViewExpanded(MenuItem item) {
418         if (item instanceof SupportMenuItem) {
419             return ((SupportMenuItem) item).isActionViewExpanded();
420         }
421         return IMPL.isActionViewExpanded(item);
422     }
423 
424     /**
425      * Set an {@link OnActionExpandListener} on this menu
426      * item to be notified when the associated action view is expanded or collapsed.
427      * The menu item must be configured to expand or collapse its action view using the flag
428      * {@link #SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW}.
429      *
430      * @param listener Listener that will respond to expand/collapse events
431      * @return This menu item instance for call chaining
432      */
433     public static MenuItem setOnActionExpandListener(MenuItem item,
434             OnActionExpandListener listener) {
435         if (item instanceof SupportMenuItem) {
436             return ((SupportMenuItem) item).setSupportOnActionExpandListener(listener);
437         }
438         return IMPL.setOnActionExpandListener(item, listener);
439     }
440 
441     private MenuItemCompat() {}
442 }
443