blob: fe1cf72cb88422c45a8c2a84a53e4332f1a12c01 [file] [log] [blame]
Adam Powell696cba52011-03-29 10:38:16 -07001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.view.menu;
18
Adam Powell696cba52011-03-29 10:38:16 -070019import android.content.Context;
Adam Powellbfcdfaf2011-08-19 11:46:59 -070020import android.content.res.Configuration;
Adam Powell696cba52011-03-29 10:38:16 -070021import android.content.res.Resources;
Adam Powell11ed1d62011-07-11 21:19:59 -070022import android.os.Parcel;
23import android.os.Parcelable;
Adam Powellad79b902013-06-19 11:33:28 -070024import android.transition.Transition;
25import android.transition.TransitionManager;
Adam Powell696cba52011-03-29 10:38:16 -070026import android.util.SparseBooleanArray;
Adam Powell823f0742011-09-21 17:17:01 -070027import android.view.ActionProvider;
Adam Powell54c94de2013-09-26 15:36:34 -070028import android.view.Gravity;
Adam Powell696cba52011-03-29 10:38:16 -070029import android.view.MenuItem;
30import android.view.SoundEffectConstants;
31import android.view.View;
32import android.view.View.MeasureSpec;
33import android.view.ViewGroup;
Adam Powellad79b902013-06-19 11:33:28 -070034import android.view.accessibility.AccessibilityNodeInfo;
Adam Powell696cba52011-03-29 10:38:16 -070035import android.widget.ImageButton;
Alan Viveretteca6a36112013-08-16 14:41:06 -070036import android.widget.ListPopupWindow;
37import android.widget.ListPopupWindow.ForwardingListener;
Adam Powellad79b902013-06-19 11:33:28 -070038import com.android.internal.transition.ActionBarTransition;
Alan Viverette80e72702013-07-29 11:12:59 -070039import com.android.internal.view.ActionBarPolicy;
40import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
Adam Powell696cba52011-03-29 10:38:16 -070041
42import java.util.ArrayList;
43
44/**
45 * MenuPresenter for building action menus as seen in the action bar and action modes.
46 */
Adam Powell823f0742011-09-21 17:17:01 -070047public class ActionMenuPresenter extends BaseMenuPresenter
48 implements ActionProvider.SubUiVisibilityListener {
Adam Powell640a66e2011-04-29 10:18:53 -070049 private static final String TAG = "ActionMenuPresenter";
50
Adam Powell696cba52011-03-29 10:38:16 -070051 private View mOverflowButton;
52 private boolean mReserveOverflow;
Adam Powell1ab418a2011-06-09 20:49:49 -070053 private boolean mReserveOverflowSet;
Adam Powell696cba52011-03-29 10:38:16 -070054 private int mWidthLimit;
55 private int mActionItemWidthLimit;
56 private int mMaxItems;
Adam Powell1ab418a2011-06-09 20:49:49 -070057 private boolean mMaxItemsSet;
Adam Powell640a66e2011-04-29 10:18:53 -070058 private boolean mStrictWidthLimit;
Adam Powell1ab418a2011-06-09 20:49:49 -070059 private boolean mWidthLimitSet;
Adam Powellb187cd92011-07-20 14:17:56 -070060 private boolean mExpandedActionViewsExclusive;
Adam Powell696cba52011-03-29 10:38:16 -070061
Adam Powell35aecd52011-07-01 13:43:49 -070062 private int mMinCellSize;
63
Adam Powell696cba52011-03-29 10:38:16 -070064 // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
65 private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
66
67 private View mScrapActionButtonView;
68
69 private OverflowPopup mOverflowPopup;
70 private ActionButtonSubmenu mActionButtonPopup;
71
72 private OpenOverflowRunnable mPostedOpenRunnable;
73
Adam Powell11ed1d62011-07-11 21:19:59 -070074 final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
75 int mOpenSubMenuId;
76
Adam Powell538e5652011-10-11 13:47:08 -070077 public ActionMenuPresenter(Context context) {
78 super(context, com.android.internal.R.layout.action_menu_layout,
Adam Powell696cba52011-03-29 10:38:16 -070079 com.android.internal.R.layout.action_menu_item_layout);
80 }
81
82 @Override
83 public void initForMenu(Context context, MenuBuilder menu) {
84 super.initForMenu(context, menu);
85
86 final Resources res = context.getResources();
Adam Powell1ab418a2011-06-09 20:49:49 -070087
Adam Powellb8139af2012-04-19 13:52:46 -070088 final ActionBarPolicy abp = ActionBarPolicy.get(context);
Adam Powell1ab418a2011-06-09 20:49:49 -070089 if (!mReserveOverflowSet) {
Adam Powellb8139af2012-04-19 13:52:46 -070090 mReserveOverflow = abp.showsOverflowMenuButton();
Adam Powell1ab418a2011-06-09 20:49:49 -070091 }
92
93 if (!mWidthLimitSet) {
Adam Powellb8139af2012-04-19 13:52:46 -070094 mWidthLimit = abp.getEmbeddedMenuWidthLimit();
Adam Powell1ab418a2011-06-09 20:49:49 -070095 }
Adam Powell696cba52011-03-29 10:38:16 -070096
97 // Measure for initial configuration
Adam Powell1ab418a2011-06-09 20:49:49 -070098 if (!mMaxItemsSet) {
Adam Powellb8139af2012-04-19 13:52:46 -070099 mMaxItems = abp.getMaxActionButtons();
Adam Powell1ab418a2011-06-09 20:49:49 -0700100 }
Adam Powell696cba52011-03-29 10:38:16 -0700101
102 int width = mWidthLimit;
103 if (mReserveOverflow) {
Adam Powell9b4bee02011-04-27 19:24:47 -0700104 if (mOverflowButton == null) {
Adam Powell538e5652011-10-11 13:47:08 -0700105 mOverflowButton = new OverflowMenuButton(mSystemContext);
Adam Powell9b4bee02011-04-27 19:24:47 -0700106 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
107 mOverflowButton.measure(spec, spec);
108 }
Adam Powell696cba52011-03-29 10:38:16 -0700109 width -= mOverflowButton.getMeasuredWidth();
110 } else {
111 mOverflowButton = null;
112 }
113
114 mActionItemWidthLimit = width;
115
Adam Powell35aecd52011-07-01 13:43:49 -0700116 mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
117
Adam Powell696cba52011-03-29 10:38:16 -0700118 // Drop a scrap view as it may no longer reflect the proper context/config.
119 mScrapActionButtonView = null;
120 }
121
Adam Powellbfcdfaf2011-08-19 11:46:59 -0700122 public void onConfigurationChanged(Configuration newConfig) {
123 if (!mMaxItemsSet) {
124 mMaxItems = mContext.getResources().getInteger(
125 com.android.internal.R.integer.max_action_buttons);
Adam Powellf203e0a2012-03-06 17:40:12 -0800126 }
127 if (mMenu != null) {
128 mMenu.onItemsChanged(true);
Adam Powellbfcdfaf2011-08-19 11:46:59 -0700129 }
130 }
131
Adam Powell640a66e2011-04-29 10:18:53 -0700132 public void setWidthLimit(int width, boolean strict) {
Adam Powell1ab418a2011-06-09 20:49:49 -0700133 mWidthLimit = width;
Adam Powell640a66e2011-04-29 10:18:53 -0700134 mStrictWidthLimit = strict;
Adam Powell1ab418a2011-06-09 20:49:49 -0700135 mWidthLimitSet = true;
136 }
137
138 public void setReserveOverflow(boolean reserveOverflow) {
139 mReserveOverflow = reserveOverflow;
140 mReserveOverflowSet = true;
Adam Powell9b4bee02011-04-27 19:24:47 -0700141 }
142
143 public void setItemLimit(int itemCount) {
144 mMaxItems = itemCount;
Adam Powell1ab418a2011-06-09 20:49:49 -0700145 mMaxItemsSet = true;
Adam Powell9b4bee02011-04-27 19:24:47 -0700146 }
147
Adam Powellb187cd92011-07-20 14:17:56 -0700148 public void setExpandedActionViewsExclusive(boolean isExclusive) {
149 mExpandedActionViewsExclusive = isExclusive;
150 }
151
Adam Powell696cba52011-03-29 10:38:16 -0700152 @Override
153 public MenuView getMenuView(ViewGroup root) {
154 MenuView result = super.getMenuView(root);
155 ((ActionMenuView) result).setPresenter(this);
156 return result;
157 }
158
159 @Override
Alan Viverettefbe4a582013-09-06 13:45:54 -0700160 public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
Adam Powell8d02dea2011-05-31 21:35:13 -0700161 View actionView = item.getActionView();
Adam Powell35aecd52011-07-01 13:43:49 -0700162 if (actionView == null || item.hasCollapsibleActionView()) {
Adam Powellc3ca3ea2013-10-10 18:07:48 -0700163 actionView = super.getItemView(item, convertView, parent);
Adam Powell35aecd52011-07-01 13:43:49 -0700164 }
Adam Powell8d02dea2011-05-31 21:35:13 -0700165 actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
Adam Powell35aecd52011-07-01 13:43:49 -0700166
Adam Powellefc3bb02014-01-10 10:16:27 -0800167 final ActionMenuView menuParent = (ActionMenuView) parent;
168 final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
169 if (!menuParent.checkLayoutParams(lp)) {
170 actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
171 }
172 return actionView;
173 }
174
175 @Override
176 public void bindItemView(final MenuItemImpl item, MenuView.ItemView itemView) {
177 itemView.initialize(item, 0);
178
179 final ActionMenuView menuView = (ActionMenuView) mMenuView;
180 final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
181 actionItemView.setItemInvoker(menuView);
182
Alan Viverettefbe4a582013-09-06 13:45:54 -0700183 if (item.hasSubMenu()) {
Adam Powellefc3bb02014-01-10 10:16:27 -0800184 actionItemView.setOnTouchListener(new ForwardingListener(actionItemView) {
Alan Viverettefbe4a582013-09-06 13:45:54 -0700185 @Override
186 public ListPopupWindow getPopup() {
187 return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
188 }
189
190 @Override
191 protected boolean onForwardingStarted() {
192 return onSubMenuSelected((SubMenuBuilder) item.getSubMenu());
193 }
194
195 @Override
196 protected boolean onForwardingStopped() {
197 return dismissPopupMenus();
198 }
199 });
200 } else {
Adam Powellefc3bb02014-01-10 10:16:27 -0800201 actionItemView.setOnTouchListener(null);
Alan Viverettefbe4a582013-09-06 13:45:54 -0700202 }
Adam Powell696cba52011-03-29 10:38:16 -0700203 }
204
205 @Override
206 public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
207 return item.isActionButton();
208 }
209
210 @Override
211 public void updateMenuView(boolean cleared) {
Adam Powellad79b902013-06-19 11:33:28 -0700212 final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
213 if (menuViewParent != null) {
Chet Haased8d7c382013-09-23 11:26:36 -0700214 ActionBarTransition.beginDelayedTransition(menuViewParent);
Adam Powellad79b902013-06-19 11:33:28 -0700215 }
Adam Powell696cba52011-03-29 10:38:16 -0700216 super.updateMenuView(cleared);
217
Adam Powellda971082013-10-03 18:21:58 -0700218 ((View) mMenuView).requestLayout();
219
Adam Powell823f0742011-09-21 17:17:01 -0700220 if (mMenu != null) {
221 final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
222 final int count = actionItems.size();
223 for (int i = 0; i < count; i++) {
224 final ActionProvider provider = actionItems.get(i).getActionProvider();
225 if (provider != null) {
226 provider.setSubUiVisibilityListener(this);
227 }
228 }
229 }
230
Adam Powell275702c2011-09-23 17:34:04 -0700231 final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
232 mMenu.getNonActionItems() : null;
233
234 boolean hasOverflow = false;
235 if (mReserveOverflow && nonActionItems != null) {
236 final int count = nonActionItems.size();
237 if (count == 1) {
238 hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
239 } else {
240 hasOverflow = count > 0;
241 }
242 }
243
Adam Powell14b7e2c2011-08-12 11:11:50 -0700244 if (hasOverflow) {
Adam Powell696cba52011-03-29 10:38:16 -0700245 if (mOverflowButton == null) {
Adam Powell538e5652011-10-11 13:47:08 -0700246 mOverflowButton = new OverflowMenuButton(mSystemContext);
Adam Powell696cba52011-03-29 10:38:16 -0700247 }
248 ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
249 if (parent != mMenuView) {
250 if (parent != null) {
251 parent.removeView(mOverflowButton);
252 }
Adam Powell35aecd52011-07-01 13:43:49 -0700253 ActionMenuView menuView = (ActionMenuView) mMenuView;
254 menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
Adam Powell696cba52011-03-29 10:38:16 -0700255 }
256 } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
257 ((ViewGroup) mMenuView).removeView(mOverflowButton);
258 }
Adam Powell14b7e2c2011-08-12 11:11:50 -0700259
260 ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
Adam Powell696cba52011-03-29 10:38:16 -0700261 }
262
263 @Override
264 public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
265 if (parent.getChildAt(childIndex) == mOverflowButton) return false;
266 return super.filterLeftoverView(parent, childIndex);
267 }
268
269 public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
270 if (!subMenu.hasVisibleItems()) return false;
271
272 SubMenuBuilder topSubMenu = subMenu;
273 while (topSubMenu.getParentMenu() != mMenu) {
274 topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
275 }
276 View anchor = findViewForItem(topSubMenu.getItem());
Adam Powell11ed1d62011-07-11 21:19:59 -0700277 if (anchor == null) {
278 if (mOverflowButton == null) return false;
279 anchor = mOverflowButton;
280 }
Adam Powell696cba52011-03-29 10:38:16 -0700281
Adam Powell11ed1d62011-07-11 21:19:59 -0700282 mOpenSubMenuId = subMenu.getItem().getItemId();
Adam Powell696cba52011-03-29 10:38:16 -0700283 mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
284 mActionButtonPopup.setAnchorView(anchor);
285 mActionButtonPopup.show();
286 super.onSubMenuSelected(subMenu);
287 return true;
288 }
289
290 private View findViewForItem(MenuItem item) {
291 final ViewGroup parent = (ViewGroup) mMenuView;
292 if (parent == null) return null;
293
294 final int count = parent.getChildCount();
295 for (int i = 0; i < count; i++) {
296 final View child = parent.getChildAt(i);
297 if (child instanceof MenuView.ItemView &&
298 ((MenuView.ItemView) child).getItemData() == item) {
299 return child;
300 }
301 }
302 return null;
303 }
304
305 /**
306 * Display the overflow menu if one is present.
307 * @return true if the overflow menu was shown, false otherwise.
308 */
309 public boolean showOverflowMenu() {
Adam Powell70e9f4b2011-08-22 16:31:24 -0700310 if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
Jake Whartona6476402012-03-29 01:42:37 -0700311 mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
Adam Powell696cba52011-03-29 10:38:16 -0700312 OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
313 mPostedOpenRunnable = new OpenOverflowRunnable(popup);
314 // Post this for later; we might still need a layout for the anchor to be right.
315 ((View) mMenuView).post(mPostedOpenRunnable);
316
317 // ActionMenuPresenter uses null as a callback argument here
318 // to indicate overflow is opening.
319 super.onSubMenuSelected(null);
320
321 return true;
322 }
323 return false;
324 }
325
326 /**
327 * Hide the overflow menu if it is currently showing.
328 *
329 * @return true if the overflow menu was hidden, false otherwise.
330 */
331 public boolean hideOverflowMenu() {
332 if (mPostedOpenRunnable != null && mMenuView != null) {
333 ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
Adam Powell678ed0c2011-10-27 17:46:07 -0700334 mPostedOpenRunnable = null;
Adam Powell696cba52011-03-29 10:38:16 -0700335 return true;
336 }
337
338 MenuPopupHelper popup = mOverflowPopup;
339 if (popup != null) {
340 popup.dismiss();
341 return true;
342 }
343 return false;
344 }
345
346 /**
347 * Dismiss all popup menus - overflow and submenus.
348 * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
349 */
350 public boolean dismissPopupMenus() {
351 boolean result = hideOverflowMenu();
352 result |= hideSubMenus();
353 return result;
354 }
355
356 /**
357 * Dismiss all submenu popups.
358 *
359 * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
360 */
361 public boolean hideSubMenus() {
362 if (mActionButtonPopup != null) {
363 mActionButtonPopup.dismiss();
364 return true;
365 }
366 return false;
367 }
368
369 /**
370 * @return true if the overflow menu is currently showing
371 */
372 public boolean isOverflowMenuShowing() {
373 return mOverflowPopup != null && mOverflowPopup.isShowing();
374 }
375
Adam Powell5fcf5b92013-09-11 08:45:36 -0700376 public boolean isOverflowMenuShowPending() {
377 return mPostedOpenRunnable != null || isOverflowMenuShowing();
378 }
379
Adam Powell696cba52011-03-29 10:38:16 -0700380 /**
381 * @return true if space has been reserved in the action menu for an overflow item.
382 */
383 public boolean isOverflowReserved() {
384 return mReserveOverflow;
385 }
386
387 public boolean flagActionItems() {
388 final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
389 final int itemsSize = visibleItems.size();
390 int maxActions = mMaxItems;
391 int widthLimit = mActionItemWidthLimit;
392 final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
393 final ViewGroup parent = (ViewGroup) mMenuView;
394
395 int requiredItems = 0;
396 int requestedItems = 0;
397 int firstActionWidth = 0;
398 boolean hasOverflow = false;
399 for (int i = 0; i < itemsSize; i++) {
400 MenuItemImpl item = visibleItems.get(i);
401 if (item.requiresActionButton()) {
402 requiredItems++;
403 } else if (item.requestsActionButton()) {
404 requestedItems++;
405 } else {
406 hasOverflow = true;
407 }
Adam Powellb187cd92011-07-20 14:17:56 -0700408 if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
409 // Overflow everything if we have an expanded action view and we're
410 // space constrained.
411 maxActions = 0;
412 }
Adam Powell696cba52011-03-29 10:38:16 -0700413 }
414
415 // Reserve a spot for the overflow item if needed.
416 if (mReserveOverflow &&
417 (hasOverflow || requiredItems + requestedItems > maxActions)) {
418 maxActions--;
419 }
420 maxActions -= requiredItems;
421
422 final SparseBooleanArray seenGroups = mActionButtonGroups;
423 seenGroups.clear();
424
Adam Powell35aecd52011-07-01 13:43:49 -0700425 int cellSize = 0;
426 int cellsRemaining = 0;
427 if (mStrictWidthLimit) {
428 cellsRemaining = widthLimit / mMinCellSize;
429 final int cellSizeRemaining = widthLimit % mMinCellSize;
430 cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
431 }
432
Adam Powell696cba52011-03-29 10:38:16 -0700433 // Flag as many more requested items as will fit.
434 for (int i = 0; i < itemsSize; i++) {
435 MenuItemImpl item = visibleItems.get(i);
436
437 if (item.requiresActionButton()) {
Adam Powell35aecd52011-07-01 13:43:49 -0700438 View v = getItemView(item, mScrapActionButtonView, parent);
439 if (mScrapActionButtonView == null) {
440 mScrapActionButtonView = v;
Adam Powell696cba52011-03-29 10:38:16 -0700441 }
Adam Powell35aecd52011-07-01 13:43:49 -0700442 if (mStrictWidthLimit) {
443 cellsRemaining -= ActionMenuView.measureChildForCells(v,
444 cellSize, cellsRemaining, querySpec, 0);
445 } else {
446 v.measure(querySpec, querySpec);
447 }
Adam Powell696cba52011-03-29 10:38:16 -0700448 final int measuredWidth = v.getMeasuredWidth();
449 widthLimit -= measuredWidth;
450 if (firstActionWidth == 0) {
451 firstActionWidth = measuredWidth;
452 }
453 final int groupId = item.getGroupId();
454 if (groupId != 0) {
455 seenGroups.put(groupId, true);
456 }
Adam Powellea1ca952011-06-21 14:07:59 -0700457 item.setIsActionButton(true);
Adam Powell696cba52011-03-29 10:38:16 -0700458 } else if (item.requestsActionButton()) {
459 // Items in a group with other items that already have an action slot
460 // can break the max actions rule, but not the width limit.
461 final int groupId = item.getGroupId();
462 final boolean inGroup = seenGroups.get(groupId);
Adam Powell35aecd52011-07-01 13:43:49 -0700463 boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
464 (!mStrictWidthLimit || cellsRemaining > 0);
Adam Powell696cba52011-03-29 10:38:16 -0700465
466 if (isAction) {
Adam Powell35aecd52011-07-01 13:43:49 -0700467 View v = getItemView(item, mScrapActionButtonView, parent);
468 if (mScrapActionButtonView == null) {
469 mScrapActionButtonView = v;
Adam Powell696cba52011-03-29 10:38:16 -0700470 }
Adam Powell35aecd52011-07-01 13:43:49 -0700471 if (mStrictWidthLimit) {
472 final int cells = ActionMenuView.measureChildForCells(v,
473 cellSize, cellsRemaining, querySpec, 0);
474 cellsRemaining -= cells;
475 if (cells == 0) {
476 isAction = false;
477 }
478 } else {
479 v.measure(querySpec, querySpec);
480 }
Adam Powell696cba52011-03-29 10:38:16 -0700481 final int measuredWidth = v.getMeasuredWidth();
482 widthLimit -= measuredWidth;
483 if (firstActionWidth == 0) {
484 firstActionWidth = measuredWidth;
485 }
486
Adam Powell640a66e2011-04-29 10:18:53 -0700487 if (mStrictWidthLimit) {
Adam Powell35aecd52011-07-01 13:43:49 -0700488 isAction &= widthLimit >= 0;
Adam Powell640a66e2011-04-29 10:18:53 -0700489 } else {
490 // Did this push the entire first item past the limit?
Adam Powell35aecd52011-07-01 13:43:49 -0700491 isAction &= widthLimit + firstActionWidth > 0;
Adam Powell696cba52011-03-29 10:38:16 -0700492 }
493 }
494
495 if (isAction && groupId != 0) {
496 seenGroups.put(groupId, true);
497 } else if (inGroup) {
498 // We broke the width limit. Demote the whole group, they all overflow now.
499 seenGroups.put(groupId, false);
500 for (int j = 0; j < i; j++) {
501 MenuItemImpl areYouMyGroupie = visibleItems.get(j);
502 if (areYouMyGroupie.getGroupId() == groupId) {
Adam Powell23f4cc02011-08-18 10:30:46 -0700503 // Give back the action slot
504 if (areYouMyGroupie.isActionButton()) maxActions++;
Adam Powell696cba52011-03-29 10:38:16 -0700505 areYouMyGroupie.setIsActionButton(false);
506 }
507 }
508 }
509
Adam Powell23f4cc02011-08-18 10:30:46 -0700510 if (isAction) maxActions--;
511
Adam Powell696cba52011-03-29 10:38:16 -0700512 item.setIsActionButton(isAction);
Svetoslav Ganov14c46692012-10-09 18:11:36 -0700513 } else {
514 // Neither requires nor requests an action button.
515 item.setIsActionButton(false);
Adam Powell696cba52011-03-29 10:38:16 -0700516 }
517 }
518 return true;
519 }
520
521 @Override
522 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
523 dismissPopupMenus();
524 super.onCloseMenu(menu, allMenusAreClosing);
525 }
526
Adam Powell11ed1d62011-07-11 21:19:59 -0700527 @Override
528 public Parcelable onSaveInstanceState() {
529 SavedState state = new SavedState();
530 state.openSubMenuId = mOpenSubMenuId;
531 return state;
532 }
533
534 @Override
535 public void onRestoreInstanceState(Parcelable state) {
536 SavedState saved = (SavedState) state;
537 if (saved.openSubMenuId > 0) {
538 MenuItem item = mMenu.findItem(saved.openSubMenuId);
539 if (item != null) {
540 SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
541 onSubMenuSelected(subMenu);
542 }
543 }
544 }
545
Adam Powell823f0742011-09-21 17:17:01 -0700546 @Override
547 public void onSubUiVisibilityChanged(boolean isVisible) {
548 if (isVisible) {
549 // Not a submenu, but treat it like one.
550 super.onSubMenuSelected(null);
551 } else {
552 mMenu.close(false);
553 }
554 }
555
Adam Powell11ed1d62011-07-11 21:19:59 -0700556 private static class SavedState implements Parcelable {
557 public int openSubMenuId;
558
559 SavedState() {
560 }
561
562 SavedState(Parcel in) {
563 openSubMenuId = in.readInt();
564 }
565
566 @Override
567 public int describeContents() {
568 return 0;
569 }
570
571 @Override
572 public void writeToParcel(Parcel dest, int flags) {
573 dest.writeInt(openSubMenuId);
574 }
575
576 public static final Parcelable.Creator<SavedState> CREATOR
577 = new Parcelable.Creator<SavedState>() {
578 public SavedState createFromParcel(Parcel in) {
579 return new SavedState(in);
580 }
581
582 public SavedState[] newArray(int size) {
583 return new SavedState[size];
584 }
585 };
586 }
587
Adam Powell696cba52011-03-29 10:38:16 -0700588 private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
589 public OverflowMenuButton(Context context) {
590 super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
591
592 setClickable(true);
593 setFocusable(true);
594 setVisibility(VISIBLE);
595 setEnabled(true);
Alan Viveretteca6a36112013-08-16 14:41:06 -0700596
Alan Viverette69960142013-08-22 17:26:57 -0700597 setOnTouchListener(new ForwardingListener(this) {
Alan Viveretteca6a36112013-08-16 14:41:06 -0700598 @Override
599 public ListPopupWindow getPopup() {
600 if (mOverflowPopup == null) {
601 return null;
602 }
603
604 return mOverflowPopup.getPopup();
605 }
606
607 @Override
608 public boolean onForwardingStarted() {
609 showOverflowMenu();
610 return true;
611 }
612
613 @Override
614 public boolean onForwardingStopped() {
615 // Displaying the popup occurs asynchronously, so wait for
616 // the runnable to finish before deciding whether to stop
617 // forwarding.
618 if (mPostedOpenRunnable != null) {
619 return false;
620 }
621
622 hideOverflowMenu();
623 return true;
624 }
625 });
Adam Powell696cba52011-03-29 10:38:16 -0700626 }
627
628 @Override
629 public boolean performClick() {
630 if (super.performClick()) {
631 return true;
632 }
633
634 playSoundEffect(SoundEffectConstants.CLICK);
635 showOverflowMenu();
636 return true;
637 }
638
Alan Viverette80e72702013-07-29 11:12:59 -0700639 @Override
Adam Powell696cba52011-03-29 10:38:16 -0700640 public boolean needsDividerBefore() {
Adam Powell35aecd52011-07-01 13:43:49 -0700641 return false;
Adam Powell696cba52011-03-29 10:38:16 -0700642 }
643
Alan Viverette80e72702013-07-29 11:12:59 -0700644 @Override
Adam Powell696cba52011-03-29 10:38:16 -0700645 public boolean needsDividerAfter() {
646 return false;
647 }
Adam Powelld5c81db2012-08-02 14:35:26 -0700648
649 @Override
650 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
651 if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
652 // Fill available height
653 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
654 MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
655 }
656 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
657 }
Alan Viverette058ac7c2013-08-19 16:44:30 -0700658
659 @Override
660 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
661 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganovcb8ed392013-08-23 20:37:28 -0700662 info.setCanOpenPopup(true);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700663 }
Adam Powell696cba52011-03-29 10:38:16 -0700664 }
665
666 private class OverflowPopup extends MenuPopupHelper {
667 public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
668 boolean overflowOnly) {
669 super(context, menu, anchorView, overflowOnly);
Adam Powell54c94de2013-09-26 15:36:34 -0700670 setGravity(Gravity.END);
Adam Powell11ed1d62011-07-11 21:19:59 -0700671 setCallback(mPopupPresenterCallback);
Adam Powell696cba52011-03-29 10:38:16 -0700672 }
673
674 @Override
675 public void onDismiss() {
676 super.onDismiss();
677 mMenu.close();
678 mOverflowPopup = null;
679 }
680 }
681
682 private class ActionButtonSubmenu extends MenuPopupHelper {
683 private SubMenuBuilder mSubMenu;
684
685 public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
686 super(context, subMenu);
687 mSubMenu = subMenu;
688
689 MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
690 if (!item.isActionButton()) {
691 // Give a reasonable anchor to nested submenus.
692 setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
693 }
Adam Powell11ed1d62011-07-11 21:19:59 -0700694
695 setCallback(mPopupPresenterCallback);
Adam Powell91511032011-07-13 10:24:06 -0700696
697 boolean preserveIconSpacing = false;
698 final int count = subMenu.size();
699 for (int i = 0; i < count; i++) {
700 MenuItem childItem = subMenu.getItem(i);
701 if (childItem.isVisible() && childItem.getIcon() != null) {
702 preserveIconSpacing = true;
703 break;
704 }
705 }
706 setForceShowIcon(preserveIconSpacing);
Adam Powell696cba52011-03-29 10:38:16 -0700707 }
708
709 @Override
710 public void onDismiss() {
711 super.onDismiss();
Adam Powell696cba52011-03-29 10:38:16 -0700712 mActionButtonPopup = null;
Adam Powell11ed1d62011-07-11 21:19:59 -0700713 mOpenSubMenuId = 0;
714 }
715 }
716
717 private class PopupPresenterCallback implements MenuPresenter.Callback {
718
719 @Override
720 public boolean onOpenSubMenu(MenuBuilder subMenu) {
Adam Powell823f0742011-09-21 17:17:01 -0700721 if (subMenu == null) return false;
722
Adam Powell11ed1d62011-07-11 21:19:59 -0700723 mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
Adam Powellb411b322013-11-01 18:22:11 -0700724 final MenuPresenter.Callback cb = getCallback();
725 return cb != null ? cb.onOpenSubMenu(subMenu) : false;
Adam Powell11ed1d62011-07-11 21:19:59 -0700726 }
727
728 @Override
729 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
Adam Powell823f0742011-09-21 17:17:01 -0700730 if (menu instanceof SubMenuBuilder) {
731 ((SubMenuBuilder) menu).getRootMenu().close(false);
732 }
Adam Powellb411b322013-11-01 18:22:11 -0700733 final MenuPresenter.Callback cb = getCallback();
734 if (cb != null) {
735 cb.onCloseMenu(menu, allMenusAreClosing);
736 }
Adam Powell696cba52011-03-29 10:38:16 -0700737 }
738 }
739
740 private class OpenOverflowRunnable implements Runnable {
741 private OverflowPopup mPopup;
742
743 public OpenOverflowRunnable(OverflowPopup popup) {
744 mPopup = popup;
745 }
746
747 public void run() {
748 mMenu.changeMenuMode();
Adam Powell678ed0c2011-10-27 17:46:07 -0700749 final View menuView = (View) mMenuView;
750 if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
Adam Powell696cba52011-03-29 10:38:16 -0700751 mOverflowPopup = mPopup;
Adam Powell696cba52011-03-29 10:38:16 -0700752 }
Adam Powell678ed0c2011-10-27 17:46:07 -0700753 mPostedOpenRunnable = null;
Adam Powell696cba52011-03-29 10:38:16 -0700754 }
755 }
Adam Powell696cba52011-03-29 10:38:16 -0700756}