blob: 5d7d322689238375eb1aa7a343019bf7c2af45ff [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
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
25import android.content.res.Configuration;
26import android.content.res.Resources;
27import android.graphics.drawable.Drawable;
Adam Powell11ed1d62011-07-11 21:19:59 -070028import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.os.Parcelable;
30import android.util.SparseArray;
Adam Powell961dd112011-07-12 14:25:23 -070031import android.view.ActionProvider;
Adam Powell36fced92011-01-16 15:48:07 -080032import android.view.ContextMenu.ContextMenuInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.view.KeyCharacterMap;
34import android.view.KeyEvent;
35import android.view.Menu;
36import android.view.MenuItem;
37import android.view.SubMenu;
38import android.view.View;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039
40import java.lang.ref.WeakReference;
41import java.util.ArrayList;
42import java.util.List;
Adam Powell696cba52011-03-29 10:38:16 -070043import java.util.concurrent.CopyOnWriteArrayList;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044
45/**
46 * Implementation of the {@link android.view.Menu} interface for creating a
47 * standard menu UI.
48 */
49public class MenuBuilder implements Menu {
Adam Powell89b09da2011-07-27 11:55:29 -070050 private static final String TAG = "MenuBuilder";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
Adam Powell11ed1d62011-07-11 21:19:59 -070052 private static final String PRESENTER_KEY = "android:menu:presenters";
Adam Powell038f1c82011-07-21 14:28:10 -070053 private static final String ACTION_VIEW_STATES_KEY = "android:menu:actionviewstates";
54 private static final String EXPANDED_ACTION_VIEW_ID = "android:menu:expandedactionview";
Adam Powell11ed1d62011-07-11 21:19:59 -070055
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 private static final int[] sCategoryToOrder = new int[] {
57 1, /* No category */
58 4, /* CONTAINER */
59 5, /* SYSTEM */
60 3, /* SECONDARY */
61 2, /* ALTERNATIVE */
62 0, /* SELECTED_ALTERNATIVE */
63 };
64
65 private final Context mContext;
66 private final Resources mResources;
67
68 /**
69 * Whether the shortcuts should be qwerty-accessible. Use isQwertyMode()
70 * instead of accessing this directly.
71 */
72 private boolean mQwertyMode;
73
74 /**
75 * Whether the shortcuts should be visible on menus. Use isShortcutsVisible()
76 * instead of accessing this directly.
77 */
78 private boolean mShortcutsVisible;
79
80 /**
81 * Callback that will receive the various menu-related events generated by
82 * this class. Use getCallback to get a reference to the callback.
83 */
84 private Callback mCallback;
85
86 /** Contains all of the items for this menu */
87 private ArrayList<MenuItemImpl> mItems;
88
89 /** Contains only the items that are currently visible. This will be created/refreshed from
90 * {@link #getVisibleItems()} */
91 private ArrayList<MenuItemImpl> mVisibleItems;
92 /**
93 * Whether or not the items (or any one item's shown state) has changed since it was last
94 * fetched from {@link #getVisibleItems()}
95 */
96 private boolean mIsVisibleItemsStale;
Adam Powell96675b12010-06-10 18:58:59 -070097
98 /**
99 * Contains only the items that should appear in the Action Bar, if present.
100 */
101 private ArrayList<MenuItemImpl> mActionItems;
102 /**
103 * Contains items that should NOT appear in the Action Bar, if present.
104 */
105 private ArrayList<MenuItemImpl> mNonActionItems;
Adam Powell696cba52011-03-29 10:38:16 -0700106
Adam Powell36fced92011-01-16 15:48:07 -0800107 /**
Adam Powell96675b12010-06-10 18:58:59 -0700108 * Whether or not the items (or any one item's action state) has changed since it was
109 * last fetched.
110 */
111 private boolean mIsActionItemsStale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112
113 /**
Adam Powell4d9861e2010-08-17 11:14:40 -0700114 * Default value for how added items should show in the action list.
115 */
116 private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
117
118 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 * Current use case is Context Menus: As Views populate the context menu, each one has
120 * extra information that should be passed along. This is the current menu info that
121 * should be set on all items added to this menu.
122 */
123 private ContextMenuInfo mCurrentMenuInfo;
124
125 /** Header title for menu types that have a header (context and submenus) */
126 CharSequence mHeaderTitle;
127 /** Header icon for menu types that have a header and support icons (context) */
128 Drawable mHeaderIcon;
129 /** Header custom view for menu types that have a header and support custom views (context) */
130 View mHeaderView;
131
132 /**
133 * Contains the state of the View hierarchy for all menu views when the menu
134 * was frozen.
135 */
136 private SparseArray<Parcelable> mFrozenViewStates;
137
138 /**
139 * Prevents onItemsChanged from doing its junk, useful for batching commands
140 * that may individually call onItemsChanged.
141 */
142 private boolean mPreventDispatchingItemsChanged = false;
Adam Powell696cba52011-03-29 10:38:16 -0700143 private boolean mItemsChangedWhileDispatchPrevented = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144
145 private boolean mOptionalIconsVisible = false;
Adam Powell2fbf4de62010-09-30 15:46:46 -0700146
Adam Powell696cba52011-03-29 10:38:16 -0700147 private boolean mIsClosing = false;
Adam Powell36fced92011-01-16 15:48:07 -0800148
Adam Powell696cba52011-03-29 10:38:16 -0700149 private ArrayList<MenuItemImpl> mTempShortcutItemList = new ArrayList<MenuItemImpl>();
Adam Powell1821ff92011-01-24 21:44:28 -0800150
Adam Powell696cba52011-03-29 10:38:16 -0700151 private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
152 new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
Adam Powell6b0e97c2011-08-03 12:06:32 -0700153
154 /**
155 * Currently expanded menu item; must be collapsed when we clear.
156 */
157 private MenuItemImpl mExpandedItem;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800158
159 /**
Adam Powell696cba52011-03-29 10:38:16 -0700160 * Called by menu to notify of close and selection changes.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161 */
162 public interface Callback {
163 /**
164 * Called when a menu item is selected.
165 * @param menu The menu that is the parent of the item
166 * @param item The menu item that is selected
167 * @return whether the menu item selection was handled
168 */
169 public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
170
171 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 * Called when the mode of the menu changes (for example, from icon to expanded).
173 *
174 * @param menu the menu that has changed modes
175 */
176 public void onMenuModeChange(MenuBuilder menu);
177 }
178
179 /**
180 * Called by menu items to execute their associated action
181 */
182 public interface ItemInvoker {
183 public boolean invokeItem(MenuItemImpl item);
184 }
185
186 public MenuBuilder(Context context) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800187 mContext = context;
188 mResources = context.getResources();
189
190 mItems = new ArrayList<MenuItemImpl>();
191
192 mVisibleItems = new ArrayList<MenuItemImpl>();
193 mIsVisibleItemsStale = true;
194
Adam Powell96675b12010-06-10 18:58:59 -0700195 mActionItems = new ArrayList<MenuItemImpl>();
196 mNonActionItems = new ArrayList<MenuItemImpl>();
197 mIsActionItemsStale = true;
198
Jeff Brown4aed78b2011-01-14 17:36:55 -0800199 setShortcutsVisibleInner(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200 }
201
Adam Powell4d9861e2010-08-17 11:14:40 -0700202 public MenuBuilder setDefaultShowAsAction(int defaultShowAsAction) {
203 mDefaultShowAsAction = defaultShowAsAction;
204 return this;
205 }
Adam Powell696cba52011-03-29 10:38:16 -0700206
207 /**
208 * Add a presenter to this menu. This will only hold a WeakReference;
209 * you do not need to explicitly remove a presenter, but you can using
210 * {@link #removeMenuPresenter(MenuPresenter)}.
211 *
212 * @param presenter The presenter to add
213 */
214 public void addMenuPresenter(MenuPresenter presenter) {
215 mPresenters.add(new WeakReference<MenuPresenter>(presenter));
216 presenter.initForMenu(mContext, this);
217 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800218 }
219
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 /**
Adam Powell696cba52011-03-29 10:38:16 -0700221 * Remove a presenter from this menu. That presenter will no longer
222 * receive notifications of updates to this menu's data.
223 *
224 * @param presenter The presenter to remove
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800225 */
Adam Powell696cba52011-03-29 10:38:16 -0700226 public void removeMenuPresenter(MenuPresenter presenter) {
227 for (WeakReference<MenuPresenter> ref : mPresenters) {
228 final MenuPresenter item = ref.get();
229 if (item == null || item == presenter) {
230 mPresenters.remove(ref);
231 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800232 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 }
234
Adam Powell696cba52011-03-29 10:38:16 -0700235 private void dispatchPresenterUpdate(boolean cleared) {
236 if (mPresenters.isEmpty()) return;
237
Adam Powell640a66e2011-04-29 10:18:53 -0700238 stopDispatchingItemsChanged();
Adam Powell696cba52011-03-29 10:38:16 -0700239 for (WeakReference<MenuPresenter> ref : mPresenters) {
240 final MenuPresenter presenter = ref.get();
241 if (presenter == null) {
242 mPresenters.remove(ref);
243 } else {
244 presenter.updateMenuView(cleared);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800245 }
246 }
Adam Powell640a66e2011-04-29 10:18:53 -0700247 startDispatchingItemsChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800248 }
249
Adam Powellc0cc6802013-12-03 18:58:29 -0800250 private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu,
251 MenuPresenter preferredPresenter) {
Adam Powell696cba52011-03-29 10:38:16 -0700252 if (mPresenters.isEmpty()) return false;
253
254 boolean result = false;
255
Adam Powellc0cc6802013-12-03 18:58:29 -0800256 // Try the preferred presenter first.
257 if (preferredPresenter != null) {
258 result = preferredPresenter.onSubMenuSelected(subMenu);
259 }
260
Adam Powell696cba52011-03-29 10:38:16 -0700261 for (WeakReference<MenuPresenter> ref : mPresenters) {
262 final MenuPresenter presenter = ref.get();
263 if (presenter == null) {
264 mPresenters.remove(ref);
265 } else if (!result) {
266 result = presenter.onSubMenuSelected(subMenu);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800267 }
268 }
Adam Powell696cba52011-03-29 10:38:16 -0700269 return result;
270 }
271
Adam Powell11ed1d62011-07-11 21:19:59 -0700272 private void dispatchSaveInstanceState(Bundle outState) {
273 if (mPresenters.isEmpty()) return;
274
275 SparseArray<Parcelable> presenterStates = new SparseArray<Parcelable>();
276
277 for (WeakReference<MenuPresenter> ref : mPresenters) {
278 final MenuPresenter presenter = ref.get();
279 if (presenter == null) {
280 mPresenters.remove(ref);
281 } else {
282 final int id = presenter.getId();
283 if (id > 0) {
284 final Parcelable state = presenter.onSaveInstanceState();
285 if (state != null) {
286 presenterStates.put(id, state);
287 }
288 }
289 }
290 }
291
292 outState.putSparseParcelableArray(PRESENTER_KEY, presenterStates);
293 }
294
295 private void dispatchRestoreInstanceState(Bundle state) {
296 SparseArray<Parcelable> presenterStates = state.getSparseParcelableArray(PRESENTER_KEY);
297
298 if (presenterStates == null || mPresenters.isEmpty()) return;
299
300 for (WeakReference<MenuPresenter> ref : mPresenters) {
301 final MenuPresenter presenter = ref.get();
302 if (presenter == null) {
303 mPresenters.remove(ref);
304 } else {
305 final int id = presenter.getId();
306 if (id > 0) {
307 Parcelable parcel = presenterStates.get(id);
308 if (parcel != null) {
309 presenter.onRestoreInstanceState(parcel);
310 }
311 }
312 }
313 }
314 }
315
316 public void savePresenterStates(Bundle outState) {
317 dispatchSaveInstanceState(outState);
318 }
319
320 public void restorePresenterStates(Bundle state) {
321 dispatchRestoreInstanceState(state);
322 }
323
Adam Powell038f1c82011-07-21 14:28:10 -0700324 public void saveActionViewStates(Bundle outStates) {
325 SparseArray<Parcelable> viewStates = null;
326
327 final int itemCount = size();
328 for (int i = 0; i < itemCount; i++) {
329 final MenuItem item = getItem(i);
330 final View v = item.getActionView();
331 if (v != null && v.getId() != View.NO_ID) {
332 if (viewStates == null) {
333 viewStates = new SparseArray<Parcelable>();
334 }
335 v.saveHierarchyState(viewStates);
336 if (item.isActionViewExpanded()) {
337 outStates.putInt(EXPANDED_ACTION_VIEW_ID, item.getItemId());
338 }
339 }
340 if (item.hasSubMenu()) {
341 final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
342 subMenu.saveActionViewStates(outStates);
343 }
344 }
345
346 if (viewStates != null) {
347 outStates.putSparseParcelableArray(getActionViewStatesKey(), viewStates);
348 }
349 }
350
351 public void restoreActionViewStates(Bundle states) {
352 if (states == null) {
353 return;
354 }
355
356 SparseArray<Parcelable> viewStates = states.getSparseParcelableArray(
357 getActionViewStatesKey());
358
359 final int itemCount = size();
360 for (int i = 0; i < itemCount; i++) {
361 final MenuItem item = getItem(i);
362 final View v = item.getActionView();
363 if (v != null && v.getId() != View.NO_ID) {
364 v.restoreHierarchyState(viewStates);
365 }
366 if (item.hasSubMenu()) {
367 final SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
368 subMenu.restoreActionViewStates(states);
369 }
370 }
371
372 final int expandedId = states.getInt(EXPANDED_ACTION_VIEW_ID);
373 if (expandedId > 0) {
374 MenuItem itemToExpand = findItem(expandedId);
375 if (itemToExpand != null) {
376 itemToExpand.expandActionView();
377 }
378 }
379 }
380
381 protected String getActionViewStatesKey() {
382 return ACTION_VIEW_STATES_KEY;
383 }
384
Adam Powell696cba52011-03-29 10:38:16 -0700385 public void setCallback(Callback cb) {
386 mCallback = cb;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 }
388
389 /**
390 * Adds an item to the menu. The other add methods funnel to this.
391 */
392 private MenuItem addInternal(int group, int id, int categoryOrder, CharSequence title) {
393 final int ordering = getOrdering(categoryOrder);
394
Deepanshu Gupta10019612014-04-18 12:32:38 -0700395 final MenuItemImpl item = createNewMenuItem(group, id, categoryOrder, ordering, title,
396 mDefaultShowAsAction);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397
398 if (mCurrentMenuInfo != null) {
399 // Pass along the current menu info
400 item.setMenuInfo(mCurrentMenuInfo);
401 }
402
403 mItems.add(findInsertIndex(mItems, ordering), item);
Adam Powell696cba52011-03-29 10:38:16 -0700404 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800405
406 return item;
407 }
Deepanshu Gupta10019612014-04-18 12:32:38 -0700408
409 // Layoutlib overrides this method to return its custom implementation of MenuItemImpl
410 private MenuItemImpl createNewMenuItem(int group, int id, int categoryOrder, int ordering,
411 CharSequence title, int defaultShowAsAction) {
412 return new MenuItemImpl(this, group, id, categoryOrder, ordering, title,
413 defaultShowAsAction);
414 }
415
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800416 public MenuItem add(CharSequence title) {
417 return addInternal(0, 0, 0, title);
418 }
419
420 public MenuItem add(int titleRes) {
421 return addInternal(0, 0, 0, mResources.getString(titleRes));
422 }
423
424 public MenuItem add(int group, int id, int categoryOrder, CharSequence title) {
425 return addInternal(group, id, categoryOrder, title);
426 }
427
428 public MenuItem add(int group, int id, int categoryOrder, int title) {
429 return addInternal(group, id, categoryOrder, mResources.getString(title));
430 }
431
432 public SubMenu addSubMenu(CharSequence title) {
433 return addSubMenu(0, 0, 0, title);
434 }
435
436 public SubMenu addSubMenu(int titleRes) {
437 return addSubMenu(0, 0, 0, mResources.getString(titleRes));
438 }
439
440 public SubMenu addSubMenu(int group, int id, int categoryOrder, CharSequence title) {
441 final MenuItemImpl item = (MenuItemImpl) addInternal(group, id, categoryOrder, title);
442 final SubMenuBuilder subMenu = new SubMenuBuilder(mContext, this, item);
443 item.setSubMenu(subMenu);
444
445 return subMenu;
446 }
447
448 public SubMenu addSubMenu(int group, int id, int categoryOrder, int title) {
449 return addSubMenu(group, id, categoryOrder, mResources.getString(title));
450 }
451
452 public int addIntentOptions(int group, int id, int categoryOrder, ComponentName caller,
453 Intent[] specifics, Intent intent, int flags, MenuItem[] outSpecificItems) {
454 PackageManager pm = mContext.getPackageManager();
455 final List<ResolveInfo> lri =
456 pm.queryIntentActivityOptions(caller, specifics, intent, 0);
457 final int N = lri != null ? lri.size() : 0;
458
459 if ((flags & FLAG_APPEND_TO_GROUP) == 0) {
460 removeGroup(group);
461 }
462
463 for (int i=0; i<N; i++) {
464 final ResolveInfo ri = lri.get(i);
465 Intent rintent = new Intent(
466 ri.specificIndex < 0 ? intent : specifics[ri.specificIndex]);
467 rintent.setComponent(new ComponentName(
468 ri.activityInfo.applicationInfo.packageName,
469 ri.activityInfo.name));
470 final MenuItem item = add(group, id, categoryOrder, ri.loadLabel(pm))
471 .setIcon(ri.loadIcon(pm))
472 .setIntent(rintent);
473 if (outSpecificItems != null && ri.specificIndex >= 0) {
474 outSpecificItems[ri.specificIndex] = item;
475 }
476 }
477
478 return N;
479 }
480
481 public void removeItem(int id) {
482 removeItemAtInt(findItemIndex(id), true);
483 }
484
485 public void removeGroup(int group) {
486 final int i = findGroupIndex(group);
487
488 if (i >= 0) {
489 final int maxRemovable = mItems.size() - i;
490 int numRemoved = 0;
491 while ((numRemoved++ < maxRemovable) && (mItems.get(i).getGroupId() == group)) {
492 // Don't force update for each one, this method will do it at the end
493 removeItemAtInt(i, false);
494 }
495
496 // Notify menu views
Adam Powell696cba52011-03-29 10:38:16 -0700497 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 }
499 }
500
501 /**
502 * Remove the item at the given index and optionally forces menu views to
503 * update.
504 *
505 * @param index The index of the item to be removed. If this index is
506 * invalid an exception is thrown.
507 * @param updateChildrenOnMenuViews Whether to force update on menu views.
508 * Please make sure you eventually call this after your batch of
509 * removals.
510 */
511 private void removeItemAtInt(int index, boolean updateChildrenOnMenuViews) {
512 if ((index < 0) || (index >= mItems.size())) return;
513
514 mItems.remove(index);
515
Adam Powell696cba52011-03-29 10:38:16 -0700516 if (updateChildrenOnMenuViews) onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 }
518
519 public void removeItemAt(int index) {
520 removeItemAtInt(index, true);
521 }
522
523 public void clearAll() {
524 mPreventDispatchingItemsChanged = true;
525 clear();
526 clearHeader();
527 mPreventDispatchingItemsChanged = false;
Adam Powell696cba52011-03-29 10:38:16 -0700528 mItemsChangedWhileDispatchPrevented = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 onItemsChanged(true);
530 }
531
532 public void clear() {
Adam Powell6b0e97c2011-08-03 12:06:32 -0700533 if (mExpandedItem != null) {
534 collapseItemActionView(mExpandedItem);
535 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800536 mItems.clear();
537
538 onItemsChanged(true);
539 }
540
541 void setExclusiveItemChecked(MenuItem item) {
542 final int group = item.getGroupId();
543
544 final int N = mItems.size();
545 for (int i = 0; i < N; i++) {
546 MenuItemImpl curItem = mItems.get(i);
547 if (curItem.getGroupId() == group) {
548 if (!curItem.isExclusiveCheckable()) continue;
549 if (!curItem.isCheckable()) continue;
550
551 // Check the item meant to be checked, uncheck the others (that are in the group)
552 curItem.setCheckedInt(curItem == item);
553 }
554 }
555 }
556
557 public void setGroupCheckable(int group, boolean checkable, boolean exclusive) {
558 final int N = mItems.size();
559
560 for (int i = 0; i < N; i++) {
561 MenuItemImpl item = mItems.get(i);
562 if (item.getGroupId() == group) {
563 item.setExclusiveCheckable(exclusive);
564 item.setCheckable(checkable);
565 }
566 }
567 }
568
569 public void setGroupVisible(int group, boolean visible) {
570 final int N = mItems.size();
571
572 // We handle the notification of items being changed ourselves, so we use setVisibleInt rather
573 // than setVisible and at the end notify of items being changed
574
575 boolean changedAtLeastOneItem = false;
576 for (int i = 0; i < N; i++) {
577 MenuItemImpl item = mItems.get(i);
578 if (item.getGroupId() == group) {
579 if (item.setVisibleInt(visible)) changedAtLeastOneItem = true;
580 }
581 }
582
Adam Powell23f4cc02011-08-18 10:30:46 -0700583 if (changedAtLeastOneItem) onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 }
585
586 public void setGroupEnabled(int group, boolean enabled) {
587 final int N = mItems.size();
588
589 for (int i = 0; i < N; i++) {
590 MenuItemImpl item = mItems.get(i);
591 if (item.getGroupId() == group) {
592 item.setEnabled(enabled);
593 }
594 }
595 }
596
597 public boolean hasVisibleItems() {
598 final int size = size();
599
600 for (int i = 0; i < size; i++) {
601 MenuItemImpl item = mItems.get(i);
602 if (item.isVisible()) {
603 return true;
604 }
605 }
606
607 return false;
608 }
609
610 public MenuItem findItem(int id) {
611 final int size = size();
612 for (int i = 0; i < size; i++) {
613 MenuItemImpl item = mItems.get(i);
614 if (item.getItemId() == id) {
615 return item;
616 } else if (item.hasSubMenu()) {
617 MenuItem possibleItem = item.getSubMenu().findItem(id);
618
619 if (possibleItem != null) {
620 return possibleItem;
621 }
622 }
623 }
624
625 return null;
626 }
627
628 public int findItemIndex(int id) {
629 final int size = size();
630
631 for (int i = 0; i < size; i++) {
632 MenuItemImpl item = mItems.get(i);
633 if (item.getItemId() == id) {
634 return i;
635 }
636 }
637
638 return -1;
639 }
640
641 public int findGroupIndex(int group) {
642 return findGroupIndex(group, 0);
643 }
644
645 public int findGroupIndex(int group, int start) {
646 final int size = size();
647
648 if (start < 0) {
649 start = 0;
650 }
651
652 for (int i = start; i < size; i++) {
653 final MenuItemImpl item = mItems.get(i);
654
655 if (item.getGroupId() == group) {
656 return i;
657 }
658 }
659
660 return -1;
661 }
662
663 public int size() {
664 return mItems.size();
665 }
666
667 /** {@inheritDoc} */
668 public MenuItem getItem(int index) {
669 return mItems.get(index);
670 }
671
672 public boolean isShortcutKey(int keyCode, KeyEvent event) {
673 return findItemWithShortcutForKey(keyCode, event) != null;
674 }
675
676 public void setQwertyMode(boolean isQwerty) {
677 mQwertyMode = isQwerty;
Adam Powell696cba52011-03-29 10:38:16 -0700678
679 onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800680 }
681
682 /**
683 * Returns the ordering across all items. This will grab the category from
684 * the upper bits, find out how to order the category with respect to other
685 * categories, and combine it with the lower bits.
686 *
687 * @param categoryOrder The category order for a particular item (if it has
688 * not been or/add with a category, the default category is
689 * assumed).
690 * @return An ordering integer that can be used to order this item across
691 * all the items (even from other categories).
692 */
Adam Powell696cba52011-03-29 10:38:16 -0700693 private static int getOrdering(int categoryOrder) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
695
696 if (index < 0 || index >= sCategoryToOrder.length) {
697 throw new IllegalArgumentException("order does not contain a valid category.");
698 }
699
700 return (sCategoryToOrder[index] << CATEGORY_SHIFT) | (categoryOrder & USER_MASK);
701 }
702
703 /**
704 * @return whether the menu shortcuts are in qwerty mode or not
705 */
706 boolean isQwertyMode() {
707 return mQwertyMode;
708 }
709
710 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 * Sets whether the shortcuts should be visible on menus. Devices without hardware
712 * key input will never make shortcuts visible even if this method is passed 'true'.
713 *
714 * @param shortcutsVisible Whether shortcuts should be visible (if true and a
715 * menu item does not have a shortcut defined, that item will
716 * still NOT show a shortcut)
717 */
718 public void setShortcutsVisible(boolean shortcutsVisible) {
719 if (mShortcutsVisible == shortcutsVisible) return;
Jeff Brown4aed78b2011-01-14 17:36:55 -0800720
721 setShortcutsVisibleInner(shortcutsVisible);
Adam Powell696cba52011-03-29 10:38:16 -0700722 onItemsChanged(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723 }
724
Jeff Brown4aed78b2011-01-14 17:36:55 -0800725 private void setShortcutsVisibleInner(boolean shortcutsVisible) {
726 mShortcutsVisible = shortcutsVisible
727 && mResources.getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS
728 && mResources.getBoolean(
729 com.android.internal.R.bool.config_showMenuShortcutsWhenKeyboardPresent);
730 }
731
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732 /**
733 * @return Whether shortcuts should be visible on menus.
734 */
735 public boolean isShortcutsVisible() {
736 return mShortcutsVisible;
737 }
738
739 Resources getResources() {
740 return mResources;
741 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742
743 public Context getContext() {
744 return mContext;
745 }
746
Adam Powell696cba52011-03-29 10:38:16 -0700747 boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
748 return mCallback != null && mCallback.onMenuItemSelected(menu, item);
749 }
750
751 /**
752 * Dispatch a mode change event to this menu's callback.
753 */
754 public void changeMenuMode() {
755 if (mCallback != null) {
756 mCallback.onMenuModeChange(this);
757 }
758 }
759
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800760 private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
761 for (int i = items.size() - 1; i >= 0; i--) {
762 MenuItemImpl item = items.get(i);
763 if (item.getOrdering() <= ordering) {
764 return i + 1;
765 }
766 }
767
768 return 0;
769 }
770
771 public boolean performShortcut(int keyCode, KeyEvent event, int flags) {
772 final MenuItemImpl item = findItemWithShortcutForKey(keyCode, event);
773
774 boolean handled = false;
775
776 if (item != null) {
777 handled = performItemAction(item, flags);
778 }
779
780 if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
781 close(true);
782 }
783
784 return handled;
785 }
786
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100787 /*
788 * This function will return all the menu and sub-menu items that can
789 * be directly (the shortcut directly corresponds) and indirectly
790 * (the ALT-enabled char corresponds to the shortcut) associated
791 * with the keyCode.
792 */
Adam Powell696cba52011-03-29 10:38:16 -0700793 void findItemsWithShortcutForKey(List<MenuItemImpl> items, int keyCode, KeyEvent event) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800794 final boolean qwerty = isQwertyMode();
795 final int metaState = event.getMetaState();
796 final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
797 // Get the chars associated with the keyCode (i.e using any chording combo)
798 final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
799 // The delete key is not mapped to '\b' so we treat it specially
800 if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
Adam Powell696cba52011-03-29 10:38:16 -0700801 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800802 }
803
804 // Look for an item whose shortcut is this key.
805 final int N = mItems.size();
806 for (int i = 0; i < N; i++) {
807 MenuItemImpl item = mItems.get(i);
808 if (item.hasSubMenu()) {
Adam Powell696cba52011-03-29 10:38:16 -0700809 ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800810 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100811 final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
812 if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
813 (shortcutChar != 0) &&
814 (shortcutChar == possibleChars.meta[0]
815 || shortcutChar == possibleChars.meta[2]
816 || (qwerty && shortcutChar == '\b' &&
817 keyCode == KeyEvent.KEYCODE_DEL)) &&
818 item.isEnabled()) {
819 items.add(item);
820 }
821 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100822 }
823
824 /*
825 * We want to return the menu item associated with the key, but if there is no
826 * ambiguity (i.e. there is only one menu item corresponding to the key) we want
827 * to return it even if it's not an exact match; this allow the user to
828 * _not_ use the ALT key for example, making the use of shortcuts slightly more
829 * user-friendly. An example is on the G1, '!' and '1' are on the same key, and
830 * in Gmail, Menu+1 will trigger Menu+! (the actual shortcut).
831 *
832 * On the other hand, if two (or more) shortcuts corresponds to the same key,
833 * we have to only return the exact match.
834 */
835 MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
836 // Get all items that can be associated directly or indirectly with the keyCode
Adam Powell696cba52011-03-29 10:38:16 -0700837 ArrayList<MenuItemImpl> items = mTempShortcutItemList;
838 items.clear();
839 findItemsWithShortcutForKey(items, keyCode, event);
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100840
Adam Powell696cba52011-03-29 10:38:16 -0700841 if (items.isEmpty()) {
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100842 return null;
843 }
844
845 final int metaState = event.getMetaState();
846 final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
847 // Get the chars associated with the keyCode (i.e using any chording combo)
848 event.getKeyData(possibleChars);
849
850 // If we have only one element, we can safely returns it
Adam Powell696cba52011-03-29 10:38:16 -0700851 final int size = items.size();
852 if (size == 1) {
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100853 return items.get(0);
854 }
855
856 final boolean qwerty = isQwertyMode();
857 // If we found more than one item associated with the key,
858 // we have to return the exact match
Adam Powell696cba52011-03-29 10:38:16 -0700859 for (int i = 0; i < size; i++) {
860 final MenuItemImpl item = items.get(i);
861 final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
862 item.getNumericShortcut();
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100863 if ((shortcutChar == possibleChars.meta[0] &&
864 (metaState & KeyEvent.META_ALT_ON) == 0)
865 || (shortcutChar == possibleChars.meta[2] &&
866 (metaState & KeyEvent.META_ALT_ON) != 0)
867 || (qwerty && shortcutChar == '\b' &&
868 keyCode == KeyEvent.KEYCODE_DEL)) {
869 return item;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800870 }
871 }
872 return null;
873 }
Nicolas Roarde0fc8382009-09-24 21:30:06 +0100874
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 public boolean performIdentifierAction(int id, int flags) {
876 // Look for an item whose identifier is the id.
877 return performItemAction(findItem(id), flags);
878 }
879
880 public boolean performItemAction(MenuItem item, int flags) {
Adam Powellc0cc6802013-12-03 18:58:29 -0800881 return performItemAction(item, null, flags);
882 }
883
884 public boolean performItemAction(MenuItem item, MenuPresenter preferredPresenter, int flags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800885 MenuItemImpl itemImpl = (MenuItemImpl) item;
886
887 if (itemImpl == null || !itemImpl.isEnabled()) {
888 return false;
Adam Powell8d02dea2011-05-31 21:35:13 -0700889 }
Svetoslav Ganov76559a62011-07-06 17:17:52 -0700890
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800891 boolean invoked = itemImpl.invoke();
892
Adam Powellf77f4802012-05-14 16:44:43 -0700893 final ActionProvider provider = item.getActionProvider();
894 final boolean providerHasSubMenu = provider != null && provider.hasSubMenu();
Adam Powell8d02dea2011-05-31 21:35:13 -0700895 if (itemImpl.hasCollapsibleActionView()) {
896 invoked |= itemImpl.expandActionView();
897 if (invoked) close(true);
Adam Powellf77f4802012-05-14 16:44:43 -0700898 } else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 close(false);
900
Adam Powellf77f4802012-05-14 16:44:43 -0700901 if (!itemImpl.hasSubMenu()) {
902 itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
903 }
904
905 final SubMenuBuilder subMenu = (SubMenuBuilder) itemImpl.getSubMenu();
906 if (providerHasSubMenu) {
Adam Powell961dd112011-07-12 14:25:23 -0700907 provider.onPrepareSubMenu(subMenu);
908 }
Adam Powellc0cc6802013-12-03 18:58:29 -0800909 invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter);
Adam Powell696cba52011-03-29 10:38:16 -0700910 if (!invoked) close(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800911 } else {
912 if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
913 close(true);
914 }
915 }
916
917 return invoked;
918 }
919
920 /**
921 * Closes the visible menu.
922 *
923 * @param allMenusAreClosing Whether the menus are completely closing (true),
924 * or whether there is another menu coming in this menu's place
925 * (false). For example, if the menu is closing because a
926 * sub menu is about to be shown, <var>allMenusAreClosing</var>
927 * is false.
928 */
Adam Powellfa18d182014-01-07 15:56:59 -0800929 public final void close(boolean allMenusAreClosing) {
Adam Powell696cba52011-03-29 10:38:16 -0700930 if (mIsClosing) return;
931
932 mIsClosing = true;
933 for (WeakReference<MenuPresenter> ref : mPresenters) {
934 final MenuPresenter presenter = ref.get();
935 if (presenter == null) {
936 mPresenters.remove(ref);
937 } else {
938 presenter.onCloseMenu(this, allMenusAreClosing);
939 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800940 }
Adam Powell696cba52011-03-29 10:38:16 -0700941 mIsClosing = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800942 }
943
944 /** {@inheritDoc} */
945 public void close() {
946 close(true);
947 }
948
949 /**
950 * Called when an item is added or removed.
951 *
Adam Powell696cba52011-03-29 10:38:16 -0700952 * @param structureChanged true if the menu structure changed,
953 * false if only item properties changed.
Adam Powell23f4cc02011-08-18 10:30:46 -0700954 * (Visibility is a structural property since it affects layout.)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800955 */
Adam Powellfa18d182014-01-07 15:56:59 -0800956 public void onItemsChanged(boolean structureChanged) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800957 if (!mPreventDispatchingItemsChanged) {
Adam Powell696cba52011-03-29 10:38:16 -0700958 if (structureChanged) {
959 mIsVisibleItemsStale = true;
960 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800961 }
Adam Powell696cba52011-03-29 10:38:16 -0700962
963 dispatchPresenterUpdate(structureChanged);
964 } else {
965 mItemsChangedWhileDispatchPrevented = true;
966 }
967 }
968
969 /**
970 * Stop dispatching item changed events to presenters until
971 * {@link #startDispatchingItemsChanged()} is called. Useful when
972 * many menu operations are going to be performed as a batch.
973 */
974 public void stopDispatchingItemsChanged() {
Adam Powella86b3502011-04-21 14:49:23 -0700975 if (!mPreventDispatchingItemsChanged) {
976 mPreventDispatchingItemsChanged = true;
977 mItemsChangedWhileDispatchPrevented = false;
978 }
Adam Powell696cba52011-03-29 10:38:16 -0700979 }
980
981 public void startDispatchingItemsChanged() {
982 mPreventDispatchingItemsChanged = false;
983
984 if (mItemsChangedWhileDispatchPrevented) {
985 mItemsChangedWhileDispatchPrevented = false;
986 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800987 }
988 }
989
990 /**
991 * Called by {@link MenuItemImpl} when its visible flag is changed.
992 * @param item The item that has gone through a visibility change.
993 */
994 void onItemVisibleChanged(MenuItemImpl item) {
995 // Notify of items being changed
Adam Powell696cba52011-03-29 10:38:16 -0700996 mIsVisibleItemsStale = true;
Adam Powell23f4cc02011-08-18 10:30:46 -0700997 onItemsChanged(true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800998 }
999
Adam Powell96675b12010-06-10 18:58:59 -07001000 /**
1001 * Called by {@link MenuItemImpl} when its action request status is changed.
1002 * @param item The item that has gone through a change in action request status.
1003 */
1004 void onItemActionRequestChanged(MenuItemImpl item) {
1005 // Notify of items being changed
Adam Powell696cba52011-03-29 10:38:16 -07001006 mIsActionItemsStale = true;
Adam Powell23f4cc02011-08-18 10:30:46 -07001007 onItemsChanged(true);
Adam Powell96675b12010-06-10 18:58:59 -07001008 }
1009
Adam Powellfa18d182014-01-07 15:56:59 -08001010 public ArrayList<MenuItemImpl> getVisibleItems() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001011 if (!mIsVisibleItemsStale) return mVisibleItems;
1012
1013 // Refresh the visible items
1014 mVisibleItems.clear();
1015
1016 final int itemsSize = mItems.size();
1017 MenuItemImpl item;
1018 for (int i = 0; i < itemsSize; i++) {
1019 item = mItems.get(i);
1020 if (item.isVisible()) mVisibleItems.add(item);
1021 }
1022
1023 mIsVisibleItemsStale = false;
Adam Powell96675b12010-06-10 18:58:59 -07001024 mIsActionItemsStale = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001025
1026 return mVisibleItems;
1027 }
Adam Powell36fced92011-01-16 15:48:07 -08001028
1029 /**
1030 * This method determines which menu items get to be 'action items' that will appear
1031 * in an action bar and which items should be 'overflow items' in a secondary menu.
1032 * The rules are as follows:
1033 *
1034 * <p>Items are considered for inclusion in the order specified within the menu.
1035 * There is a limit of mMaxActionItems as a total count, optionally including the overflow
1036 * menu button itself. This is a soft limit; if an item shares a group ID with an item
1037 * previously included as an action item, the new item will stay with its group and become
1038 * an action item itself even if it breaks the max item count limit. This is done to
1039 * limit the conceptual complexity of the items presented within an action bar. Only a few
1040 * unrelated concepts should be presented to the user in this space, and groups are treated
1041 * as a single concept.
1042 *
1043 * <p>There is also a hard limit of consumed measurable space: mActionWidthLimit. This
1044 * limit may be broken by a single item that exceeds the remaining space, but no further
1045 * items may be added. If an item that is part of a group cannot fit within the remaining
1046 * measured width, the entire group will be demoted to overflow. This is done to ensure room
1047 * for navigation and other affordances in the action bar as well as reduce general UI clutter.
1048 *
1049 * <p>The space freed by demoting a full group cannot be consumed by future menu items.
1050 * Once items begin to overflow, all future items become overflow items as well. This is
1051 * to avoid inadvertent reordering that may break the app's intended design.
Adam Powell36fced92011-01-16 15:48:07 -08001052 */
Adam Powell696cba52011-03-29 10:38:16 -07001053 public void flagActionItems() {
Adam Powellda971082013-10-03 18:21:58 -07001054 // Important side effect: if getVisibleItems is stale it may refresh,
1055 // which can affect action items staleness.
1056 final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
1057
Adam Powell96675b12010-06-10 18:58:59 -07001058 if (!mIsActionItemsStale) {
1059 return;
1060 }
Adam Powell8028dd32010-07-15 10:16:33 -07001061
Adam Powell696cba52011-03-29 10:38:16 -07001062 // Presenters flag action items as needed.
1063 boolean flagged = false;
1064 for (WeakReference<MenuPresenter> ref : mPresenters) {
1065 final MenuPresenter presenter = ref.get();
1066 if (presenter == null) {
1067 mPresenters.remove(ref);
Adam Powell8028dd32010-07-15 10:16:33 -07001068 } else {
Adam Powell696cba52011-03-29 10:38:16 -07001069 flagged |= presenter.flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001070 }
1071 }
Adam Powell8028dd32010-07-15 10:16:33 -07001072
Adam Powell696cba52011-03-29 10:38:16 -07001073 if (flagged) {
1074 mActionItems.clear();
1075 mNonActionItems.clear();
Adam Powell696cba52011-03-29 10:38:16 -07001076 final int itemsSize = visibleItems.size();
1077 for (int i = 0; i < itemsSize; i++) {
1078 MenuItemImpl item = visibleItems.get(i);
1079 if (item.isActionButton()) {
1080 mActionItems.add(item);
1081 } else {
1082 mNonActionItems.add(item);
Adam Powell36fced92011-01-16 15:48:07 -08001083 }
Adam Powell96675b12010-06-10 18:58:59 -07001084 }
Adam Powell04dc06f2011-08-22 18:11:11 -07001085 } else {
1086 // Nobody flagged anything, everything is a non-action item.
Adam Powell696cba52011-03-29 10:38:16 -07001087 // (This happens during a first pass with no action-item presenters.)
1088 mActionItems.clear();
1089 mNonActionItems.clear();
1090 mNonActionItems.addAll(getVisibleItems());
Adam Powell96675b12010-06-10 18:58:59 -07001091 }
Adam Powell96675b12010-06-10 18:58:59 -07001092 mIsActionItemsStale = false;
1093 }
1094
Adam Powellfa18d182014-01-07 15:56:59 -08001095 public ArrayList<MenuItemImpl> getActionItems() {
Adam Powell696cba52011-03-29 10:38:16 -07001096 flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001097 return mActionItems;
1098 }
1099
Adam Powellfa18d182014-01-07 15:56:59 -08001100 public ArrayList<MenuItemImpl> getNonActionItems() {
Adam Powell696cba52011-03-29 10:38:16 -07001101 flagActionItems();
Adam Powell96675b12010-06-10 18:58:59 -07001102 return mNonActionItems;
1103 }
Adam Powell36fced92011-01-16 15:48:07 -08001104
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001105 public void clearHeader() {
1106 mHeaderIcon = null;
1107 mHeaderTitle = null;
1108 mHeaderView = null;
1109
1110 onItemsChanged(false);
1111 }
1112
1113 private void setHeaderInternal(final int titleRes, final CharSequence title, final int iconRes,
1114 final Drawable icon, final View view) {
1115 final Resources r = getResources();
1116
1117 if (view != null) {
1118 mHeaderView = view;
1119
1120 // If using a custom view, then the title and icon aren't used
1121 mHeaderTitle = null;
1122 mHeaderIcon = null;
1123 } else {
1124 if (titleRes > 0) {
1125 mHeaderTitle = r.getText(titleRes);
1126 } else if (title != null) {
1127 mHeaderTitle = title;
1128 }
1129
1130 if (iconRes > 0) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -08001131 mHeaderIcon = getContext().getDrawable(iconRes);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001132 } else if (icon != null) {
1133 mHeaderIcon = icon;
1134 }
1135
1136 // If using the title or icon, then a custom view isn't used
1137 mHeaderView = null;
1138 }
1139
1140 // Notify of change
1141 onItemsChanged(false);
1142 }
1143
1144 /**
1145 * Sets the header's title. This replaces the header view. Called by the
1146 * builder-style methods of subclasses.
1147 *
1148 * @param title The new title.
1149 * @return This MenuBuilder so additional setters can be called.
1150 */
1151 protected MenuBuilder setHeaderTitleInt(CharSequence title) {
1152 setHeaderInternal(0, title, 0, null, null);
1153 return this;
1154 }
1155
1156 /**
1157 * Sets the header's title. This replaces the header view. Called by the
1158 * builder-style methods of subclasses.
1159 *
1160 * @param titleRes The new title (as a resource ID).
1161 * @return This MenuBuilder so additional setters can be called.
1162 */
1163 protected MenuBuilder setHeaderTitleInt(int titleRes) {
1164 setHeaderInternal(titleRes, null, 0, null, null);
1165 return this;
1166 }
1167
1168 /**
1169 * Sets the header's icon. This replaces the header view. Called by the
1170 * builder-style methods of subclasses.
1171 *
1172 * @param icon The new icon.
1173 * @return This MenuBuilder so additional setters can be called.
1174 */
1175 protected MenuBuilder setHeaderIconInt(Drawable icon) {
1176 setHeaderInternal(0, null, 0, icon, null);
1177 return this;
1178 }
1179
1180 /**
1181 * Sets the header's icon. This replaces the header view. Called by the
1182 * builder-style methods of subclasses.
1183 *
1184 * @param iconRes The new icon (as a resource ID).
1185 * @return This MenuBuilder so additional setters can be called.
1186 */
1187 protected MenuBuilder setHeaderIconInt(int iconRes) {
1188 setHeaderInternal(0, null, iconRes, null, null);
1189 return this;
1190 }
1191
1192 /**
1193 * Sets the header's view. This replaces the title and icon. Called by the
1194 * builder-style methods of subclasses.
1195 *
1196 * @param view The new view.
1197 * @return This MenuBuilder so additional setters can be called.
1198 */
1199 protected MenuBuilder setHeaderViewInt(View view) {
1200 setHeaderInternal(0, null, 0, null, view);
1201 return this;
1202 }
1203
1204 public CharSequence getHeaderTitle() {
1205 return mHeaderTitle;
1206 }
1207
1208 public Drawable getHeaderIcon() {
1209 return mHeaderIcon;
1210 }
1211
1212 public View getHeaderView() {
1213 return mHeaderView;
1214 }
1215
1216 /**
1217 * Gets the root menu (if this is a submenu, find its root menu).
1218 * @return The root menu.
1219 */
1220 public MenuBuilder getRootMenu() {
1221 return this;
1222 }
1223
1224 /**
1225 * Sets the current menu info that is set on all items added to this menu
1226 * (until this is called again with different menu info, in which case that
1227 * one will be added to all subsequent item additions).
1228 *
1229 * @param menuInfo The extra menu information to add.
1230 */
1231 public void setCurrentMenuInfo(ContextMenuInfo menuInfo) {
1232 mCurrentMenuInfo = menuInfo;
1233 }
1234
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001235 void setOptionalIconsVisible(boolean visible) {
1236 mOptionalIconsVisible = visible;
1237 }
1238
1239 boolean getOptionalIconsVisible() {
1240 return mOptionalIconsVisible;
1241 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001242
1243 public boolean expandItemActionView(MenuItemImpl item) {
1244 if (mPresenters.isEmpty()) return false;
1245
1246 boolean expanded = false;
1247
1248 stopDispatchingItemsChanged();
1249 for (WeakReference<MenuPresenter> ref : mPresenters) {
1250 final MenuPresenter presenter = ref.get();
1251 if (presenter == null) {
1252 mPresenters.remove(ref);
1253 } else if ((expanded = presenter.expandItemActionView(this, item))) {
1254 break;
1255 }
1256 }
1257 startDispatchingItemsChanged();
1258
Adam Powell6b0e97c2011-08-03 12:06:32 -07001259 if (expanded) {
1260 mExpandedItem = item;
1261 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001262 return expanded;
1263 }
1264
1265 public boolean collapseItemActionView(MenuItemImpl item) {
Adam Powell6b0e97c2011-08-03 12:06:32 -07001266 if (mPresenters.isEmpty() || mExpandedItem != item) return false;
Adam Powell8d02dea2011-05-31 21:35:13 -07001267
1268 boolean collapsed = false;
1269
1270 stopDispatchingItemsChanged();
1271 for (WeakReference<MenuPresenter> ref : mPresenters) {
1272 final MenuPresenter presenter = ref.get();
1273 if (presenter == null) {
1274 mPresenters.remove(ref);
1275 } else if ((collapsed = presenter.collapseItemActionView(this, item))) {
1276 break;
1277 }
1278 }
1279 startDispatchingItemsChanged();
1280
Adam Powell6b0e97c2011-08-03 12:06:32 -07001281 if (collapsed) {
1282 mExpandedItem = null;
1283 }
Adam Powell8d02dea2011-05-31 21:35:13 -07001284 return collapsed;
1285 }
Adam Powell275702c2011-09-23 17:34:04 -07001286
1287 public MenuItemImpl getExpandedItem() {
1288 return mExpandedItem;
1289 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001290}