blob: 67dc81af5895ca17d3626646290aa7c3d0a9fcb1 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package com.android.internal.view.menu;
18
19
Alan Viverette28a84682016-01-04 13:43:23 -050020import android.annotation.NonNull;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.content.res.Configuration;
27import android.content.res.Resources;
28import android.graphics.drawable.Drawable;
Adam Powell11ed1d62011-07-11 21:19:59 -070029import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.os.Parcelable;
31import android.util.SparseArray;
Adam Powell961dd112011-07-12 14:25:23 -070032import android.view.ActionProvider;
Adam Powell36fced92011-01-16 15:48:07 -080033import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.view.KeyCharacterMap;
35import android.view.KeyEvent;
36import android.view.Menu;
37import android.view.MenuItem;
38import android.view.SubMenu;
39import android.view.View;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040
41import java.lang.ref.WeakReference;
42import java.util.ArrayList;
43import java.util.List;
Adam Powell696cba52011-03-29 10:38:16 -070044import java.util.concurrent.CopyOnWriteArrayList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045
46/**
47 * Implementation of the {@link android.view.Menu} interface for creating a
48 * standard menu UI.
49 */
50public class MenuBuilder implements Menu {
Adam Powell89b09da2011-07-27 11:55:29 -070051 private static final String TAG = "MenuBuilder";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052
Adam Powell11ed1d62011-07-11 21:19:59 -070053 private static final String PRESENTER_KEY = "android:menu:presenters";
Adam Powell038f1c82011-07-21 14:28:10 -070054 private static final String ACTION_VIEW_STATES_KEY = "android:menu:actionviewstates";
55 private static final String EXPANDED_ACTION_VIEW_ID = "android:menu:expandedactionview";
Adam Powell11ed1d62011-07-11 21:19:59 -070056
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 private static final int[] sCategoryToOrder = new int[] {
58 1, /* No category */
59 4, /* CONTAINER */
60 5, /* SYSTEM */
61 3, /* SECONDARY */
62 2, /* ALTERNATIVE */
63 0, /* SELECTED_ALTERNATIVE */
64 };
65
66 private final Context mContext;
67 private final Resources mResources;
68
69 /**
70 * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
71 * instead of accessing this directly.
72 */
73 private boolean mQwertyMode;
74
75 /**
76 * Whether the shortcuts should be visible on menus. Use isShortcutsVisible()
77 * instead of accessing this directly.
78 */
79 private boolean mShortcutsVisible;
80
81 /**
82 * Callback that will receive the various menu-related events generated by
83 * this class. Use getCallback to get a reference to the callback.
84 */
85 private Callback mCallback;
86
87 /** Contains all of the items for this menu */
88 private ArrayList<MenuItemImpl> mItems;
89
90 /** Contains only the items that are currently visible. This will be created/refreshed from
91 * {@link #getVisibleItems()} */
92 private ArrayList<MenuItemImpl> mVisibleItems;
93 /**
94 * Whether or not the items (or any one item's shown state) has changed since it was last
95 * fetched from {@link #getVisibleItems()}
96 */
97 private boolean mIsVisibleItemsStale;
Adam Powell96675b12010-06-10 18:58:59 -070098
99 /**
100 * Contains only the items that should appear in the Action Bar, if present.
101 */
102 private ArrayList<MenuItemImpl> mActionItems;
103 /**
104 * Contains items that should NOT appear in the Action Bar, if present.
105 */
106 private ArrayList<MenuItemImpl> mNonActionItems;
Adam Powell696cba52011-03-29 10:38:16 -0700107
Adam Powell36fced92011-01-16 15:48:07 -0800108 /**
Adam Powell96675b12010-06-10 18:58:59 -0700109 * Whether or not the items (or any one item's action state) has changed since it was
110 * last fetched.
111 */
112 private boolean mIsActionItemsStale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800113
114 /**
Adam Powell4d9861e2010-08-17 11:14:40 -0700115 * Default value for how added items should show in the action list.
116 */
117 private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
118
119 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120 * Current use case is Context Menus: As Views populate the context menu, each one has
121 * extra information that should be passed along. This is the current menu info that
122 * should be set on all items added to this menu.
123 */
124 private ContextMenuInfo mCurrentMenuInfo;
125
126 /** Header title for menu types that have a header (context and submenus) */
127 CharSequence mHeaderTitle;
128 /** Header icon for menu types that have a header and support icons (context) */
129 Drawable mHeaderIcon;
130 /** Header custom view for menu types that have a header and support custom views (context) */
131 View mHeaderView;
132
133 /**
134 * Contains the state of the View hierarchy for all menu views when the menu
135 * was frozen.
136 */
137 private SparseArray<Parcelable> mFrozenViewStates;
138
139 /**
140 * Prevents onItemsChanged from doing its junk, useful for batching commands
141 * that may individually call onItemsChanged.
142 */
143 private boolean mPreventDispatchingItemsChanged = false;
Adam Powell696cba52011-03-29 10:38:16 -0700144 private boolean mItemsChangedWhileDispatchPrevented = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145
146 private boolean mOptionalIconsVisible = false;
Adam Powell2fbf4de62010-09-30 15:46:46 -0700147
Adam Powell696cba52011-03-29 10:38:16 -0700148 private boolean mIsClosing = false;
Adam Powell36fced92011-01-16 15:48:07 -0800149
Adam Powell696cba52011-03-29 10:38:16 -0700150 private ArrayList<MenuItemImpl> mTempShortcutItemList = new ArrayList<MenuItemImpl>();
Adam Powell1821ff92011-01-24 21:44:28 -0800151
Adam Powell696cba52011-03-29 10:38:16 -0700152 private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
153 new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
Adam Powell6b0e97c2011-08-03 12:06:32 -0700154
155 /**
156 * Currently expanded menu item; must be collapsed when we clear.
157 */
158 private MenuItemImpl mExpandedItem;
Tarun Lohani0b842b42017-07-26 11:57:05 -0700159
160 /**
161 * Whether group dividers are enabled.
162 */
163 private boolean mGroupDividerEnabled = false;
164
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800165 /**
Adam Powell696cba52011-03-29 10:38:16 -0700166 * Called by menu to notify of close and selection changes.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 */
168 public interface Callback {
169 /**
170 * Called when a menu item is selected.
171 * @param menu The menu that is the parent of the item
172 * @param item The menu item that is selected
173 * @return whether the menu item selection was handled
174 */
175 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
176
177 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178 * Called when the mode of the menu changes (for example, from icon to expanded).
179 *
180 * @param menu the menu that has changed modes
181 */
182 public void onMenuModeChange(MenuBuilder menu);
183 }
184
185 /**
186 * Called by menu items to execute their associated action
187 */
188 public interface ItemInvoker {
189 public boolean invokeItem(MenuItemImpl item);
190 }
191
192 public MenuBuilder(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 mContext = context;
194 mResources = context.getResources();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800195 mItems = new ArrayList<MenuItemImpl>();
196
197 mVisibleItems = new ArrayList<MenuItemImpl>();
198 mIsVisibleItemsStale = true;
199
Adam Powell96675b12010-06-10 18:58:59 -0700200 mActionItems = new ArrayList<MenuItemImpl>();
201 mNonActionItems = new ArrayList<MenuItemImpl>();
202 mIsActionItemsStale = true;
203
Jeff Brown4aed78b2011-01-14 17:36:55 -0800204 setShortcutsVisibleInner(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 }
206
Adam Powell4d9861e2010-08-17 11:14:40 -0700207 public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) {
208 mDefaultShowAsAction = defaultShowAsAction;
209 return this;
210 }
Adam Powell696cba52011-03-29 10:38:16 -0700211
212 /**
213 * Add a presenter to this menu. This will only hold a WeakReference;
214 * you do not need to explicitly remove a presenter, but you can using
215 * {@link #removeMenuPresenter(MenuPresenter)}.
216 *
217 * @param presenter The presenter to add
218 */
219 public void addMenuPresenter(MenuPresenter presenter) {
Alan Viverette3d0f21d2014-07-10 16:15:01 -0700220 addMenuPresenter(presenter, mContext);
221 }
222
223 /**
224 * Add a presenter to this menu that uses an alternate context for
225 * inflating menu items. This will only hold a WeakReference; you do not
226 * need to explicitly remove a presenter, but you can using
227 * {@link #removeMenuPresenter(MenuPresenter)}.
228 *
229 * @param presenter The presenter to add
230 * @param menuContext The context used to inflate menu items
231 */
232 public void addMenuPresenter(MenuPresenter presenter, Context menuContext) {
Adam Powell696cba52011-03-29 10:38:16 -0700233 mPresenters.add(new WeakReference<MenuPresenter>(presenter));
Alan Viverette3d0f21d2014-07-10 16:15:01 -0700234 presenter.initForMenu(menuContext, this);
Adam Powell696cba52011-03-29 10:38:16 -0700235 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800236 }
237
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800238 /**
Adam Powell696cba52011-03-29 10:38:16 -0700239 * Remove a presenter from this menu. That presenter will no longer
240 * receive notifications of updates to this menu's data.
241 *
242 * @param presenter The presenter to remove
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800243 */
Adam Powell696cba52011-03-29 10:38:16 -0700244 public void removeMenuPresenter(MenuPresenter presenter) {
245 for (WeakReference<MenuPresenter> ref : mPresenters) {
246 final MenuPresenter item = ref.get();
247 if (item == null || item == presenter) {
248 mPresenters.remove(ref);
249 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800250 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 }
252
Adam Powell696cba52011-03-29 10:38:16 -0700253 private void dispatchPresenterUpdate(boolean cleared) {
254 if (mPresenters.isEmpty()) return;
255
Adam Powell640a66e2011-04-29 10:18:53 -0700256 stopDispatchingItemsChanged();
Adam Powell696cba52011-03-29 10:38:16 -0700257 for (WeakReference<MenuPresenter> ref : mPresenters) {
258 final MenuPresenter presenter = ref.get();
259 if (presenter == null) {
260 mPresenters.remove(ref);
261 } else {
262 presenter.updateMenuView(cleared);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 }
264 }
Adam Powell640a66e2011-04-29 10:18:53 -0700265 startDispatchingItemsChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266 }
267
Adam Powellc0cc6802013-12-03 18:58:29 -0800268 private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu,
269 MenuPresenter preferredPresenter) {
Adam Powell696cba52011-03-29 10:38:16 -0700270 if (mPresenters.isEmpty()) return false;
271
272 boolean result = false;
273
Adam Powellc0cc6802013-12-03 18:58:29 -0800274 // Try the preferred presenter first.
275 if (preferredPresenter != null) {
276 result = preferredPresenter.onSubMenuSelected(subMenu);
277 }
278
Adam Powell696cba52011-03-29 10:38:16 -0700279 for (WeakReference<MenuPresenter> ref : mPresenters) {
280 final MenuPresenter presenter = ref.get();
281 if (presenter == null) {
282 mPresenters.remove(ref);
283 } else if (!result) {
284 result = presenter.onSubMenuSelected(subMenu);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800285 }
286 }
Adam Powell696cba52011-03-29 10:38:16 -0700287 return result;
288 }
289
Adam Powell11ed1d62011-07-11 21:19:59 -0700290 private void dispatchSaveInstanceState(Bundle outState) {
291 if (mPresenters.isEmpty()) return;
292
293 SparseArray<Parcelable> presenterStates = new SparseArray<Parcelable>();
294
295 for (WeakReference<MenuPresenter> ref : mPresenters) {
296 final MenuPresenter presenter = ref.get();
297 if (presenter == null) {
298 mPresenters.remove(ref);
299 } else {
300 final int id = presenter.getId();
301 if (id > 0) {
302 final Parcelable state = presenter.onSaveInstanceState();
303 if (state != null) {
304 presenterStates.put(id, state);
305 }
306 }
307 }
308 }
309
310 outState.putSparseParcelableArray(PRESENTER_KEY, presenterStates);
311 }
312
313 private void dispatchRestoreInstanceState(Bundle state) {
314 SparseArray<Parcelable> presenterStates = state.getSparseParcelableArray(PRESENTER_KEY);
315
316 if (presenterStates == null || mPresenters.isEmpty()) return;
317
318 for (WeakReference<MenuPresenter> ref : mPresenters) {
319 final MenuPresenter presenter = ref.get();
320 if (presenter == null) {
321 mPresenters.remove(ref);
322 } else {
323 final int id = presenter.getId();
324 if (id > 0) {
325 Parcelable parcel = presenterStates.get(id);
326 if (parcel != null) {
327 presenter.onRestoreInstanceState(parcel);
328 }
329 }
330 }
331 }
332 }
333
334 public void savePresenterStates(Bundle outState) {
335 dispatchSaveInstanceState(outState);
336 }
337
338 public void restorePresenterStates(Bundle state) {
339 dispatchRestoreInstanceState(state);
340 }
341
Adam Powell038f1c82011-07-21 14:28:10 -0700342 public void saveActionViewStates(Bundle outStates) {
343 SparseArray<Parcelable> viewStates = null;
344
345 final int itemCount = size();
346 for (int i = 0; i < itemCount; i++) {
347 final MenuItem item = getItem(i);
348 final View v = item.getActionView();
349 if (v != null && v.getId() != View.NO_ID) {
350 if (viewStates == null) {
351 viewStates = new SparseArray<Parcelable>();
352 }
353 v.saveHierarchyState(viewStates);
354 if (item.isActionViewExpanded()) {
355 outStates.putInt(EXPANDED_ACTION_VIEW_ID, item.getItemId());
356 }
357 }
358 if (item.hasSubMenu()) {
359 final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
360 subMenu.saveActionViewStates(outStates);
361 }
362 }
363
364 if (viewStates != null) {
365 outStates.putSparseParcelableArray(getActionViewStatesKey(), viewStates);
366 }
367 }
368
369 public void restoreActionViewStates(Bundle states) {
370 if (states == null) {
371 return;
372 }
373
374 SparseArray<Parcelable> viewStates = states.getSparseParcelableArray(
375 getActionViewStatesKey());
376
377 final int itemCount = size();
378 for (int i = 0; i < itemCount; i++) {
379 final MenuItem item = getItem(i);
380 final View v = item.getActionView();
381 if (v != null && v.getId() != View.NO_ID) {
382 v.restoreHierarchyState(viewStates);
383 }
384 if (item.hasSubMenu()) {
385 final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
386 subMenu.restoreActionViewStates(states);
387 }
388 }
389
390 final int expandedId = states.getInt(EXPANDED_ACTION_VIEW_ID);
391 if (expandedId > 0) {
392 MenuItem itemToExpand = findItem(expandedId);
393 if (itemToExpand != null) {
394 itemToExpand.expandActionView();
395 }
396 }
397 }
398
399 protected String getActionViewStatesKey() {
400 return ACTION_VIEW_STATES_KEY;
401 }
402
Adam Powell696cba52011-03-29 10:38:16 -0700403 public void setCallback(Callback cb) {
404 mCallback = cb;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405 }
406
407 /**
408 * Adds an item to the menu. The other add methods funnel to this.
409 */
410 private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
411 final int ordering = getOrdering(categoryOrder);
412
Deepanshu Gupta10019612014-04-18 12:32:38 -0700413 final MenuItemImpl item = createNewMenuItem(group, id, categoryOrder, ordering, title,
414 mDefaultShowAsAction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800415
416 if (mCurrentMenuInfo != null) {
417 // Pass along the current menu info
418 item.setMenuInfo(mCurrentMenuInfo);
419 }
420
421 mItems.add(findInsertIndex(mItems, ordering), item);
Adam Powell696cba52011-03-29 10:38:16 -0700422 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423
424 return item;
425 }
Deepanshu Gupta10019612014-04-18 12:32:38 -0700426
427 // Layoutlib overrides this method to return its custom implementation of MenuItemImpl
428 private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering,
429 CharSequence title, int defaultShowAsAction) {
430 return new MenuItemImpl(this, group, id, categoryOrder, ordering, title,
431 defaultShowAsAction);
432 }
433
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800434 public MenuItem add(CharSequence title) {
435 return addInternal(0, 0, 0, title);
436 }
437
438 public MenuItem add(int titleRes) {
439 return addInternal(0, 0, 0, mResources.getString(titleRes));
440 }
441
442 public MenuItem add(int group, int id, int categoryOrder, CharSequence title) {
443 return addInternal(group, id, categoryOrder, title);
444 }
445
446 public MenuItem add(int group, int id, int categoryOrder, int title) {
447 return addInternal(group, id, categoryOrder, mResources.getString(title));
448 }
449
450 public SubMenu addSubMenu(CharSequence title) {
451 return addSubMenu(0, 0, 0, title);
452 }
453
454 public SubMenu addSubMenu(int titleRes) {
455 return addSubMenu(0, 0, 0, mResources.getString(titleRes));
456 }
457
458 public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) {
459 final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
460 final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item);
461 item.setSubMenu(subMenu);
462
463 return subMenu;
464 }
465
466 public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) {
467 return addSubMenu(group, id, categoryOrder, mResources.getString(title));
468 }
469
Tarun Lohani0b842b42017-07-26 11:57:05 -0700470 @Override
471 public void setGroupDividerEnabled(boolean groupDividerEnabled) {
472 mGroupDividerEnabled = groupDividerEnabled;
473 }
474
475 public boolean isGroupDividerEnabled() {
476 return mGroupDividerEnabled;
477 }
478
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800479 public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller,
480 Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
481 PackageManager pm = mContext.getPackageManager();
482 final List<ResolveInfo> lri =
483 pm.queryIntentActivityOptions(caller, specifics, intent, 0);
484 final int N = lri != null ? lri.size() : 0;
485
486 if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
487 removeGroup(group);
488 }
489
490 for (int i=0; i<N; i++) {
491 final ResolveInfo ri = lri.get(i);
492 Intent rintent = new Intent(
493 ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
494 rintent.setComponent(new ComponentName(
495 ri.activityInfo.applicationInfo.packageName,
496 ri.activityInfo.name));
497 final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm))
498 .setIcon(ri.loadIcon(pm))
499 .setIntent(rintent);
500 if (outSpecificItems != null && ri.specificIndex >= 0) {
501 outSpecificItems[ri.specificIndex] = item;
502 }
503 }
504
505 return N;
506 }
507
508 public void removeItem(int id) {
509 removeItemAtInt(findItemIndex(id), true);
510 }
511
512 public void removeGroup(int group) {
513 final int i = findGroupIndex(group);
514
515 if (i >= 0) {
516 final int maxRemovable = mItems.size() - i;
517 int numRemoved = 0;
518 while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) {
519 // Don't force update for each one, this method will do it at the end
520 removeItemAtInt(i, false);
521 }
522
523 // Notify menu views
Adam Powell696cba52011-03-29 10:38:16 -0700524 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 }
526 }
527
528 /**
529 * Remove the item at the given index and optionally forces menu views to
530 * update.
531 *
532 * @param index The index of the item to be removed. If this index is
533 * invalid an exception is thrown.
534 * @param updateChildrenOnMenuViews Whether to force update on menu views.
535 * Please make sure you eventually call this after your batch of
536 * removals.
537 */
538 private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
539 if ((index < 0) || (index >= mItems.size())) return;
540
541 mItems.remove(index);
542
Adam Powell696cba52011-03-29 10:38:16 -0700543 if (updateChildrenOnMenuViews) onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800544 }
545
546 public void removeItemAt(int index) {
547 removeItemAtInt(index, true);
548 }
549
550 public void clearAll() {
551 mPreventDispatchingItemsChanged = true;
552 clear();
553 clearHeader();
Vladislav Kaznacheeva6dfa742018-01-25 13:59:35 -0800554 mPresenters.clear();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 mPreventDispatchingItemsChanged = false;
Adam Powell696cba52011-03-29 10:38:16 -0700556 mItemsChangedWhileDispatchPrevented = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800557 onItemsChanged(true);
558 }
559
560 public void clear() {
Adam Powell6b0e97c2011-08-03 12:06:32 -0700561 if (mExpandedItem != null) {
562 collapseItemActionView(mExpandedItem);
563 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800564 mItems.clear();
565
566 onItemsChanged(true);
567 }
568
569 void setExclusiveItemChecked(MenuItem item) {
570 final int group = item.getGroupId();
571
572 final int N = mItems.size();
573 for (int i = 0; i < N; i++) {
574 MenuItemImpl curItem = mItems.get(i);
575 if (curItem.getGroupId() == group) {
576 if (!curItem.isExclusiveCheckable()) continue;
577 if (!curItem.isCheckable()) continue;
578
579 // Check the item meant to be checked, uncheck the others (that are in the group)
580 curItem.setCheckedInt(curItem == item);
581 }
582 }
583 }
584
585 public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
586 final int N = mItems.size();
587
588 for (int i = 0; i < N; i++) {
589 MenuItemImpl item = mItems.get(i);
590 if (item.getGroupId() == group) {
591 item.setExclusiveCheckable(exclusive);
592 item.setCheckable(checkable);
593 }
594 }
595 }
596
597 public void setGroupVisible(int group, boolean visible) {
598 final int N = mItems.size();
599
600 // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
601 // than setVisible and at the end notify of items being changed
602
603 boolean changedAtLeastOneItem = false;
604 for (int i = 0; i < N; i++) {
605 MenuItemImpl item = mItems.get(i);
606 if (item.getGroupId() == group) {
607 if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
608 }
609 }
610
Adam Powell23f4cc02011-08-18 10:30:46 -0700611 if (changedAtLeastOneItem) onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 }
613
614 public void setGroupEnabled(int group, boolean enabled) {
615 final int N = mItems.size();
616
617 for (int i = 0; i < N; i++) {
618 MenuItemImpl item = mItems.get(i);
619 if (item.getGroupId() == group) {
620 item.setEnabled(enabled);
621 }
622 }
623 }
624
625 public boolean hasVisibleItems() {
626 final int size = size();
627
628 for (int i = 0; i < size; i++) {
629 MenuItemImpl item = mItems.get(i);
630 if (item.isVisible()) {
631 return true;
632 }
633 }
634
635 return false;
636 }
637
638 public MenuItem findItem(int id) {
639 final int size = size();
640 for (int i = 0; i < size; i++) {
641 MenuItemImpl item = mItems.get(i);
642 if (item.getItemId() == id) {
643 return item;
644 } else if (item.hasSubMenu()) {
645 MenuItem possibleItem = item.getSubMenu().findItem(id);
646
647 if (possibleItem != null) {
648 return possibleItem;
649 }
650 }
651 }
652
653 return null;
654 }
655
656 public int findItemIndex(int id) {
657 final int size = size();
658
659 for (int i = 0; i < size; i++) {
660 MenuItemImpl item = mItems.get(i);
661 if (item.getItemId() == id) {
662 return i;
663 }
664 }
665
666 return -1;
667 }
668
669 public int findGroupIndex(int group) {
670 return findGroupIndex(group, 0);
671 }
672
673 public int findGroupIndex(int group, int start) {
674 final int size = size();
675
676 if (start < 0) {
677 start = 0;
678 }
679
680 for (int i = start; i < size; i++) {
681 final MenuItemImpl item = mItems.get(i);
682
683 if (item.getGroupId() == group) {
684 return i;
685 }
686 }
687
688 return -1;
689 }
690
691 public int size() {
692 return mItems.size();
693 }
694
695 /** {@inheritDoc} */
696 public MenuItem getItem(int index) {
697 return mItems.get(index);
698 }
699
700 public boolean isShortcutKey(int keyCode, KeyEvent event) {
701 return findItemWithShortcutForKey(keyCode, event) != null;
702 }
703
704 public void setQwertyMode(boolean isQwerty) {
705 mQwertyMode = isQwerty;
Adam Powell696cba52011-03-29 10:38:16 -0700706
707 onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 }
709
710 /**
711 * Returns the ordering across all items. This will grab the category from
712 * the upper bits, find out how to order the category with respect to other
713 * categories, and combine it with the lower bits.
714 *
715 * @param categoryOrder The category order for a particular item (if it has
716 * not been or/add with a category, the default category is
717 * assumed).
718 * @return An ordering integer that can be used to order this item across
719 * all the items (even from other categories).
720 */
Adam Powell696cba52011-03-29 10:38:16 -0700721 private static int getOrdering(int categoryOrder) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722 final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
723
724 if (index < 0 || index >= sCategoryToOrder.length) {
725 throw new IllegalArgumentException("order does not contain a valid category.");
726 }
727
728 return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
729 }
730
731 /**
732 * @return whether the menu shortcuts are in qwerty mode or not
733 */
734 boolean isQwertyMode() {
735 return mQwertyMode;
736 }
737
738 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800739 * Sets whether the shortcuts should be visible on menus. Devices without hardware
740 * key input will never make shortcuts visible even if this method is passed 'true'.
741 *
742 * @param shortcutsVisible Whether shortcuts should be visible (if true and a
743 * menu item does not have a shortcut defined, that item will
744 * still NOT show a shortcut)
745 */
746 public void setShortcutsVisible(boolean shortcutsVisible) {
747 if (mShortcutsVisible == shortcutsVisible) return;
Jeff Brown4aed78b2011-01-14 17:36:55 -0800748
749 setShortcutsVisibleInner(shortcutsVisible);
Adam Powell696cba52011-03-29 10:38:16 -0700750 onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800751 }
752
Jeff Brown4aed78b2011-01-14 17:36:55 -0800753 private void setShortcutsVisibleInner(boolean shortcutsVisible) {
754 mShortcutsVisible = shortcutsVisible
755 && mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS
756 && mResources.getBoolean(
757 com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
758 }
759
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 /**
761 * @return Whether shortcuts should be visible on menus.
762 */
763 public boolean isShortcutsVisible() {
764 return mShortcutsVisible;
765 }
766
767 Resources getResources() {
768 return mResources;
769 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800770
771 public Context getContext() {
772 return mContext;
773 }
774
Adam Powell696cba52011-03-29 10:38:16 -0700775 boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
776 return mCallback != null && mCallback.onMenuItemSelected(menu, item);
777 }
778
779 /**
780 * Dispatch a mode change event to this menu's callback.
781 */
782 public void changeMenuMode() {
783 if (mCallback != null) {
784 mCallback.onMenuModeChange(this);
785 }
786 }
787
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800788 private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
789 for (int i = items.size() - 1; i >= 0; i--) {
790 MenuItemImpl item = items.get(i);
791 if (item.getOrdering() <= ordering) {
792 return i + 1;
793 }
794 }
795
796 return 0;
797 }
798
799 public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
800 final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event);
801
802 boolean handled = false;
803
804 if (item != null) {
805 handled = performItemAction(item, flags);
806 }
807
808 if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
Alan Viverette00aa5102015-11-03 13:03:15 -0500809 close(true /* closeAllMenus */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 }
811
812 return handled;
813 }
814
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100815 /*
816 * This function will return all the menu and sub-menu items that can
817 * be directly (the shortcut directly corresponds) and indirectly
818 * (the ALT-enabled char corresponds to the shortcut) associated
819 * with the keyCode.
820 */
Adam Powell696cba52011-03-29 10:38:16 -0700821 void findItemsWithShortcutForKey(List<MenuItemImpl> items, int keyCode, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800822 final boolean qwerty = isQwertyMode();
Peeyush Agarwale631e322016-10-19 11:41:42 +0100823 final int modifierState = event.getModifiers();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800824 final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
825 // Get the chars associated with the keyCode (i.e using any chording combo)
826 final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
827 // The delete key is not mapped to '\b' so we treat it specially
828 if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
Adam Powell696cba52011-03-29 10:38:16 -0700829 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800830 }
831
832 // Look for an item whose shortcut is this key.
833 final int N = mItems.size();
834 for (int i = 0; i < N; i++) {
835 MenuItemImpl item = mItems.get(i);
836 if (item.hasSubMenu()) {
Adam Powell696cba52011-03-29 10:38:16 -0700837 ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800838 }
Peeyush Agarwale631e322016-10-19 11:41:42 +0100839 final char shortcutChar =
840 qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
841 final int shortcutModifiers =
842 qwerty ? item.getAlphabeticModifiers() : item.getNumericModifiers();
843 final boolean isModifiersExactMatch = (modifierState & SUPPORTED_MODIFIERS_MASK)
844 == (shortcutModifiers & SUPPORTED_MODIFIERS_MASK);
845 if (isModifiersExactMatch && (shortcutChar != 0) &&
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100846 (shortcutChar == possibleChars.meta[0]
847 || shortcutChar == possibleChars.meta[2]
848 || (qwerty && shortcutChar == '\b' &&
849 keyCode == KeyEvent.KEYCODE_DEL)) &&
Alan Viverettee38eb4d2014-10-22 23:51:32 +0000850 item.isEnabled()) {
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100851 items.add(item);
852 }
853 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100854 }
855
856 /*
857 * We want to return the menu item associated with the key, but if there is no
858 * ambiguity (i.e. there is only one menu item corresponding to the key) we want
859 * to return it even if it's not an exact match; this allow the user to
860 * _not_ use the ALT key for example, making the use of shortcuts slightly more
861 * user-friendly. An example is on the G1, '!' and '1' are on the same key, and
862 * in Gmail, Menu+1 will trigger Menu+! (the actual shortcut).
863 *
864 * On the other hand, if two (or more) shortcuts corresponds to the same key,
865 * we have to only return the exact match.
866 */
867 MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
868 // Get all items that can be associated directly or indirectly with the keyCode
Adam Powell696cba52011-03-29 10:38:16 -0700869 ArrayList<MenuItemImpl> items = mTempShortcutItemList;
870 items.clear();
871 findItemsWithShortcutForKey(items, keyCode, event);
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100872
Adam Powell696cba52011-03-29 10:38:16 -0700873 if (items.isEmpty()) {
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100874 return null;
875 }
876
877 final int metaState = event.getMetaState();
878 final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
879 // Get the chars associated with the keyCode (i.e using any chording combo)
880 event.getKeyData(possibleChars);
881
882 // If we have only one element, we can safely returns it
Adam Powell696cba52011-03-29 10:38:16 -0700883 final int size = items.size();
884 if (size == 1) {
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100885 return items.get(0);
886 }
887
888 final boolean qwerty = isQwertyMode();
889 // If we found more than one item associated with the key,
890 // we have to return the exact match
Adam Powell696cba52011-03-29 10:38:16 -0700891 for (int i = 0; i < size; i++) {
892 final MenuItemImpl item = items.get(i);
893 final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
894 item.getNumericShortcut();
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100895 if ((shortcutChar == possibleChars.meta[0] &&
896 (metaState & KeyEvent.META_ALT_ON) == 0)
897 || (shortcutChar == possibleChars.meta[2] &&
898 (metaState & KeyEvent.META_ALT_ON) != 0)
899 || (qwerty && shortcutChar == '\b' &&
900 keyCode == KeyEvent.KEYCODE_DEL)) {
901 return item;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800902 }
903 }
904 return null;
905 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100906
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800907 public boolean performIdentifierAction(int id, int flags) {
908 // Look for an item whose identifier is the id.
909 return performItemAction(findItem(id), flags);
910 }
911
912 public boolean performItemAction(MenuItem item, int flags) {
Adam Powellc0cc6802013-12-03 18:58:29 -0800913 return performItemAction(item, null, flags);
914 }
915
916 public boolean performItemAction(MenuItem item, MenuPresenter preferredPresenter, int flags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800917 MenuItemImpl itemImpl = (MenuItemImpl) item;
918
919 if (itemImpl == null || !itemImpl.isEnabled()) {
920 return false;
Adam Powell8d02dea2011-05-31 21:35:13 -0700921 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700922
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800923 boolean invoked = itemImpl.invoke();
924
Adam Powellf77f4802012-05-14 16:44:43 -0700925 final ActionProvider provider = item.getActionProvider();
926 final boolean providerHasSubMenu = provider != null && provider.hasSubMenu();
Adam Powell8d02dea2011-05-31 21:35:13 -0700927 if (itemImpl.hasCollapsibleActionView()) {
928 invoked |= itemImpl.expandActionView();
Alan Viverette00aa5102015-11-03 13:03:15 -0500929 if (invoked) {
930 close(true /* closeAllMenus */);
931 }
Adam Powellf77f4802012-05-14 16:44:43 -0700932 } else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
Adam Powellf77f4802012-05-14 16:44:43 -0700933 if (!itemImpl.hasSubMenu()) {
934 itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
935 }
936
937 final SubMenuBuilder subMenu = (SubMenuBuilder) itemImpl.getSubMenu();
938 if (providerHasSubMenu) {
Adam Powell961dd112011-07-12 14:25:23 -0700939 provider.onPrepareSubMenu(subMenu);
940 }
Adam Powellc0cc6802013-12-03 18:58:29 -0800941 invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter);
Alan Viverette00aa5102015-11-03 13:03:15 -0500942 if (!invoked) {
943 close(true /* closeAllMenus */);
944 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 } else {
946 if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
Alan Viverette00aa5102015-11-03 13:03:15 -0500947 close(true /* closeAllMenus */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800948 }
949 }
950
951 return invoked;
952 }
953
954 /**
Alan Viverette00aa5102015-11-03 13:03:15 -0500955 * Closes the menu.
956 *
957 * @param closeAllMenus {@code true} if all displayed menus and submenus
958 * should be completely closed (as when a menu item is
959 * selected) or {@code false} if only this menu should
960 * be closed
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961 */
Alan Viverette00aa5102015-11-03 13:03:15 -0500962 public final void close(boolean closeAllMenus) {
Adam Powell696cba52011-03-29 10:38:16 -0700963 if (mIsClosing) return;
964
965 mIsClosing = true;
966 for (WeakReference<MenuPresenter> ref : mPresenters) {
967 final MenuPresenter presenter = ref.get();
968 if (presenter == null) {
969 mPresenters.remove(ref);
970 } else {
Alan Viverette00aa5102015-11-03 13:03:15 -0500971 presenter.onCloseMenu(this, closeAllMenus);
Adam Powell696cba52011-03-29 10:38:16 -0700972 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800973 }
Adam Powell696cba52011-03-29 10:38:16 -0700974 mIsClosing = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800975 }
976
977 /** {@inheritDoc} */
978 public void close() {
Alan Viverette00aa5102015-11-03 13:03:15 -0500979 close(true /* closeAllMenus */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800980 }
981
982 /**
983 * Called when an item is added or removed.
984 *
Adam Powell696cba52011-03-29 10:38:16 -0700985 * @param structureChanged true if the menu structure changed,
986 * false if only item properties changed.
Adam Powell23f4cc02011-08-18 10:30:46 -0700987 * (Visibility is a structural property since it affects layout.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800988 */
Adam Powellfa18d182014-01-07 15:56:59 -0800989 public void onItemsChanged(boolean structureChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800990 if (!mPreventDispatchingItemsChanged) {
Adam Powell696cba52011-03-29 10:38:16 -0700991 if (structureChanged) {
992 mIsVisibleItemsStale = true;
993 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 }
Adam Powell696cba52011-03-29 10:38:16 -0700995
996 dispatchPresenterUpdate(structureChanged);
997 } else {
998 mItemsChangedWhileDispatchPrevented = true;
999 }
1000 }
1001
1002 /**
1003 * Stop dispatching item changed events to presenters until
1004 * {@link #startDispatchingItemsChanged()} is called. Useful when
1005 * many menu operations are going to be performed as a batch.
1006 */
1007 public void stopDispatchingItemsChanged() {
Adam Powella86b3502011-04-21 14:49:23 -07001008 if (!mPreventDispatchingItemsChanged) {
1009 mPreventDispatchingItemsChanged = true;
1010 mItemsChangedWhileDispatchPrevented = false;
1011 }
Adam Powell696cba52011-03-29 10:38:16 -07001012 }
1013
1014 public void startDispatchingItemsChanged() {
1015 mPreventDispatchingItemsChanged = false;
1016
1017 if (mItemsChangedWhileDispatchPrevented) {
1018 mItemsChangedWhileDispatchPrevented = false;
1019 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001020 }
1021 }
1022
1023 /**
1024 * Called by {@link MenuItemImpl} when its visible flag is changed.
1025 * @param item The item that has gone through a visibility change.
1026 */
1027 void onItemVisibleChanged(MenuItemImpl item) {
1028 // Notify of items being changed
Adam Powell696cba52011-03-29 10:38:16 -07001029 mIsVisibleItemsStale = true;
Adam Powell23f4cc02011-08-18 10:30:46 -07001030 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001031 }
1032
Adam Powell96675b12010-06-10 18:58:59 -07001033 /**
1034 * Called by {@link MenuItemImpl} when its action request status is changed.
1035 * @param item The item that has gone through a change in action request status.
1036 */
1037 void onItemActionRequestChanged(MenuItemImpl item) {
1038 // Notify of items being changed
Adam Powell696cba52011-03-29 10:38:16 -07001039 mIsActionItemsStale = true;
Adam Powell23f4cc02011-08-18 10:30:46 -07001040 onItemsChanged(true);
Adam Powell96675b12010-06-10 18:58:59 -07001041 }
Alan Viverette28a84682016-01-04 13:43:23 -05001042
1043 @NonNull
Adam Powellfa18d182014-01-07 15:56:59 -08001044 public ArrayList<MenuItemImpl> getVisibleItems() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001045 if (!mIsVisibleItemsStale) return mVisibleItems;
Alan Viverette28a84682016-01-04 13:43:23 -05001046
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001047 // Refresh the visible items
1048 mVisibleItems.clear();
Alan Viverette28a84682016-01-04 13:43:23 -05001049
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001050 final int itemsSize = mItems.size();
1051 MenuItemImpl item;
1052 for (int i = 0; i < itemsSize; i++) {
1053 item = mItems.get(i);
1054 if (item.isVisible()) mVisibleItems.add(item);
1055 }
Alan Viverette28a84682016-01-04 13:43:23 -05001056
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001057 mIsVisibleItemsStale = false;
Adam Powell96675b12010-06-10 18:58:59 -07001058 mIsActionItemsStale = true;
Alan Viverette28a84682016-01-04 13:43:23 -05001059
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060 return mVisibleItems;
1061 }
Adam Powell36fced92011-01-16 15:48:07 -08001062
1063 /**
1064 * This method determines which menu items get to be 'action items' that will appear
1065 * in an action bar and which items should be 'overflow items' in a secondary menu.
1066 * The rules are as follows:
1067 *
1068 * <p>Items are considered for inclusion in the order specified within the menu.
1069 * There is a limit of mMaxActionItems as a total count, optionally including the overflow
1070 * menu button itself. This is a soft limit; if an item shares a group ID with an item
1071 * previously included as an action item, the new item will stay with its group and become
1072 * an action item itself even if it breaks the max item count limit. This is done to
1073 * limit the conceptual complexity of the items presented within an action bar. Only a few
1074 * unrelated concepts should be presented to the user in this space, and groups are treated
1075 * as a single concept.
1076 *
1077 * <p>There is also a hard limit of consumed measurable space: mActionWidthLimit. This
1078 * limit may be broken by a single item that exceeds the remaining space, but no further
1079 * items may be added. If an item that is part of a group cannot fit within the remaining
1080 * measured width, the entire group will be demoted to overflow. This is done to ensure room
1081 * for navigation and other affordances in the action bar as well as reduce general UI clutter.
1082 *
1083 * <p>The space freed by demoting a full group cannot be consumed by future menu items.
1084 * Once items begin to overflow, all future items become overflow items as well. This is
1085 * to avoid inadvertent reordering that may break the app's intended design.
Adam Powell36fced92011-01-16 15:48:07 -08001086 */
Adam Powell696cba52011-03-29 10:38:16 -07001087 public void flagActionItems() {
Adam Powellda971082013-10-03 18:21:58 -07001088 // Important side effect: if getVisibleItems is stale it may refresh,
1089 // which can affect action items staleness.
1090 final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
1091
Adam Powell96675b12010-06-10 18:58:59 -07001092 if (!mIsActionItemsStale) {
1093 return;
1094 }
Adam Powell8028dd32010-07-15 10:16:33 -07001095
Adam Powell696cba52011-03-29 10:38:16 -07001096 // Presenters flag action items as needed.
1097 boolean flagged = false;
1098 for (WeakReference<MenuPresenter> ref : mPresenters) {
1099 final MenuPresenter presenter = ref.get();
1100 if (presenter == null) {
1101 mPresenters.remove(ref);
Adam Powell8028dd32010-07-15 10:16:33 -07001102 } else {
Adam Powell696cba52011-03-29 10:38:16 -07001103 flagged |= presenter.flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001104 }
1105 }
Adam Powell8028dd32010-07-15 10:16:33 -07001106
Adam Powell696cba52011-03-29 10:38:16 -07001107 if (flagged) {
1108 mActionItems.clear();
1109 mNonActionItems.clear();
Adam Powell696cba52011-03-29 10:38:16 -07001110 final int itemsSize = visibleItems.size();
1111 for (int i = 0; i < itemsSize; i++) {
1112 MenuItemImpl item = visibleItems.get(i);
1113 if (item.isActionButton()) {
1114 mActionItems.add(item);
1115 } else {
1116 mNonActionItems.add(item);
Adam Powell36fced92011-01-16 15:48:07 -08001117 }
Adam Powell96675b12010-06-10 18:58:59 -07001118 }
Adam Powell04dc06f2011-08-22 18:11:11 -07001119 } else {
1120 // Nobody flagged anything, everything is a non-action item.
Adam Powell696cba52011-03-29 10:38:16 -07001121 // (This happens during a first pass with no action-item presenters.)
1122 mActionItems.clear();
1123 mNonActionItems.clear();
1124 mNonActionItems.addAll(getVisibleItems());
Adam Powell96675b12010-06-10 18:58:59 -07001125 }
Adam Powell96675b12010-06-10 18:58:59 -07001126 mIsActionItemsStale = false;
1127 }
1128
Adam Powellfa18d182014-01-07 15:56:59 -08001129 public ArrayList<MenuItemImpl> getActionItems() {
Adam Powell696cba52011-03-29 10:38:16 -07001130 flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001131 return mActionItems;
1132 }
1133
Adam Powellfa18d182014-01-07 15:56:59 -08001134 public ArrayList<MenuItemImpl> getNonActionItems() {
Adam Powell696cba52011-03-29 10:38:16 -07001135 flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001136 return mNonActionItems;
1137 }
Adam Powell36fced92011-01-16 15:48:07 -08001138
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001139 public void clearHeader() {
1140 mHeaderIcon = null;
1141 mHeaderTitle = null;
1142 mHeaderView = null;
1143
1144 onItemsChanged(false);
1145 }
1146
1147 private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes,
1148 final Drawable icon, final View view) {
1149 final Resources r = getResources();
1150
1151 if (view != null) {
1152 mHeaderView = view;
1153
1154 // If using a custom view, then the title and icon aren't used
1155 mHeaderTitle = null;
1156 mHeaderIcon = null;
1157 } else {
1158 if (titleRes > 0) {
1159 mHeaderTitle = r.getText(titleRes);
1160 } else if (title != null) {
1161 mHeaderTitle = title;
1162 }
1163
1164 if (iconRes > 0) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001165 mHeaderIcon = getContext().getDrawable(iconRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001166 } else if (icon != null) {
1167 mHeaderIcon = icon;
1168 }
1169
1170 // If using the title or icon, then a custom view isn't used
1171 mHeaderView = null;
1172 }
1173
1174 // Notify of change
1175 onItemsChanged(false);
1176 }
1177
1178 /**
1179 * Sets the header's title. This replaces the header view. Called by the
1180 * builder-style methods of subclasses.
1181 *
1182 * @param title The new title.
1183 * @return This MenuBuilder so additional setters can be called.
1184 */
1185 protected MenuBuilder setHeaderTitleInt(CharSequence title) {
1186 setHeaderInternal(0, title, 0, null, null);
1187 return this;
1188 }
1189
1190 /**
1191 * Sets the header's title. This replaces the header view. Called by the
1192 * builder-style methods of subclasses.
1193 *
1194 * @param titleRes The new title (as a resource ID).
1195 * @return This MenuBuilder so additional setters can be called.
1196 */
1197 protected MenuBuilder setHeaderTitleInt(int titleRes) {
1198 setHeaderInternal(titleRes, null, 0, null, null);
1199 return this;
1200 }
1201
1202 /**
1203 * Sets the header's icon. This replaces the header view. Called by the
1204 * builder-style methods of subclasses.
1205 *
1206 * @param icon The new icon.
1207 * @return This MenuBuilder so additional setters can be called.
1208 */
1209 protected MenuBuilder setHeaderIconInt(Drawable icon) {
1210 setHeaderInternal(0, null, 0, icon, null);
1211 return this;
1212 }
1213
1214 /**
1215 * Sets the header's icon. This replaces the header view. Called by the
1216 * builder-style methods of subclasses.
1217 *
1218 * @param iconRes The new icon (as a resource ID).
1219 * @return This MenuBuilder so additional setters can be called.
1220 */
1221 protected MenuBuilder setHeaderIconInt(int iconRes) {
1222 setHeaderInternal(0, null, iconRes, null, null);
1223 return this;
1224 }
1225
1226 /**
1227 * Sets the header's view. This replaces the title and icon. Called by the
1228 * builder-style methods of subclasses.
1229 *
1230 * @param view The new view.
1231 * @return This MenuBuilder so additional setters can be called.
1232 */
1233 protected MenuBuilder setHeaderViewInt(View view) {
1234 setHeaderInternal(0, null, 0, null, view);
1235 return this;
1236 }
1237
1238 public CharSequence getHeaderTitle() {
1239 return mHeaderTitle;
1240 }
1241
1242 public Drawable getHeaderIcon() {
1243 return mHeaderIcon;
1244 }
1245
1246 public View getHeaderView() {
1247 return mHeaderView;
1248 }
1249
1250 /**
1251 * Gets the root menu (if this is a submenu, find its root menu).
1252 * @return The root menu.
1253 */
1254 public MenuBuilder getRootMenu() {
1255 return this;
1256 }
1257
1258 /**
1259 * Sets the current menu info that is set on all items added to this menu
1260 * (until this is called again with different menu info, in which case that
1261 * one will be added to all subsequent item additions).
1262 *
1263 * @param menuInfo The extra menu information to add.
1264 */
1265 public void setCurrentMenuInfo(ContextMenuInfo menuInfo) {
1266 mCurrentMenuInfo = menuInfo;
1267 }
1268
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001269 void setOptionalIconsVisible(boolean visible) {
1270 mOptionalIconsVisible = visible;
1271 }
1272
1273 boolean getOptionalIconsVisible() {
1274 return mOptionalIconsVisible;
1275 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001276
1277 public boolean expandItemActionView(MenuItemImpl item) {
1278 if (mPresenters.isEmpty()) return false;
1279
1280 boolean expanded = false;
1281
1282 stopDispatchingItemsChanged();
1283 for (WeakReference<MenuPresenter> ref : mPresenters) {
1284 final MenuPresenter presenter = ref.get();
1285 if (presenter == null) {
1286 mPresenters.remove(ref);
1287 } else if ((expanded = presenter.expandItemActionView(this, item))) {
1288 break;
1289 }
1290 }
1291 startDispatchingItemsChanged();
1292
Adam Powell6b0e97c2011-08-03 12:06:32 -07001293 if (expanded) {
1294 mExpandedItem = item;
1295 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001296 return expanded;
1297 }
1298
1299 public boolean collapseItemActionView(MenuItemImpl item) {
Adam Powell6b0e97c2011-08-03 12:06:32 -07001300 if (mPresenters.isEmpty() || mExpandedItem != item) return false;
Adam Powell8d02dea2011-05-31 21:35:13 -07001301
1302 boolean collapsed = false;
1303
1304 stopDispatchingItemsChanged();
1305 for (WeakReference<MenuPresenter> ref : mPresenters) {
1306 final MenuPresenter presenter = ref.get();
1307 if (presenter == null) {
1308 mPresenters.remove(ref);
1309 } else if ((collapsed = presenter.collapseItemActionView(this, item))) {
1310 break;
1311 }
1312 }
1313 startDispatchingItemsChanged();
1314
Adam Powell6b0e97c2011-08-03 12:06:32 -07001315 if (collapsed) {
1316 mExpandedItem = null;
1317 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001318 return collapsed;
1319 }
Adam Powell275702c2011-09-23 17:34:04 -07001320
1321 public MenuItemImpl getExpandedItem() {
1322 return mExpandedItem;
1323 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001324}