blob: 1f405a7c3563b51ba39b37b52a1bfb2daa95c087 [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
Adam Powellfa18d182014-01-07 15:56:59 -080017package android.widget;
Adam Powell696cba52011-03-29 10:38:16 -070018
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 Powell696cba52011-03-29 10:38:16 -070024import android.util.SparseBooleanArray;
Adam Powell823f0742011-09-21 17:17:01 -070025import android.view.ActionProvider;
Adam Powell54c94de2013-09-26 15:36:34 -070026import android.view.Gravity;
Adam Powell696cba52011-03-29 10:38:16 -070027import android.view.MenuItem;
28import android.view.SoundEffectConstants;
29import android.view.View;
30import android.view.View.MeasureSpec;
31import android.view.ViewGroup;
Adam Powellad79b902013-06-19 11:33:28 -070032import android.view.accessibility.AccessibilityNodeInfo;
Alan Viveretteca6a36112013-08-16 14:41:06 -070033import android.widget.ListPopupWindow.ForwardingListener;
Adam Powellad79b902013-06-19 11:33:28 -070034import com.android.internal.transition.ActionBarTransition;
Alan Viverette80e72702013-07-29 11:12:59 -070035import com.android.internal.view.ActionBarPolicy;
Adam Powellfa18d182014-01-07 15:56:59 -080036import com.android.internal.view.menu.ActionMenuItemView;
37import com.android.internal.view.menu.BaseMenuPresenter;
38import com.android.internal.view.menu.MenuBuilder;
39import com.android.internal.view.menu.MenuItemImpl;
40import com.android.internal.view.menu.MenuPopupHelper;
41import com.android.internal.view.menu.MenuView;
42import com.android.internal.view.menu.SubMenuBuilder;
Adam Powell696cba52011-03-29 10:38:16 -070043
44import java.util.ArrayList;
45
46/**
47 * MenuPresenter for building action menus as seen in the action bar and action modes.
Adam Powellfa18d182014-01-07 15:56:59 -080048 *
49 * @hide
Adam Powell696cba52011-03-29 10:38:16 -070050 */
Adam Powell823f0742011-09-21 17:17:01 -070051public class ActionMenuPresenter extends BaseMenuPresenter
52 implements ActionProvider.SubUiVisibilityListener {
Adam Powell640a66e2011-04-29 10:18:53 -070053 private static final String TAG = "ActionMenuPresenter";
54
Adam Powell696cba52011-03-29 10:38:16 -070055 private View mOverflowButton;
56 private boolean mReserveOverflow;
Adam Powell1ab418a2011-06-09 20:49:49 -070057 private boolean mReserveOverflowSet;
Adam Powell696cba52011-03-29 10:38:16 -070058 private int mWidthLimit;
59 private int mActionItemWidthLimit;
60 private int mMaxItems;
Adam Powell1ab418a2011-06-09 20:49:49 -070061 private boolean mMaxItemsSet;
Adam Powell640a66e2011-04-29 10:18:53 -070062 private boolean mStrictWidthLimit;
Adam Powell1ab418a2011-06-09 20:49:49 -070063 private boolean mWidthLimitSet;
Adam Powellb187cd92011-07-20 14:17:56 -070064 private boolean mExpandedActionViewsExclusive;
Adam Powell696cba52011-03-29 10:38:16 -070065
Adam Powell35aecd52011-07-01 13:43:49 -070066 private int mMinCellSize;
67
Adam Powell696cba52011-03-29 10:38:16 -070068 // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
69 private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
70
71 private View mScrapActionButtonView;
72
73 private OverflowPopup mOverflowPopup;
74 private ActionButtonSubmenu mActionButtonPopup;
75
76 private OpenOverflowRunnable mPostedOpenRunnable;
Alan Viverette56110722014-01-10 14:03:21 -080077 private ActionMenuPopupCallback mPopupCallback;
Adam Powell696cba52011-03-29 10:38:16 -070078
Adam Powell11ed1d62011-07-11 21:19:59 -070079 final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
80 int mOpenSubMenuId;
81
Adam Powell538e5652011-10-11 13:47:08 -070082 public ActionMenuPresenter(Context context) {
83 super(context, com.android.internal.R.layout.action_menu_layout,
Adam Powell696cba52011-03-29 10:38:16 -070084 com.android.internal.R.layout.action_menu_item_layout);
85 }
86
87 @Override
88 public void initForMenu(Context context, MenuBuilder menu) {
89 super.initForMenu(context, menu);
90
91 final Resources res = context.getResources();
Adam Powell1ab418a2011-06-09 20:49:49 -070092
Adam Powellb8139af2012-04-19 13:52:46 -070093 final ActionBarPolicy abp = ActionBarPolicy.get(context);
Adam Powell1ab418a2011-06-09 20:49:49 -070094 if (!mReserveOverflowSet) {
Adam Powellb8139af2012-04-19 13:52:46 -070095 mReserveOverflow = abp.showsOverflowMenuButton();
Adam Powell1ab418a2011-06-09 20:49:49 -070096 }
97
98 if (!mWidthLimitSet) {
Adam Powellb8139af2012-04-19 13:52:46 -070099 mWidthLimit = abp.getEmbeddedMenuWidthLimit();
Adam Powell1ab418a2011-06-09 20:49:49 -0700100 }
Adam Powell696cba52011-03-29 10:38:16 -0700101
102 // Measure for initial configuration
Adam Powell1ab418a2011-06-09 20:49:49 -0700103 if (!mMaxItemsSet) {
Adam Powellb8139af2012-04-19 13:52:46 -0700104 mMaxItems = abp.getMaxActionButtons();
Adam Powell1ab418a2011-06-09 20:49:49 -0700105 }
Adam Powell696cba52011-03-29 10:38:16 -0700106
107 int width = mWidthLimit;
108 if (mReserveOverflow) {
Adam Powell9b4bee02011-04-27 19:24:47 -0700109 if (mOverflowButton == null) {
Adam Powell538e5652011-10-11 13:47:08 -0700110 mOverflowButton = new OverflowMenuButton(mSystemContext);
Adam Powell9b4bee02011-04-27 19:24:47 -0700111 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
112 mOverflowButton.measure(spec, spec);
113 }
Adam Powell696cba52011-03-29 10:38:16 -0700114 width -= mOverflowButton.getMeasuredWidth();
115 } else {
116 mOverflowButton = null;
117 }
118
119 mActionItemWidthLimit = width;
120
Adam Powell35aecd52011-07-01 13:43:49 -0700121 mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
122
Adam Powell696cba52011-03-29 10:38:16 -0700123 // Drop a scrap view as it may no longer reflect the proper context/config.
124 mScrapActionButtonView = null;
125 }
126
Adam Powellbfcdfaf2011-08-19 11:46:59 -0700127 public void onConfigurationChanged(Configuration newConfig) {
128 if (!mMaxItemsSet) {
129 mMaxItems = mContext.getResources().getInteger(
130 com.android.internal.R.integer.max_action_buttons);
Adam Powellf203e0a2012-03-06 17:40:12 -0800131 }
132 if (mMenu != null) {
133 mMenu.onItemsChanged(true);
Adam Powellbfcdfaf2011-08-19 11:46:59 -0700134 }
135 }
136
Adam Powell640a66e2011-04-29 10:18:53 -0700137 public void setWidthLimit(int width, boolean strict) {
Adam Powell1ab418a2011-06-09 20:49:49 -0700138 mWidthLimit = width;
Adam Powell640a66e2011-04-29 10:18:53 -0700139 mStrictWidthLimit = strict;
Adam Powell1ab418a2011-06-09 20:49:49 -0700140 mWidthLimitSet = true;
141 }
142
143 public void setReserveOverflow(boolean reserveOverflow) {
144 mReserveOverflow = reserveOverflow;
145 mReserveOverflowSet = true;
Adam Powell9b4bee02011-04-27 19:24:47 -0700146 }
147
148 public void setItemLimit(int itemCount) {
149 mMaxItems = itemCount;
Adam Powell1ab418a2011-06-09 20:49:49 -0700150 mMaxItemsSet = true;
Adam Powell9b4bee02011-04-27 19:24:47 -0700151 }
152
Adam Powellb187cd92011-07-20 14:17:56 -0700153 public void setExpandedActionViewsExclusive(boolean isExclusive) {
154 mExpandedActionViewsExclusive = isExclusive;
155 }
156
Adam Powell696cba52011-03-29 10:38:16 -0700157 @Override
158 public MenuView getMenuView(ViewGroup root) {
159 MenuView result = super.getMenuView(root);
160 ((ActionMenuView) result).setPresenter(this);
161 return result;
162 }
163
164 @Override
Alan Viverettefbe4a582013-09-06 13:45:54 -0700165 public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
Adam Powell8d02dea2011-05-31 21:35:13 -0700166 View actionView = item.getActionView();
Adam Powell35aecd52011-07-01 13:43:49 -0700167 if (actionView == null || item.hasCollapsibleActionView()) {
Adam Powellc3ca3ea2013-10-10 18:07:48 -0700168 actionView = super.getItemView(item, convertView, parent);
Adam Powell35aecd52011-07-01 13:43:49 -0700169 }
Adam Powell8d02dea2011-05-31 21:35:13 -0700170 actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
Adam Powell35aecd52011-07-01 13:43:49 -0700171
Adam Powellfe9c3542014-01-10 18:32:08 +0000172 final ActionMenuView menuParent = (ActionMenuView) parent;
173 final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
174 if (!menuParent.checkLayoutParams(lp)) {
175 actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
176 }
177 return actionView;
178 }
179
180 @Override
Alan Viverette56110722014-01-10 14:03:21 -0800181 public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
Adam Powellfe9c3542014-01-10 18:32:08 +0000182 itemView.initialize(item, 0);
183
184 final ActionMenuView menuView = (ActionMenuView) mMenuView;
185 final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
186 actionItemView.setItemInvoker(menuView);
187
Alan Viverette56110722014-01-10 14:03:21 -0800188 if (mPopupCallback == null) {
189 mPopupCallback = new ActionMenuPopupCallback();
Alan Viverettefbe4a582013-09-06 13:45:54 -0700190 }
Alan Viverette56110722014-01-10 14:03:21 -0800191 actionItemView.setPopupCallback(mPopupCallback);
Adam Powell696cba52011-03-29 10:38:16 -0700192 }
193
194 @Override
195 public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
196 return item.isActionButton();
197 }
198
199 @Override
200 public void updateMenuView(boolean cleared) {
Adam Powellad79b902013-06-19 11:33:28 -0700201 final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
202 if (menuViewParent != null) {
Chet Haased8d7c382013-09-23 11:26:36 -0700203 ActionBarTransition.beginDelayedTransition(menuViewParent);
Adam Powellad79b902013-06-19 11:33:28 -0700204 }
Adam Powell696cba52011-03-29 10:38:16 -0700205 super.updateMenuView(cleared);
206
Adam Powellda971082013-10-03 18:21:58 -0700207 ((View) mMenuView).requestLayout();
208
Adam Powell823f0742011-09-21 17:17:01 -0700209 if (mMenu != null) {
210 final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
211 final int count = actionItems.size();
212 for (int i = 0; i < count; i++) {
213 final ActionProvider provider = actionItems.get(i).getActionProvider();
214 if (provider != null) {
215 provider.setSubUiVisibilityListener(this);
216 }
217 }
218 }
219
Adam Powell275702c2011-09-23 17:34:04 -0700220 final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
221 mMenu.getNonActionItems() : null;
222
223 boolean hasOverflow = false;
224 if (mReserveOverflow && nonActionItems != null) {
225 final int count = nonActionItems.size();
226 if (count == 1) {
227 hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
228 } else {
229 hasOverflow = count > 0;
230 }
231 }
232
Adam Powell14b7e2c2011-08-12 11:11:50 -0700233 if (hasOverflow) {
Adam Powell696cba52011-03-29 10:38:16 -0700234 if (mOverflowButton == null) {
Adam Powell538e5652011-10-11 13:47:08 -0700235 mOverflowButton = new OverflowMenuButton(mSystemContext);
Adam Powell696cba52011-03-29 10:38:16 -0700236 }
237 ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
238 if (parent != mMenuView) {
239 if (parent != null) {
240 parent.removeView(mOverflowButton);
241 }
Adam Powell35aecd52011-07-01 13:43:49 -0700242 ActionMenuView menuView = (ActionMenuView) mMenuView;
243 menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
Adam Powell696cba52011-03-29 10:38:16 -0700244 }
245 } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
246 ((ViewGroup) mMenuView).removeView(mOverflowButton);
247 }
Adam Powell14b7e2c2011-08-12 11:11:50 -0700248
249 ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
Adam Powell696cba52011-03-29 10:38:16 -0700250 }
251
252 @Override
253 public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
254 if (parent.getChildAt(childIndex) == mOverflowButton) return false;
255 return super.filterLeftoverView(parent, childIndex);
256 }
257
258 public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
259 if (!subMenu.hasVisibleItems()) return false;
260
261 SubMenuBuilder topSubMenu = subMenu;
262 while (topSubMenu.getParentMenu() != mMenu) {
263 topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
264 }
265 View anchor = findViewForItem(topSubMenu.getItem());
Adam Powell11ed1d62011-07-11 21:19:59 -0700266 if (anchor == null) {
267 if (mOverflowButton == null) return false;
268 anchor = mOverflowButton;
269 }
Adam Powell696cba52011-03-29 10:38:16 -0700270
Adam Powell11ed1d62011-07-11 21:19:59 -0700271 mOpenSubMenuId = subMenu.getItem().getItemId();
Adam Powell696cba52011-03-29 10:38:16 -0700272 mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
273 mActionButtonPopup.setAnchorView(anchor);
274 mActionButtonPopup.show();
275 super.onSubMenuSelected(subMenu);
276 return true;
277 }
278
279 private View findViewForItem(MenuItem item) {
280 final ViewGroup parent = (ViewGroup) mMenuView;
281 if (parent == null) return null;
282
283 final int count = parent.getChildCount();
284 for (int i = 0; i < count; i++) {
285 final View child = parent.getChildAt(i);
286 if (child instanceof MenuView.ItemView &&
287 ((MenuView.ItemView) child).getItemData() == item) {
288 return child;
289 }
290 }
291 return null;
292 }
293
294 /**
295 * Display the overflow menu if one is present.
296 * @return true if the overflow menu was shown, false otherwise.
297 */
298 public boolean showOverflowMenu() {
Adam Powell70e9f4b2011-08-22 16:31:24 -0700299 if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
Jake Whartona6476402012-03-29 01:42:37 -0700300 mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
Adam Powell696cba52011-03-29 10:38:16 -0700301 OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
302 mPostedOpenRunnable = new OpenOverflowRunnable(popup);
303 // Post this for later; we might still need a layout for the anchor to be right.
304 ((View) mMenuView).post(mPostedOpenRunnable);
305
306 // ActionMenuPresenter uses null as a callback argument here
307 // to indicate overflow is opening.
308 super.onSubMenuSelected(null);
309
310 return true;
311 }
312 return false;
313 }
314
315 /**
316 * Hide the overflow menu if it is currently showing.
317 *
318 * @return true if the overflow menu was hidden, false otherwise.
319 */
320 public boolean hideOverflowMenu() {
321 if (mPostedOpenRunnable != null && mMenuView != null) {
322 ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
Adam Powell678ed0c2011-10-27 17:46:07 -0700323 mPostedOpenRunnable = null;
Adam Powell696cba52011-03-29 10:38:16 -0700324 return true;
325 }
326
327 MenuPopupHelper popup = mOverflowPopup;
328 if (popup != null) {
329 popup.dismiss();
330 return true;
331 }
332 return false;
333 }
334
335 /**
336 * Dismiss all popup menus - overflow and submenus.
337 * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
338 */
339 public boolean dismissPopupMenus() {
340 boolean result = hideOverflowMenu();
341 result |= hideSubMenus();
342 return result;
343 }
344
345 /**
346 * Dismiss all submenu popups.
347 *
348 * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
349 */
350 public boolean hideSubMenus() {
351 if (mActionButtonPopup != null) {
352 mActionButtonPopup.dismiss();
353 return true;
354 }
355 return false;
356 }
357
358 /**
359 * @return true if the overflow menu is currently showing
360 */
361 public boolean isOverflowMenuShowing() {
362 return mOverflowPopup != null && mOverflowPopup.isShowing();
363 }
364
Adam Powell5fcf5b92013-09-11 08:45:36 -0700365 public boolean isOverflowMenuShowPending() {
366 return mPostedOpenRunnable != null || isOverflowMenuShowing();
367 }
368
Adam Powell696cba52011-03-29 10:38:16 -0700369 /**
370 * @return true if space has been reserved in the action menu for an overflow item.
371 */
372 public boolean isOverflowReserved() {
373 return mReserveOverflow;
374 }
375
376 public boolean flagActionItems() {
377 final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
378 final int itemsSize = visibleItems.size();
379 int maxActions = mMaxItems;
380 int widthLimit = mActionItemWidthLimit;
381 final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
382 final ViewGroup parent = (ViewGroup) mMenuView;
383
384 int requiredItems = 0;
385 int requestedItems = 0;
386 int firstActionWidth = 0;
387 boolean hasOverflow = false;
388 for (int i = 0; i < itemsSize; i++) {
389 MenuItemImpl item = visibleItems.get(i);
390 if (item.requiresActionButton()) {
391 requiredItems++;
392 } else if (item.requestsActionButton()) {
393 requestedItems++;
394 } else {
395 hasOverflow = true;
396 }
Adam Powellb187cd92011-07-20 14:17:56 -0700397 if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
398 // Overflow everything if we have an expanded action view and we're
399 // space constrained.
400 maxActions = 0;
401 }
Adam Powell696cba52011-03-29 10:38:16 -0700402 }
403
404 // Reserve a spot for the overflow item if needed.
405 if (mReserveOverflow &&
406 (hasOverflow || requiredItems + requestedItems > maxActions)) {
407 maxActions--;
408 }
409 maxActions -= requiredItems;
410
411 final SparseBooleanArray seenGroups = mActionButtonGroups;
412 seenGroups.clear();
413
Adam Powell35aecd52011-07-01 13:43:49 -0700414 int cellSize = 0;
415 int cellsRemaining = 0;
416 if (mStrictWidthLimit) {
417 cellsRemaining = widthLimit / mMinCellSize;
418 final int cellSizeRemaining = widthLimit % mMinCellSize;
419 cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
420 }
421
Adam Powell696cba52011-03-29 10:38:16 -0700422 // Flag as many more requested items as will fit.
423 for (int i = 0; i < itemsSize; i++) {
424 MenuItemImpl item = visibleItems.get(i);
425
426 if (item.requiresActionButton()) {
Adam Powell35aecd52011-07-01 13:43:49 -0700427 View v = getItemView(item, mScrapActionButtonView, parent);
428 if (mScrapActionButtonView == null) {
429 mScrapActionButtonView = v;
Adam Powell696cba52011-03-29 10:38:16 -0700430 }
Adam Powell35aecd52011-07-01 13:43:49 -0700431 if (mStrictWidthLimit) {
432 cellsRemaining -= ActionMenuView.measureChildForCells(v,
433 cellSize, cellsRemaining, querySpec, 0);
434 } else {
435 v.measure(querySpec, querySpec);
436 }
Adam Powell696cba52011-03-29 10:38:16 -0700437 final int measuredWidth = v.getMeasuredWidth();
438 widthLimit -= measuredWidth;
439 if (firstActionWidth == 0) {
440 firstActionWidth = measuredWidth;
441 }
442 final int groupId = item.getGroupId();
443 if (groupId != 0) {
444 seenGroups.put(groupId, true);
445 }
Adam Powellea1ca952011-06-21 14:07:59 -0700446 item.setIsActionButton(true);
Adam Powell696cba52011-03-29 10:38:16 -0700447 } else if (item.requestsActionButton()) {
448 // Items in a group with other items that already have an action slot
449 // can break the max actions rule, but not the width limit.
450 final int groupId = item.getGroupId();
451 final boolean inGroup = seenGroups.get(groupId);
Adam Powell35aecd52011-07-01 13:43:49 -0700452 boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
453 (!mStrictWidthLimit || cellsRemaining > 0);
Adam Powell696cba52011-03-29 10:38:16 -0700454
455 if (isAction) {
Adam Powell35aecd52011-07-01 13:43:49 -0700456 View v = getItemView(item, mScrapActionButtonView, parent);
457 if (mScrapActionButtonView == null) {
458 mScrapActionButtonView = v;
Adam Powell696cba52011-03-29 10:38:16 -0700459 }
Adam Powell35aecd52011-07-01 13:43:49 -0700460 if (mStrictWidthLimit) {
461 final int cells = ActionMenuView.measureChildForCells(v,
462 cellSize, cellsRemaining, querySpec, 0);
463 cellsRemaining -= cells;
464 if (cells == 0) {
465 isAction = false;
466 }
467 } else {
468 v.measure(querySpec, querySpec);
469 }
Adam Powell696cba52011-03-29 10:38:16 -0700470 final int measuredWidth = v.getMeasuredWidth();
471 widthLimit -= measuredWidth;
472 if (firstActionWidth == 0) {
473 firstActionWidth = measuredWidth;
474 }
475
Adam Powell640a66e2011-04-29 10:18:53 -0700476 if (mStrictWidthLimit) {
Adam Powell35aecd52011-07-01 13:43:49 -0700477 isAction &= widthLimit >= 0;
Adam Powell640a66e2011-04-29 10:18:53 -0700478 } else {
479 // Did this push the entire first item past the limit?
Adam Powell35aecd52011-07-01 13:43:49 -0700480 isAction &= widthLimit + firstActionWidth > 0;
Adam Powell696cba52011-03-29 10:38:16 -0700481 }
482 }
483
484 if (isAction && groupId != 0) {
485 seenGroups.put(groupId, true);
486 } else if (inGroup) {
487 // We broke the width limit. Demote the whole group, they all overflow now.
488 seenGroups.put(groupId, false);
489 for (int j = 0; j < i; j++) {
490 MenuItemImpl areYouMyGroupie = visibleItems.get(j);
491 if (areYouMyGroupie.getGroupId() == groupId) {
Adam Powell23f4cc02011-08-18 10:30:46 -0700492 // Give back the action slot
493 if (areYouMyGroupie.isActionButton()) maxActions++;
Adam Powell696cba52011-03-29 10:38:16 -0700494 areYouMyGroupie.setIsActionButton(false);
495 }
496 }
497 }
498
Adam Powell23f4cc02011-08-18 10:30:46 -0700499 if (isAction) maxActions--;
500
Adam Powell696cba52011-03-29 10:38:16 -0700501 item.setIsActionButton(isAction);
Svetoslav Ganov14c46692012-10-09 18:11:36 -0700502 } else {
503 // Neither requires nor requests an action button.
504 item.setIsActionButton(false);
Adam Powell696cba52011-03-29 10:38:16 -0700505 }
506 }
507 return true;
508 }
509
510 @Override
511 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
512 dismissPopupMenus();
513 super.onCloseMenu(menu, allMenusAreClosing);
514 }
515
Adam Powell11ed1d62011-07-11 21:19:59 -0700516 @Override
517 public Parcelable onSaveInstanceState() {
518 SavedState state = new SavedState();
519 state.openSubMenuId = mOpenSubMenuId;
520 return state;
521 }
522
523 @Override
524 public void onRestoreInstanceState(Parcelable state) {
525 SavedState saved = (SavedState) state;
526 if (saved.openSubMenuId > 0) {
527 MenuItem item = mMenu.findItem(saved.openSubMenuId);
528 if (item != null) {
529 SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
530 onSubMenuSelected(subMenu);
531 }
532 }
533 }
534
Adam Powell823f0742011-09-21 17:17:01 -0700535 @Override
536 public void onSubUiVisibilityChanged(boolean isVisible) {
537 if (isVisible) {
538 // Not a submenu, but treat it like one.
539 super.onSubMenuSelected(null);
540 } else {
541 mMenu.close(false);
542 }
543 }
544
Adam Powellfa18d182014-01-07 15:56:59 -0800545 public void setMenuView(ActionMenuView menuView) {
546 mMenuView = menuView;
547 }
548
Adam Powell11ed1d62011-07-11 21:19:59 -0700549 private static class SavedState implements Parcelable {
550 public int openSubMenuId;
551
552 SavedState() {
553 }
554
555 SavedState(Parcel in) {
556 openSubMenuId = in.readInt();
557 }
558
559 @Override
560 public int describeContents() {
561 return 0;
562 }
563
564 @Override
565 public void writeToParcel(Parcel dest, int flags) {
566 dest.writeInt(openSubMenuId);
567 }
568
569 public static final Parcelable.Creator<SavedState> CREATOR
570 = new Parcelable.Creator<SavedState>() {
571 public SavedState createFromParcel(Parcel in) {
572 return new SavedState(in);
573 }
574
575 public SavedState[] newArray(int size) {
576 return new SavedState[size];
577 }
578 };
579 }
580
Adam Powellfa18d182014-01-07 15:56:59 -0800581 private class OverflowMenuButton extends ImageButton implements ActionMenuView.ActionMenuChildView {
Adam Powell696cba52011-03-29 10:38:16 -0700582 public OverflowMenuButton(Context context) {
583 super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
584
585 setClickable(true);
586 setFocusable(true);
587 setVisibility(VISIBLE);
588 setEnabled(true);
Alan Viveretteca6a36112013-08-16 14:41:06 -0700589
Alan Viverette69960142013-08-22 17:26:57 -0700590 setOnTouchListener(new ForwardingListener(this) {
Alan Viveretteca6a36112013-08-16 14:41:06 -0700591 @Override
592 public ListPopupWindow getPopup() {
593 if (mOverflowPopup == null) {
594 return null;
595 }
596
597 return mOverflowPopup.getPopup();
598 }
599
600 @Override
601 public boolean onForwardingStarted() {
602 showOverflowMenu();
603 return true;
604 }
605
606 @Override
607 public boolean onForwardingStopped() {
608 // Displaying the popup occurs asynchronously, so wait for
609 // the runnable to finish before deciding whether to stop
610 // forwarding.
611 if (mPostedOpenRunnable != null) {
612 return false;
613 }
614
615 hideOverflowMenu();
616 return true;
617 }
618 });
Adam Powell696cba52011-03-29 10:38:16 -0700619 }
620
621 @Override
622 public boolean performClick() {
623 if (super.performClick()) {
624 return true;
625 }
626
627 playSoundEffect(SoundEffectConstants.CLICK);
628 showOverflowMenu();
629 return true;
630 }
631
Alan Viverette80e72702013-07-29 11:12:59 -0700632 @Override
Adam Powell696cba52011-03-29 10:38:16 -0700633 public boolean needsDividerBefore() {
Adam Powell35aecd52011-07-01 13:43:49 -0700634 return false;
Adam Powell696cba52011-03-29 10:38:16 -0700635 }
636
Alan Viverette80e72702013-07-29 11:12:59 -0700637 @Override
Adam Powell696cba52011-03-29 10:38:16 -0700638 public boolean needsDividerAfter() {
639 return false;
640 }
Adam Powelld5c81db2012-08-02 14:35:26 -0700641
642 @Override
643 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
644 if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {
645 // Fill available height
646 heightMeasureSpec = MeasureSpec.makeMeasureSpec(
647 MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.EXACTLY);
648 }
649 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
650 }
Alan Viverette058ac7c2013-08-19 16:44:30 -0700651
652 @Override
653 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
654 super.onInitializeAccessibilityNodeInfo(info);
Svetoslav Ganovcb8ed392013-08-23 20:37:28 -0700655 info.setCanOpenPopup(true);
Alan Viverette058ac7c2013-08-19 16:44:30 -0700656 }
Adam Powell696cba52011-03-29 10:38:16 -0700657 }
658
659 private class OverflowPopup extends MenuPopupHelper {
660 public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
661 boolean overflowOnly) {
662 super(context, menu, anchorView, overflowOnly);
Adam Powell54c94de2013-09-26 15:36:34 -0700663 setGravity(Gravity.END);
Adam Powell11ed1d62011-07-11 21:19:59 -0700664 setCallback(mPopupPresenterCallback);
Adam Powell696cba52011-03-29 10:38:16 -0700665 }
666
667 @Override
668 public void onDismiss() {
669 super.onDismiss();
670 mMenu.close();
671 mOverflowPopup = null;
672 }
673 }
674
675 private class ActionButtonSubmenu extends MenuPopupHelper {
676 private SubMenuBuilder mSubMenu;
677
678 public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
679 super(context, subMenu);
680 mSubMenu = subMenu;
681
682 MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
683 if (!item.isActionButton()) {
684 // Give a reasonable anchor to nested submenus.
685 setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
686 }
Adam Powell11ed1d62011-07-11 21:19:59 -0700687
688 setCallback(mPopupPresenterCallback);
Adam Powell91511032011-07-13 10:24:06 -0700689
690 boolean preserveIconSpacing = false;
691 final int count = subMenu.size();
692 for (int i = 0; i < count; i++) {
693 MenuItem childItem = subMenu.getItem(i);
694 if (childItem.isVisible() && childItem.getIcon() != null) {
695 preserveIconSpacing = true;
696 break;
697 }
698 }
699 setForceShowIcon(preserveIconSpacing);
Adam Powell696cba52011-03-29 10:38:16 -0700700 }
701
702 @Override
703 public void onDismiss() {
704 super.onDismiss();
Adam Powell696cba52011-03-29 10:38:16 -0700705 mActionButtonPopup = null;
Adam Powell11ed1d62011-07-11 21:19:59 -0700706 mOpenSubMenuId = 0;
707 }
708 }
709
Adam Powellfa18d182014-01-07 15:56:59 -0800710 private class PopupPresenterCallback implements Callback {
Adam Powell11ed1d62011-07-11 21:19:59 -0700711
712 @Override
713 public boolean onOpenSubMenu(MenuBuilder subMenu) {
Adam Powell823f0742011-09-21 17:17:01 -0700714 if (subMenu == null) return false;
715
Adam Powell11ed1d62011-07-11 21:19:59 -0700716 mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
Adam Powellfa18d182014-01-07 15:56:59 -0800717 final Callback cb = getCallback();
Adam Powellb411b322013-11-01 18:22:11 -0700718 return cb != null ? cb.onOpenSubMenu(subMenu) : false;
Adam Powell11ed1d62011-07-11 21:19:59 -0700719 }
720
721 @Override
722 public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
Adam Powell823f0742011-09-21 17:17:01 -0700723 if (menu instanceof SubMenuBuilder) {
724 ((SubMenuBuilder) menu).getRootMenu().close(false);
725 }
Adam Powellfa18d182014-01-07 15:56:59 -0800726 final Callback cb = getCallback();
Adam Powellb411b322013-11-01 18:22:11 -0700727 if (cb != null) {
728 cb.onCloseMenu(menu, allMenusAreClosing);
729 }
Adam Powell696cba52011-03-29 10:38:16 -0700730 }
731 }
732
733 private class OpenOverflowRunnable implements Runnable {
734 private OverflowPopup mPopup;
735
736 public OpenOverflowRunnable(OverflowPopup popup) {
737 mPopup = popup;
738 }
739
740 public void run() {
741 mMenu.changeMenuMode();
Adam Powell678ed0c2011-10-27 17:46:07 -0700742 final View menuView = (View) mMenuView;
743 if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
Adam Powell696cba52011-03-29 10:38:16 -0700744 mOverflowPopup = mPopup;
Adam Powell696cba52011-03-29 10:38:16 -0700745 }
Adam Powell678ed0c2011-10-27 17:46:07 -0700746 mPostedOpenRunnable = null;
Adam Powell696cba52011-03-29 10:38:16 -0700747 }
748 }
Alan Viverette56110722014-01-10 14:03:21 -0800749
750 private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
751 @Override
752 public ListPopupWindow getPopup() {
753 return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
754 }
755 }
Adam Powell696cba52011-03-29 10:38:16 -0700756}