Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.gecko.menu;
7 import org.mozilla.gecko.AppConstants;
8 import org.mozilla.gecko.R;
9 import org.mozilla.gecko.util.ThreadUtils;
10 import org.mozilla.gecko.util.ThreadUtils.AssertBehavior;
11 import org.mozilla.gecko.widget.GeckoActionProvider;
13 import android.content.ComponentName;
14 import android.content.Context;
15 import android.content.Intent;
16 import android.util.AttributeSet;
17 import android.util.Log;
18 import android.view.ActionProvider;
19 import android.view.KeyEvent;
20 import android.view.LayoutInflater;
21 import android.view.Menu;
22 import android.view.MenuItem;
23 import android.view.SubMenu;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.widget.AdapterView;
27 import android.widget.BaseAdapter;
28 import android.widget.LinearLayout;
29 import android.widget.ListView;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
36 public class GeckoMenu extends ListView
37 implements Menu,
38 AdapterView.OnItemClickListener,
39 GeckoMenuItem.OnShowAsActionChangedListener {
40 private static final String LOGTAG = "GeckoMenu";
42 /**
43 * Controls whether off-UI-thread method calls in this class cause an
44 * exception or just logging.
45 */
46 private static final AssertBehavior THREAD_ASSERT_BEHAVIOR = AppConstants.RELEASE_BUILD ? AssertBehavior.NONE : AssertBehavior.THROW;
48 /*
49 * A callback for a menu item selected event.
50 */
51 public static interface Callback {
52 // Called when a menu item is selected, with the actual menu item as the argument.
53 public boolean onMenuItemSelected(MenuItem item);
54 }
56 /*
57 * An interface for a presenter to show the menu.
58 * Either an Activity or a View can be a presenter, that can watch for events
59 * and show/hide menu.
60 */
61 public static interface MenuPresenter {
62 // Open the menu.
63 public void openMenu();
65 // Show the actual view containing the menu items. This can either be a parent or sub-menu.
66 public void showMenu(View menu);
68 // Close the menu.
69 public void closeMenu();
70 }
72 /*
73 * An interface for a presenter of action-items.
74 * Either an Activity or a View can be a presenter, that can watch for events
75 * and add/remove action-items. If not ActionItemBarPresenter, the menu uses a
76 * DefaultActionItemBar, that shows the action-items as a header over list-view.
77 */
78 public static interface ActionItemBarPresenter {
79 // Add an action-item.
80 public boolean addActionItem(View actionItem);
82 // Remove an action-item.
83 public void removeActionItem(View actionItem);
84 }
86 protected static final int NO_ID = 0;
88 // List of all menu items.
89 private List<GeckoMenuItem> mItems;
91 // Map of "always" action-items in action-bar and their views.
92 private Map<GeckoMenuItem, View> mPrimaryActionItems;
94 // Map of "ifRoom" action-items in action-bar and their views.
95 private Map<GeckoMenuItem, View> mSecondaryActionItems;
97 // Reference to a callback for menu events.
98 private Callback mCallback;
100 // Reference to menu presenter.
101 private MenuPresenter mMenuPresenter;
103 // Reference to "always" action-items bar in action-bar.
104 private ActionItemBarPresenter mPrimaryActionItemBar;
106 // Reference to "ifRoom" action-items bar in action-bar.
107 private final ActionItemBarPresenter mSecondaryActionItemBar;
109 // Adapter to hold the list of menu items.
110 private MenuItemsAdapter mAdapter;
112 // Show/hide icons in the list.
113 private boolean mShowIcons;
115 public GeckoMenu(Context context) {
116 this(context, null);
117 }
119 public GeckoMenu(Context context, AttributeSet attrs) {
120 this(context, attrs, R.attr.geckoMenuListViewStyle);
121 }
123 public GeckoMenu(Context context, AttributeSet attrs, int defStyle) {
124 super(context, attrs, defStyle);
126 setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,
127 LayoutParams.FILL_PARENT));
129 // Attach an adapter.
130 mAdapter = new MenuItemsAdapter();
131 setAdapter(mAdapter);
132 setOnItemClickListener(this);
134 mShowIcons = false;
135 mItems = new ArrayList<GeckoMenuItem>();
136 mPrimaryActionItems = new HashMap<GeckoMenuItem, View>();
137 mSecondaryActionItems = new HashMap<GeckoMenuItem, View>();
139 mPrimaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_action_bar, null);
140 mSecondaryActionItemBar = (DefaultActionItemBar) LayoutInflater.from(context).inflate(R.layout.menu_secondary_action_bar, null);
141 }
143 private static void assertOnUiThread() {
144 ThreadUtils.assertOnUiThread(THREAD_ASSERT_BEHAVIOR);
145 }
147 @Override
148 public MenuItem add(CharSequence title) {
149 GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, title);
150 addItem(menuItem);
151 return menuItem;
152 }
154 @Override
155 public MenuItem add(int groupId, int itemId, int order, int titleRes) {
156 GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, titleRes);
157 addItem(menuItem);
158 return menuItem;
159 }
161 @Override
162 public MenuItem add(int titleRes) {
163 GeckoMenuItem menuItem = new GeckoMenuItem(this, NO_ID, 0, titleRes);
164 addItem(menuItem);
165 return menuItem;
166 }
168 @Override
169 public MenuItem add(int groupId, int itemId, int order, CharSequence title) {
170 GeckoMenuItem menuItem = new GeckoMenuItem(this, itemId, order, title);
171 addItem(menuItem);
172 return menuItem;
173 }
175 private void addItem(GeckoMenuItem menuItem) {
176 assertOnUiThread();
177 menuItem.setOnShowAsActionChangedListener(this);
178 mAdapter.addMenuItem(menuItem);
179 mItems.add(menuItem);
180 }
182 private boolean addActionItem(final GeckoMenuItem menuItem) {
183 assertOnUiThread();
184 menuItem.setOnShowAsActionChangedListener(this);
186 final View actionView = menuItem.getActionView();
187 final int actionEnum = menuItem.getActionEnum();
188 boolean added = false;
190 if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
191 if (mPrimaryActionItems.size() == 0 &&
192 mPrimaryActionItemBar instanceof DefaultActionItemBar) {
193 // Reset the adapter before adding the header view to a list.
194 setAdapter(null);
195 addHeaderView((DefaultActionItemBar) mPrimaryActionItemBar);
196 setAdapter(mAdapter);
197 }
199 if (added = mPrimaryActionItemBar.addActionItem(actionView)) {
200 mPrimaryActionItems.put(menuItem, actionView);
201 mItems.add(menuItem);
202 }
203 } else if (actionEnum == GeckoMenuItem.SHOW_AS_ACTION_IF_ROOM) {
204 if (mSecondaryActionItems.size() == 0) {
205 // Reset the adapter before adding the header view to a list.
206 setAdapter(null);
207 addHeaderView((DefaultActionItemBar) mSecondaryActionItemBar);
208 setAdapter(mAdapter);
209 }
211 if (added = mSecondaryActionItemBar.addActionItem(actionView)) {
212 mSecondaryActionItems.put(menuItem, actionView);
213 mItems.add(menuItem);
214 }
215 }
217 // Set the listeners.
218 if (actionView instanceof MenuItemActionBar) {
219 ((MenuItemActionBar) actionView).setOnClickListener(new View.OnClickListener() {
220 @Override
221 public void onClick(View view) {
222 handleMenuItemClick(menuItem);
223 }
224 });
225 } else if (actionView instanceof MenuItemActionView) {
226 ((MenuItemActionView) actionView).setMenuItemClickListener(new View.OnClickListener() {
227 @Override
228 public void onClick(View view) {
229 handleMenuItemClick(menuItem);
230 }
231 });
232 }
234 return added;
235 }
237 @Override
238 public int addIntentOptions(int groupId, int itemId, int order, ComponentName caller, Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
239 return 0;
240 }
242 @Override
243 public SubMenu addSubMenu(int groupId, int itemId, int order, CharSequence title) {
244 MenuItem menuItem = add(groupId, itemId, order, title);
245 return addSubMenu(menuItem);
246 }
248 @Override
249 public SubMenu addSubMenu(int groupId, int itemId, int order, int titleRes) {
250 MenuItem menuItem = add(groupId, itemId, order, titleRes);
251 return addSubMenu(menuItem);
252 }
254 @Override
255 public SubMenu addSubMenu(CharSequence title) {
256 MenuItem menuItem = add(title);
257 return addSubMenu(menuItem);
258 }
260 @Override
261 public SubMenu addSubMenu(int titleRes) {
262 MenuItem menuItem = add(titleRes);
263 return addSubMenu(menuItem);
264 }
266 private SubMenu addSubMenu(MenuItem menuItem) {
267 GeckoSubMenu subMenu = new GeckoSubMenu(getContext());
268 subMenu.setMenuItem(menuItem);
269 subMenu.setCallback(mCallback);
270 subMenu.setMenuPresenter(mMenuPresenter);
271 ((GeckoMenuItem) menuItem).setSubMenu(subMenu);
272 return subMenu;
273 }
275 private void removePrimaryActionBarView() {
276 // Reset the adapter before removing the header view from a list.
277 setAdapter(null);
278 removeHeaderView((DefaultActionItemBar) mPrimaryActionItemBar);
279 setAdapter(mAdapter);
280 }
282 private void removeSecondaryActionBarView() {
283 // Reset the adapter before removing the header view from a list.
284 setAdapter(null);
285 removeHeaderView((DefaultActionItemBar) mSecondaryActionItemBar);
286 setAdapter(mAdapter);
287 }
289 @Override
290 public void clear() {
291 assertOnUiThread();
292 for (GeckoMenuItem menuItem : mItems) {
293 if (menuItem.hasSubMenu()) {
294 SubMenu sub = menuItem.getSubMenu();
295 if (sub == null) {
296 continue;
297 }
298 try {
299 sub.clear();
300 } catch (Exception ex) {
301 Log.e(LOGTAG, "Couldn't clear submenu.", ex);
302 }
303 }
304 }
306 mAdapter.clear();
307 mItems.clear();
309 /*
310 * Reinflating the menu will re-add any action items to the toolbar, so
311 * remove the old ones. This also ensures that any text associated with
312 * these is switched to the correct locale.
313 */
314 if (mPrimaryActionItemBar != null) {
315 for (View item : mPrimaryActionItems.values()) {
316 mPrimaryActionItemBar.removeActionItem(item);
317 }
318 }
319 mPrimaryActionItems.clear();
321 if (mSecondaryActionItemBar != null) {
322 for (View item : mSecondaryActionItems.values()) {
323 mSecondaryActionItemBar.removeActionItem(item);
324 }
325 }
326 mSecondaryActionItems.clear();
328 // Remove the view, too -- the first addActionItem will re-add it,
329 // and this is simpler than changing that logic.
330 if (mPrimaryActionItemBar instanceof DefaultActionItemBar) {
331 removePrimaryActionBarView();
332 }
334 removeSecondaryActionBarView();
335 }
337 @Override
338 public void close() {
339 if (mMenuPresenter != null)
340 mMenuPresenter.closeMenu();
341 }
343 private void showMenu(View viewForMenu) {
344 if (mMenuPresenter != null)
345 mMenuPresenter.showMenu(viewForMenu);
346 }
348 @Override
349 public MenuItem findItem(int id) {
350 for (GeckoMenuItem menuItem : mItems) {
351 if (menuItem.getItemId() == id) {
352 return menuItem;
353 } else if (menuItem.hasSubMenu()) {
354 if (!menuItem.hasActionProvider()) {
355 SubMenu subMenu = menuItem.getSubMenu();
356 MenuItem item = subMenu.findItem(id);
357 if (item != null)
358 return item;
359 }
360 }
361 }
362 return null;
363 }
365 @Override
366 public MenuItem getItem(int index) {
367 if (index < mItems.size())
368 return mItems.get(index);
370 return null;
371 }
373 @Override
374 public boolean hasVisibleItems() {
375 assertOnUiThread();
376 for (GeckoMenuItem menuItem : mItems) {
377 if (menuItem.isVisible() &&
378 !mPrimaryActionItems.containsKey(menuItem) &&
379 !mSecondaryActionItems.containsKey(menuItem))
380 return true;
381 }
383 return false;
384 }
386 @Override
387 public boolean isShortcutKey(int keyCode, KeyEvent event) {
388 return true;
389 }
391 @Override
392 public boolean performIdentifierAction(int id, int flags) {
393 return false;
394 }
396 @Override
397 public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
398 return false;
399 }
401 @Override
402 public void removeGroup(int groupId) {
403 }
405 @Override
406 public void removeItem(int id) {
407 assertOnUiThread();
408 GeckoMenuItem item = (GeckoMenuItem) findItem(id);
409 if (item == null)
410 return;
412 // Remove it from any sub-menu.
413 for (GeckoMenuItem menuItem : mItems) {
414 if (menuItem.hasSubMenu()) {
415 SubMenu subMenu = menuItem.getSubMenu();
416 if (subMenu != null && subMenu.findItem(id) != null) {
417 subMenu.removeItem(id);
418 return;
419 }
420 }
421 }
423 // Remove it from own menu.
424 if (mPrimaryActionItems.containsKey(item)) {
425 if (mPrimaryActionItemBar != null)
426 mPrimaryActionItemBar.removeActionItem(mPrimaryActionItems.get(item));
428 mPrimaryActionItems.remove(item);
429 mItems.remove(item);
431 if (mPrimaryActionItems.size() == 0 &&
432 mPrimaryActionItemBar instanceof DefaultActionItemBar) {
433 removePrimaryActionBarView();
434 }
436 return;
437 }
439 if (mSecondaryActionItems.containsKey(item)) {
440 if (mSecondaryActionItemBar != null)
441 mSecondaryActionItemBar.removeActionItem(mSecondaryActionItems.get(item));
443 mSecondaryActionItems.remove(item);
444 mItems.remove(item);
446 if (mSecondaryActionItems.size() == 0) {
447 removeSecondaryActionBarView();
448 }
450 return;
451 }
453 mAdapter.removeMenuItem(item);
454 mItems.remove(item);
455 }
457 @Override
458 public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
459 }
461 @Override
462 public void setGroupEnabled(int group, boolean enabled) {
463 }
465 @Override
466 public void setGroupVisible(int group, boolean visible) {
467 }
469 @Override
470 public void setQwertyMode(boolean isQwerty) {
471 }
473 @Override
474 public int size() {
475 return mItems.size();
476 }
478 @Override
479 public boolean hasActionItemBar() {
480 return (mPrimaryActionItemBar != null) && (mSecondaryActionItemBar != null);
481 }
483 @Override
484 public void onShowAsActionChanged(GeckoMenuItem item) {
485 removeItem(item.getItemId());
487 if (item.isActionItem() && addActionItem(item)) {
488 return;
489 }
491 addItem(item);
492 }
494 public void onItemChanged(GeckoMenuItem item) {
495 assertOnUiThread();
496 if (item.isActionItem()) {
497 final View actionView;
498 if (item.getActionEnum() == GeckoMenuItem.SHOW_AS_ACTION_ALWAYS) {
499 actionView = mPrimaryActionItems.get(item);
500 } else {
501 actionView = mSecondaryActionItems.get(item);
502 }
504 if (actionView != null) {
505 // The update could be coming from the background thread.
506 // Post a runnable on the UI thread of the view for it to update.
507 final GeckoMenuItem menuItem = item;
508 actionView.post(new Runnable() {
509 @Override
510 public void run() {
511 if (menuItem.isVisible()) {
512 actionView.setVisibility(View.VISIBLE);
513 if (actionView instanceof MenuItemActionBar) {
514 ((MenuItemActionBar) actionView).initialize(menuItem);
515 } else {
516 ((MenuItemActionView) actionView).initialize(menuItem);
517 }
518 } else {
519 actionView.setVisibility(View.GONE);
520 }
521 }
522 });
523 }
524 } else {
525 mAdapter.notifyDataSetChanged();
526 }
527 }
529 @Override
530 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
531 // We might be showing headers. Account them while using the position.
532 position -= getHeaderViewsCount();
534 GeckoMenuItem item = mAdapter.getItem(position);
535 handleMenuItemClick(item);
536 }
538 private void handleMenuItemClick(GeckoMenuItem item) {
539 if (!item.isEnabled())
540 return;
542 if (item.invoke()) {
543 close();
544 } else if (item.hasSubMenu()) {
545 // Refresh the submenu for the provider.
546 GeckoActionProvider provider = item.getGeckoActionProvider();
547 if (provider != null) {
548 GeckoSubMenu subMenu = new GeckoSubMenu(getContext());
549 subMenu.setShowIcons(true);
550 provider.onPrepareSubMenu(subMenu);
551 item.setSubMenu(subMenu);
552 }
554 // Show the submenu.
555 GeckoSubMenu subMenu = (GeckoSubMenu) item.getSubMenu();
556 showMenu(subMenu);
557 } else {
558 close();
559 mCallback.onMenuItemSelected(item);
560 }
561 }
563 public Callback getCallback() {
564 return mCallback;
565 }
567 public MenuPresenter getMenuPresenter() {
568 return mMenuPresenter;
569 }
571 public void setCallback(Callback callback) {
572 mCallback = callback;
574 // Update the submenus just in case this changes on the fly.
575 for (GeckoMenuItem menuItem : mItems) {
576 if (menuItem.hasSubMenu()) {
577 GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu();
578 subMenu.setCallback(mCallback);
579 }
580 }
581 }
583 public void setMenuPresenter(MenuPresenter presenter) {
584 mMenuPresenter = presenter;
586 // Update the submenus just in case this changes on the fly.
587 for (GeckoMenuItem menuItem : mItems) {
588 if (menuItem.hasSubMenu()) {
589 GeckoSubMenu subMenu = (GeckoSubMenu) menuItem.getSubMenu();
590 subMenu.setMenuPresenter(mMenuPresenter);
591 }
592 }
593 }
595 public void setActionItemBarPresenter(ActionItemBarPresenter presenter) {
596 mPrimaryActionItemBar = presenter;
597 }
599 public void setShowIcons(boolean show) {
600 if (mShowIcons != show) {
601 mShowIcons = show;
602 mAdapter.notifyDataSetChanged();
603 }
604 }
606 // Action Items are added to the header view by default.
607 // URL bar can register itself as a presenter, in case it has a different place to show them.
608 public static class DefaultActionItemBar extends LinearLayout
609 implements ActionItemBarPresenter {
610 private final int mRowHeight;
611 private float mWeightSum;
613 public DefaultActionItemBar(Context context) {
614 this(context, null);
615 }
617 public DefaultActionItemBar(Context context, AttributeSet attrs) {
618 super(context, attrs);
620 mRowHeight = getResources().getDimensionPixelSize(R.dimen.menu_item_row_height);
621 }
623 @Override
624 public boolean addActionItem(View actionItem) {
625 ViewGroup.LayoutParams actualParams = actionItem.getLayoutParams();
626 LinearLayout.LayoutParams params;
628 if (actualParams != null) {
629 params = new LinearLayout.LayoutParams(actionItem.getLayoutParams());
630 params.width = 0;
631 } else {
632 params = new LinearLayout.LayoutParams(0, mRowHeight);
633 }
635 if (actionItem instanceof MenuItemActionView) {
636 params.weight = ((MenuItemActionView) actionItem).getChildCount();
637 } else {
638 params.weight = 1.0f;
639 }
641 mWeightSum += params.weight;
643 actionItem.setLayoutParams(params);
644 addView(actionItem);
645 setWeightSum(mWeightSum);
646 return true;
647 }
649 @Override
650 public void removeActionItem(View actionItem) {
651 if (indexOfChild(actionItem) != -1) {
652 LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) actionItem.getLayoutParams();
653 mWeightSum -= params.weight;
654 removeView(actionItem);
655 }
656 }
657 }
659 // Adapter to bind menu items to the list.
660 private class MenuItemsAdapter extends BaseAdapter {
661 private static final int VIEW_TYPE_DEFAULT = 0;
662 private static final int VIEW_TYPE_ACTION_MODE = 1;
664 private List<GeckoMenuItem> mItems;
666 public MenuItemsAdapter() {
667 mItems = new ArrayList<GeckoMenuItem>();
668 }
670 @Override
671 public int getCount() {
672 if (mItems == null)
673 return 0;
675 int visibleCount = 0;
676 for (GeckoMenuItem item : mItems) {
677 if (item.isVisible())
678 visibleCount++;
679 }
681 return visibleCount;
682 }
684 @Override
685 public GeckoMenuItem getItem(int position) {
686 for (GeckoMenuItem item : mItems) {
687 if (item.isVisible()) {
688 position--;
690 if (position < 0)
691 return item;
692 }
693 }
695 return null;
696 }
698 @Override
699 public long getItemId(int position) {
700 return position;
701 }
703 @Override
704 public View getView(int position, View convertView, ViewGroup parent) {
705 GeckoMenuItem item = getItem(position);
706 GeckoMenuItem.Layout view = null;
708 // Try to re-use the view.
709 if (convertView == null && getItemViewType(position) == VIEW_TYPE_DEFAULT) {
710 view = new MenuItemDefault(parent.getContext(), null);
711 } else {
712 view = (GeckoMenuItem.Layout) convertView;
713 }
715 if (view == null || view instanceof MenuItemActionView) {
716 // Always get from the menu item.
717 // This will ensure that the default activity is refreshed.
718 view = (MenuItemActionView) item.getActionView();
720 // ListView will not perform an item click if the row has a focusable view in it.
721 // Hence, forward the click event on the menu item in the action-view to the ListView.
722 final View actionView = (View) view;
723 final int pos = position;
724 final long id = getItemId(position);
725 ((MenuItemActionView) view).setMenuItemClickListener(new View.OnClickListener() {
726 @Override
727 public void onClick(View v) {
728 GeckoMenu listView = GeckoMenu.this;
729 listView.performItemClick(actionView, pos + listView.getHeaderViewsCount(), id);
730 }
731 });
732 }
734 // Initialize the view.
735 view.setShowIcon(mShowIcons);
736 view.initialize(item);
737 return (View) view;
738 }
740 @Override
741 public int getItemViewType(int position) {
742 return getItem(position).getGeckoActionProvider() == null ? VIEW_TYPE_DEFAULT : VIEW_TYPE_ACTION_MODE;
743 }
745 @Override
746 public int getViewTypeCount() {
747 return 2;
748 }
750 @Override
751 public boolean hasStableIds() {
752 return false;
753 }
755 @Override
756 public boolean areAllItemsEnabled() {
757 // Setting this to true is a workaround to fix disappearing
758 // dividers in the menu (bug 963249).
759 return true;
760 }
762 @Override
763 public boolean isEnabled(int position) {
764 return getItem(position).isEnabled();
765 }
767 public void addMenuItem(GeckoMenuItem menuItem) {
768 if (mItems.contains(menuItem))
769 return;
771 // Insert it in proper order.
772 int index = 0;
773 for (GeckoMenuItem item : mItems) {
774 if (item.getOrder() > menuItem.getOrder()) {
775 mItems.add(index, menuItem);
776 notifyDataSetChanged();
777 return;
778 } else {
779 index++;
780 }
781 }
783 // Add the menuItem at the end.
784 mItems.add(menuItem);
785 notifyDataSetChanged();
786 }
788 public void removeMenuItem(GeckoMenuItem menuItem) {
789 // Remove it from the list.
790 mItems.remove(menuItem);
791 notifyDataSetChanged();
792 }
794 public void clear() {
795 mItems.clear();
796 notifyDataSetChanged();
797 }
799 public GeckoMenuItem getMenuItem(int id) {
800 for (GeckoMenuItem item : mItems) {
801 if (item.getItemId() == id)
802 return item;
803 }
805 return null;
806 }
807 }
808 }