• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 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 com.android.internal.view.menu.MenuView.ItemView;
20 
21 import android.content.ActivityNotFoundException;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.drawable.Drawable;
25 import android.util.Log;
26 import android.view.ActionProvider;
27 import android.view.ContextMenu.ContextMenuInfo;
28 import android.view.LayoutInflater;
29 import android.view.MenuItem;
30 import android.view.SubMenu;
31 import android.view.View;
32 import android.view.ViewDebug;
33 import android.widget.LinearLayout;
34 
35 /**
36  * @hide
37  */
38 public final class MenuItemImpl implements MenuItem {
39     private static final String TAG = "MenuItemImpl";
40 
41     private static final int SHOW_AS_ACTION_MASK = SHOW_AS_ACTION_NEVER |
42             SHOW_AS_ACTION_IF_ROOM |
43             SHOW_AS_ACTION_ALWAYS;
44 
45     private final int mId;
46     private final int mGroup;
47     private final int mCategoryOrder;
48     private final int mOrdering;
49     private CharSequence mTitle;
50     private CharSequence mTitleCondensed;
51     private Intent mIntent;
52     private char mShortcutNumericChar;
53     private char mShortcutAlphabeticChar;
54 
55     /** The icon's drawable which is only created as needed */
56     private Drawable mIconDrawable;
57     /**
58      * The icon's resource ID which is used to get the Drawable when it is
59      * needed (if the Drawable isn't already obtained--only one of the two is
60      * needed).
61      */
62     private int mIconResId = NO_ICON;
63 
64     /** The menu to which this item belongs */
65     private MenuBuilder mMenu;
66     /** If this item should launch a sub menu, this is the sub menu to launch */
67     private SubMenuBuilder mSubMenu;
68 
69     private Runnable mItemCallback;
70     private MenuItem.OnMenuItemClickListener mClickListener;
71 
72     private int mFlags = ENABLED;
73     private static final int CHECKABLE      = 0x00000001;
74     private static final int CHECKED        = 0x00000002;
75     private static final int EXCLUSIVE      = 0x00000004;
76     private static final int HIDDEN         = 0x00000008;
77     private static final int ENABLED        = 0x00000010;
78     private static final int IS_ACTION      = 0x00000020;
79 
80     private int mShowAsAction = SHOW_AS_ACTION_NEVER;
81 
82     private View mActionView;
83     private ActionProvider mActionProvider;
84     private OnActionExpandListener mOnActionExpandListener;
85     private boolean mIsActionViewExpanded = false;
86 
87     /** Used for the icon resource ID if this item does not have an icon */
88     static final int NO_ICON = 0;
89 
90     /**
91      * Current use case is for context menu: Extra information linked to the
92      * View that added this item to the context menu.
93      */
94     private ContextMenuInfo mMenuInfo;
95 
96     private static String sPrependShortcutLabel;
97     private static String sEnterShortcutLabel;
98     private static String sDeleteShortcutLabel;
99     private static String sSpaceShortcutLabel;
100 
101 
102     /**
103      * Instantiates this menu item.
104      *
105      * @param menu
106      * @param group Item ordering grouping control. The item will be added after
107      *            all other items whose order is <= this number, and before any
108      *            that are larger than it. This can also be used to define
109      *            groups of items for batch state changes. Normally use 0.
110      * @param id Unique item ID. Use 0 if you do not need a unique ID.
111      * @param categoryOrder The ordering for this item.
112      * @param title The text to display for the item.
113      */
MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering, CharSequence title, int showAsAction)114     MenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering,
115             CharSequence title, int showAsAction) {
116 
117         if (sPrependShortcutLabel == null) {
118             // This is instantiated from the UI thread, so no chance of sync issues
119             sPrependShortcutLabel = menu.getContext().getResources().getString(
120                     com.android.internal.R.string.prepend_shortcut_label);
121             sEnterShortcutLabel = menu.getContext().getResources().getString(
122                     com.android.internal.R.string.menu_enter_shortcut_label);
123             sDeleteShortcutLabel = menu.getContext().getResources().getString(
124                     com.android.internal.R.string.menu_delete_shortcut_label);
125             sSpaceShortcutLabel = menu.getContext().getResources().getString(
126                     com.android.internal.R.string.menu_space_shortcut_label);
127         }
128 
129         mMenu = menu;
130         mId = id;
131         mGroup = group;
132         mCategoryOrder = categoryOrder;
133         mOrdering = ordering;
134         mTitle = title;
135         mShowAsAction = showAsAction;
136     }
137 
138     /**
139      * Invokes the item by calling various listeners or callbacks.
140      *
141      * @return true if the invocation was handled, false otherwise
142      */
invoke()143     public boolean invoke() {
144         if (mClickListener != null &&
145             mClickListener.onMenuItemClick(this)) {
146             return true;
147         }
148 
149         if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
150             return true;
151         }
152 
153         if (mItemCallback != null) {
154             mItemCallback.run();
155             return true;
156         }
157 
158         if (mIntent != null) {
159             try {
160                 mMenu.getContext().startActivity(mIntent);
161                 return true;
162             } catch (ActivityNotFoundException e) {
163                 Log.e(TAG, "Can't find activity to handle intent; ignoring", e);
164             }
165         }
166 
167         if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) {
168             return true;
169         }
170 
171         return false;
172     }
173 
isEnabled()174     public boolean isEnabled() {
175         return (mFlags & ENABLED) != 0;
176     }
177 
setEnabled(boolean enabled)178     public MenuItem setEnabled(boolean enabled) {
179         if (enabled) {
180             mFlags |= ENABLED;
181         } else {
182             mFlags &= ~ENABLED;
183         }
184 
185         mMenu.onItemsChanged(false);
186 
187         return this;
188     }
189 
getGroupId()190     public int getGroupId() {
191         return mGroup;
192     }
193 
194     @ViewDebug.CapturedViewProperty
getItemId()195     public int getItemId() {
196         return mId;
197     }
198 
getOrder()199     public int getOrder() {
200         return mCategoryOrder;
201     }
202 
getOrdering()203     public int getOrdering() {
204         return mOrdering;
205     }
206 
getIntent()207     public Intent getIntent() {
208         return mIntent;
209     }
210 
setIntent(Intent intent)211     public MenuItem setIntent(Intent intent) {
212         mIntent = intent;
213         return this;
214     }
215 
getCallback()216     Runnable getCallback() {
217         return mItemCallback;
218     }
219 
setCallback(Runnable callback)220     public MenuItem setCallback(Runnable callback) {
221         mItemCallback = callback;
222         return this;
223     }
224 
getAlphabeticShortcut()225     public char getAlphabeticShortcut() {
226         return mShortcutAlphabeticChar;
227     }
228 
setAlphabeticShortcut(char alphaChar)229     public MenuItem setAlphabeticShortcut(char alphaChar) {
230         if (mShortcutAlphabeticChar == alphaChar) return this;
231 
232         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
233 
234         mMenu.onItemsChanged(false);
235 
236         return this;
237     }
238 
getNumericShortcut()239     public char getNumericShortcut() {
240         return mShortcutNumericChar;
241     }
242 
setNumericShortcut(char numericChar)243     public MenuItem setNumericShortcut(char numericChar) {
244         if (mShortcutNumericChar == numericChar) return this;
245 
246         mShortcutNumericChar = numericChar;
247 
248         mMenu.onItemsChanged(false);
249 
250         return this;
251     }
252 
setShortcut(char numericChar, char alphaChar)253     public MenuItem setShortcut(char numericChar, char alphaChar) {
254         mShortcutNumericChar = numericChar;
255         mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
256 
257         mMenu.onItemsChanged(false);
258 
259         return this;
260     }
261 
262     /**
263      * @return The active shortcut (based on QWERTY-mode of the menu).
264      */
getShortcut()265     char getShortcut() {
266         return (mMenu.isQwertyMode() ? mShortcutAlphabeticChar : mShortcutNumericChar);
267     }
268 
269     /**
270      * @return The label to show for the shortcut. This includes the chording
271      *         key (for example 'Menu+a'). Also, any non-human readable
272      *         characters should be human readable (for example 'Menu+enter').
273      */
getShortcutLabel()274     String getShortcutLabel() {
275 
276         char shortcut = getShortcut();
277         if (shortcut == 0) {
278             return "";
279         }
280 
281         StringBuilder sb = new StringBuilder(sPrependShortcutLabel);
282         switch (shortcut) {
283 
284             case '\n':
285                 sb.append(sEnterShortcutLabel);
286                 break;
287 
288             case '\b':
289                 sb.append(sDeleteShortcutLabel);
290                 break;
291 
292             case ' ':
293                 sb.append(sSpaceShortcutLabel);
294                 break;
295 
296             default:
297                 sb.append(shortcut);
298                 break;
299         }
300 
301         return sb.toString();
302     }
303 
304     /**
305      * @return Whether this menu item should be showing shortcuts (depends on
306      *         whether the menu should show shortcuts and whether this item has
307      *         a shortcut defined)
308      */
shouldShowShortcut()309     boolean shouldShowShortcut() {
310         // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
311         return mMenu.isShortcutsVisible() && (getShortcut() != 0);
312     }
313 
getSubMenu()314     public SubMenu getSubMenu() {
315         return mSubMenu;
316     }
317 
hasSubMenu()318     public boolean hasSubMenu() {
319         return mSubMenu != null;
320     }
321 
setSubMenu(SubMenuBuilder subMenu)322     void setSubMenu(SubMenuBuilder subMenu) {
323         mSubMenu = subMenu;
324 
325         subMenu.setHeaderTitle(getTitle());
326     }
327 
328     @ViewDebug.CapturedViewProperty
getTitle()329     public CharSequence getTitle() {
330         return mTitle;
331     }
332 
333     /**
334      * Gets the title for a particular {@link ItemView}
335      *
336      * @param itemView The ItemView that is receiving the title
337      * @return Either the title or condensed title based on what the ItemView
338      *         prefers
339      */
getTitleForItemView(MenuView.ItemView itemView)340     CharSequence getTitleForItemView(MenuView.ItemView itemView) {
341         return ((itemView != null) && itemView.prefersCondensedTitle())
342                 ? getTitleCondensed()
343                 : getTitle();
344     }
345 
setTitle(CharSequence title)346     public MenuItem setTitle(CharSequence title) {
347         mTitle = title;
348 
349         mMenu.onItemsChanged(false);
350 
351         if (mSubMenu != null) {
352             mSubMenu.setHeaderTitle(title);
353         }
354 
355         return this;
356     }
357 
setTitle(int title)358     public MenuItem setTitle(int title) {
359         return setTitle(mMenu.getContext().getString(title));
360     }
361 
getTitleCondensed()362     public CharSequence getTitleCondensed() {
363         return mTitleCondensed != null ? mTitleCondensed : mTitle;
364     }
365 
setTitleCondensed(CharSequence title)366     public MenuItem setTitleCondensed(CharSequence title) {
367         mTitleCondensed = title;
368 
369         // Could use getTitle() in the loop below, but just cache what it would do here
370         if (title == null) {
371             title = mTitle;
372         }
373 
374         mMenu.onItemsChanged(false);
375 
376         return this;
377     }
378 
getIcon()379     public Drawable getIcon() {
380         if (mIconDrawable != null) {
381             return mIconDrawable;
382         }
383 
384         if (mIconResId != NO_ICON) {
385             return mMenu.getResources().getDrawable(mIconResId);
386         }
387 
388         return null;
389     }
390 
setIcon(Drawable icon)391     public MenuItem setIcon(Drawable icon) {
392         mIconResId = NO_ICON;
393         mIconDrawable = icon;
394         mMenu.onItemsChanged(false);
395 
396         return this;
397     }
398 
setIcon(int iconResId)399     public MenuItem setIcon(int iconResId) {
400         mIconDrawable = null;
401         mIconResId = iconResId;
402 
403         // If we have a view, we need to push the Drawable to them
404         mMenu.onItemsChanged(false);
405 
406         return this;
407     }
408 
isCheckable()409     public boolean isCheckable() {
410         return (mFlags & CHECKABLE) == CHECKABLE;
411     }
412 
setCheckable(boolean checkable)413     public MenuItem setCheckable(boolean checkable) {
414         final int oldFlags = mFlags;
415         mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
416         if (oldFlags != mFlags) {
417             mMenu.onItemsChanged(false);
418         }
419 
420         return this;
421     }
422 
setExclusiveCheckable(boolean exclusive)423     public void setExclusiveCheckable(boolean exclusive) {
424         mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
425     }
426 
isExclusiveCheckable()427     public boolean isExclusiveCheckable() {
428         return (mFlags & EXCLUSIVE) != 0;
429     }
430 
isChecked()431     public boolean isChecked() {
432         return (mFlags & CHECKED) == CHECKED;
433     }
434 
setChecked(boolean checked)435     public MenuItem setChecked(boolean checked) {
436         if ((mFlags & EXCLUSIVE) != 0) {
437             // Call the method on the Menu since it knows about the others in this
438             // exclusive checkable group
439             mMenu.setExclusiveItemChecked(this);
440         } else {
441             setCheckedInt(checked);
442         }
443 
444         return this;
445     }
446 
setCheckedInt(boolean checked)447     void setCheckedInt(boolean checked) {
448         final int oldFlags = mFlags;
449         mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
450         if (oldFlags != mFlags) {
451             mMenu.onItemsChanged(false);
452         }
453     }
454 
isVisible()455     public boolean isVisible() {
456         return (mFlags & HIDDEN) == 0;
457     }
458 
459     /**
460      * Changes the visibility of the item. This method DOES NOT notify the
461      * parent menu of a change in this item, so this should only be called from
462      * methods that will eventually trigger this change.  If unsure, use {@link #setVisible(boolean)}
463      * instead.
464      *
465      * @param shown Whether to show (true) or hide (false).
466      * @return Whether the item's shown state was changed
467      */
setVisibleInt(boolean shown)468     boolean setVisibleInt(boolean shown) {
469         final int oldFlags = mFlags;
470         mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN);
471         return oldFlags != mFlags;
472     }
473 
setVisible(boolean shown)474     public MenuItem setVisible(boolean shown) {
475         // Try to set the shown state to the given state. If the shown state was changed
476         // (i.e. the previous state isn't the same as given state), notify the parent menu that
477         // the shown state has changed for this item
478         if (setVisibleInt(shown)) mMenu.onItemVisibleChanged(this);
479 
480         return this;
481     }
482 
setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener)483    public MenuItem setOnMenuItemClickListener(MenuItem.OnMenuItemClickListener clickListener) {
484         mClickListener = clickListener;
485         return this;
486     }
487 
488     @Override
toString()489     public String toString() {
490         return mTitle.toString();
491     }
492 
setMenuInfo(ContextMenuInfo menuInfo)493     void setMenuInfo(ContextMenuInfo menuInfo) {
494         mMenuInfo = menuInfo;
495     }
496 
getMenuInfo()497     public ContextMenuInfo getMenuInfo() {
498         return mMenuInfo;
499     }
500 
actionFormatChanged()501     public void actionFormatChanged() {
502         mMenu.onItemActionRequestChanged(this);
503     }
504 
505     /**
506      * @return Whether the menu should show icons for menu items.
507      */
shouldShowIcon()508     public boolean shouldShowIcon() {
509         return mMenu.getOptionalIconsVisible();
510     }
511 
isActionButton()512     public boolean isActionButton() {
513         return (mFlags & IS_ACTION) == IS_ACTION;
514     }
515 
requestsActionButton()516     public boolean requestsActionButton() {
517         return (mShowAsAction & SHOW_AS_ACTION_IF_ROOM) == SHOW_AS_ACTION_IF_ROOM;
518     }
519 
requiresActionButton()520     public boolean requiresActionButton() {
521         return (mShowAsAction & SHOW_AS_ACTION_ALWAYS) == SHOW_AS_ACTION_ALWAYS;
522     }
523 
setIsActionButton(boolean isActionButton)524     public void setIsActionButton(boolean isActionButton) {
525         if (isActionButton) {
526             mFlags |= IS_ACTION;
527         } else {
528             mFlags &= ~IS_ACTION;
529         }
530     }
531 
showsTextAsAction()532     public boolean showsTextAsAction() {
533         return (mShowAsAction & SHOW_AS_ACTION_WITH_TEXT) == SHOW_AS_ACTION_WITH_TEXT;
534     }
535 
setShowAsAction(int actionEnum)536     public void setShowAsAction(int actionEnum) {
537         switch (actionEnum & SHOW_AS_ACTION_MASK) {
538             case SHOW_AS_ACTION_ALWAYS:
539             case SHOW_AS_ACTION_IF_ROOM:
540             case SHOW_AS_ACTION_NEVER:
541                 // Looks good!
542                 break;
543 
544             default:
545                 // Mutually exclusive options selected!
546                 throw new IllegalArgumentException("SHOW_AS_ACTION_ALWAYS, SHOW_AS_ACTION_IF_ROOM,"
547                         + " and SHOW_AS_ACTION_NEVER are mutually exclusive.");
548         }
549         mShowAsAction = actionEnum;
550         mMenu.onItemActionRequestChanged(this);
551     }
552 
setActionView(View view)553     public MenuItem setActionView(View view) {
554         mActionView = view;
555         mActionProvider = null;
556         if (view != null && view.getId() == View.NO_ID && mId > 0) {
557             view.setId(mId);
558         }
559         mMenu.onItemActionRequestChanged(this);
560         return this;
561     }
562 
setActionView(int resId)563     public MenuItem setActionView(int resId) {
564         final Context context = mMenu.getContext();
565         final LayoutInflater inflater = LayoutInflater.from(context);
566         setActionView(inflater.inflate(resId, new LinearLayout(context), false));
567         return this;
568     }
569 
getActionView()570     public View getActionView() {
571         if (mActionView != null) {
572             return mActionView;
573         } else if (mActionProvider != null) {
574             mActionView = mActionProvider.onCreateActionView();
575             return mActionView;
576         } else {
577             return null;
578         }
579     }
580 
getActionProvider()581     public ActionProvider getActionProvider() {
582         return mActionProvider;
583     }
584 
setActionProvider(ActionProvider actionProvider)585     public MenuItem setActionProvider(ActionProvider actionProvider) {
586         mActionView = null;
587         mActionProvider = actionProvider;
588         mMenu.onItemsChanged(true); // Measurement can be changed
589         return this;
590     }
591 
592     @Override
setShowAsActionFlags(int actionEnum)593     public MenuItem setShowAsActionFlags(int actionEnum) {
594         setShowAsAction(actionEnum);
595         return this;
596     }
597 
598     @Override
expandActionView()599     public boolean expandActionView() {
600         if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0 || mActionView == null) {
601             return false;
602         }
603 
604         if (mOnActionExpandListener == null ||
605                 mOnActionExpandListener.onMenuItemActionExpand(this)) {
606             return mMenu.expandItemActionView(this);
607         }
608 
609         return false;
610     }
611 
612     @Override
collapseActionView()613     public boolean collapseActionView() {
614         if ((mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) == 0) {
615             return false;
616         }
617         if (mActionView == null) {
618             // We're already collapsed if we have no action view.
619             return true;
620         }
621 
622         if (mOnActionExpandListener == null ||
623                 mOnActionExpandListener.onMenuItemActionCollapse(this)) {
624             return mMenu.collapseItemActionView(this);
625         }
626 
627         return false;
628     }
629 
630     @Override
setOnActionExpandListener(OnActionExpandListener listener)631     public MenuItem setOnActionExpandListener(OnActionExpandListener listener) {
632         mOnActionExpandListener = listener;
633         return this;
634     }
635 
hasCollapsibleActionView()636     public boolean hasCollapsibleActionView() {
637         return (mShowAsAction & SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW) != 0 && mActionView != null;
638     }
639 
setActionViewExpanded(boolean isExpanded)640     public void setActionViewExpanded(boolean isExpanded) {
641         mIsActionViewExpanded = isExpanded;
642         mMenu.onItemsChanged(false);
643     }
644 
isActionViewExpanded()645     public boolean isActionViewExpanded() {
646         return mIsActionViewExpanded;
647     }
648 }
649