Home | History | Annotate | Download | only in widget
      1 /*
      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 
     17 package android.widget;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.PropertyValuesHolder;
     23 import android.annotation.NonNull;
     24 import android.annotation.Nullable;
     25 import android.annotation.UnsupportedAppUsage;
     26 import android.content.Context;
     27 import android.content.res.Configuration;
     28 import android.content.res.Resources;
     29 import android.graphics.drawable.Drawable;
     30 import android.os.Parcel;
     31 import android.os.Parcelable;
     32 import android.util.SparseArray;
     33 import android.util.SparseBooleanArray;
     34 import android.view.ActionProvider;
     35 import android.view.Gravity;
     36 import android.view.MenuItem;
     37 import android.view.SoundEffectConstants;
     38 import android.view.View;
     39 import android.view.View.MeasureSpec;
     40 import android.view.ViewGroup;
     41 import android.view.ViewTreeObserver;
     42 import android.view.accessibility.AccessibilityNodeInfo;
     43 
     44 import com.android.internal.view.ActionBarPolicy;
     45 import com.android.internal.view.menu.ActionMenuItemView;
     46 import com.android.internal.view.menu.BaseMenuPresenter;
     47 import com.android.internal.view.menu.MenuBuilder;
     48 import com.android.internal.view.menu.MenuItemImpl;
     49 import com.android.internal.view.menu.MenuPopupHelper;
     50 import com.android.internal.view.menu.MenuView;
     51 import com.android.internal.view.menu.ShowableListMenu;
     52 import com.android.internal.view.menu.SubMenuBuilder;
     53 
     54 import java.util.ArrayList;
     55 import java.util.List;
     56 
     57 /**
     58  * MenuPresenter for building action menus as seen in the action bar and action modes.
     59  *
     60  * @hide
     61  */
     62 public class ActionMenuPresenter extends BaseMenuPresenter
     63         implements ActionProvider.SubUiVisibilityListener {
     64     private static final int ITEM_ANIMATION_DURATION = 150;
     65     private static final boolean ACTIONBAR_ANIMATIONS_ENABLED = false;
     66 
     67     private OverflowMenuButton mOverflowButton;
     68     private Drawable mPendingOverflowIcon;
     69     private boolean mPendingOverflowIconSet;
     70     private boolean mReserveOverflow;
     71     private boolean mReserveOverflowSet;
     72     private int mWidthLimit;
     73     private int mActionItemWidthLimit;
     74     private int mMaxItems;
     75     private boolean mMaxItemsSet;
     76     private boolean mStrictWidthLimit;
     77     private boolean mWidthLimitSet;
     78     private boolean mExpandedActionViewsExclusive;
     79 
     80     private int mMinCellSize;
     81 
     82     // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
     83     private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
     84 
     85     private OverflowPopup mOverflowPopup;
     86     private ActionButtonSubmenu mActionButtonPopup;
     87 
     88     private OpenOverflowRunnable mPostedOpenRunnable;
     89     private ActionMenuPopupCallback mPopupCallback;
     90 
     91     final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
     92     int mOpenSubMenuId;
     93 
     94     // These collections are used to store pre- and post-layout information for menu items,
     95     // which is used to determine appropriate animations to run for changed items.
     96     private SparseArray<MenuItemLayoutInfo> mPreLayoutItems = new SparseArray<>();
     97     private SparseArray<MenuItemLayoutInfo> mPostLayoutItems = new SparseArray<>();
     98 
     99     // The list of currently running animations on menu items.
    100     private List<ItemAnimationInfo> mRunningItemAnimations = new ArrayList<>();
    101     private ViewTreeObserver.OnPreDrawListener mItemAnimationPreDrawListener =
    102             new ViewTreeObserver.OnPreDrawListener() {
    103         @Override
    104         public boolean onPreDraw() {
    105             computeMenuItemAnimationInfo(false);
    106             ((View) mMenuView).getViewTreeObserver().removeOnPreDrawListener(this);
    107             runItemAnimations();
    108             return true;
    109         }
    110     };
    111     private View.OnAttachStateChangeListener mAttachStateChangeListener =
    112             new View.OnAttachStateChangeListener() {
    113         @Override
    114         public void onViewAttachedToWindow(View v) {
    115         }
    116 
    117         @Override
    118         public void onViewDetachedFromWindow(View v) {
    119             ((View) mMenuView).getViewTreeObserver().removeOnPreDrawListener(
    120                     mItemAnimationPreDrawListener);
    121             mPreLayoutItems.clear();
    122             mPostLayoutItems.clear();
    123         }
    124     };
    125 
    126 
    127     public ActionMenuPresenter(Context context) {
    128         super(context, com.android.internal.R.layout.action_menu_layout,
    129                 com.android.internal.R.layout.action_menu_item_layout);
    130     }
    131 
    132     @Override
    133     public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
    134         super.initForMenu(context, menu);
    135 
    136         final Resources res = context.getResources();
    137 
    138         final ActionBarPolicy abp = ActionBarPolicy.get(context);
    139         if (!mReserveOverflowSet) {
    140             mReserveOverflow = abp.showsOverflowMenuButton();
    141         }
    142 
    143         if (!mWidthLimitSet) {
    144             mWidthLimit = abp.getEmbeddedMenuWidthLimit();
    145         }
    146 
    147         // Measure for initial configuration
    148         if (!mMaxItemsSet) {
    149             mMaxItems = abp.getMaxActionButtons();
    150         }
    151 
    152         int width = mWidthLimit;
    153         if (mReserveOverflow) {
    154             if (mOverflowButton == null) {
    155                 mOverflowButton = new OverflowMenuButton(mSystemContext);
    156                 if (mPendingOverflowIconSet) {
    157                     mOverflowButton.setImageDrawable(mPendingOverflowIcon);
    158                     mPendingOverflowIcon = null;
    159                     mPendingOverflowIconSet = false;
    160                 }
    161                 final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    162                 mOverflowButton.measure(spec, spec);
    163             }
    164             width -= mOverflowButton.getMeasuredWidth();
    165         } else {
    166             mOverflowButton = null;
    167         }
    168 
    169         mActionItemWidthLimit = width;
    170 
    171         mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
    172     }
    173 
    174     public void onConfigurationChanged(Configuration newConfig) {
    175         if (!mMaxItemsSet) {
    176             mMaxItems = ActionBarPolicy.get(mContext).getMaxActionButtons();
    177         }
    178         if (mMenu != null) {
    179             mMenu.onItemsChanged(true);
    180         }
    181     }
    182 
    183     public void setWidthLimit(int width, boolean strict) {
    184         mWidthLimit = width;
    185         mStrictWidthLimit = strict;
    186         mWidthLimitSet = true;
    187     }
    188 
    189     public void setReserveOverflow(boolean reserveOverflow) {
    190         mReserveOverflow = reserveOverflow;
    191         mReserveOverflowSet = true;
    192     }
    193 
    194     public void setItemLimit(int itemCount) {
    195         mMaxItems = itemCount;
    196         mMaxItemsSet = true;
    197     }
    198 
    199     public void setExpandedActionViewsExclusive(boolean isExclusive) {
    200         mExpandedActionViewsExclusive = isExclusive;
    201     }
    202 
    203     public void setOverflowIcon(Drawable icon) {
    204         if (mOverflowButton != null) {
    205             mOverflowButton.setImageDrawable(icon);
    206         } else {
    207             mPendingOverflowIconSet = true;
    208             mPendingOverflowIcon = icon;
    209         }
    210     }
    211 
    212     public Drawable getOverflowIcon() {
    213         if (mOverflowButton != null) {
    214             return mOverflowButton.getDrawable();
    215         } else if (mPendingOverflowIconSet) {
    216             return mPendingOverflowIcon;
    217         }
    218         return null;
    219     }
    220 
    221     @Override
    222     public MenuView getMenuView(ViewGroup root) {
    223         MenuView oldMenuView = mMenuView;
    224         MenuView result = super.getMenuView(root);
    225         if (oldMenuView != result) {
    226             ((ActionMenuView) result).setPresenter(this);
    227             if (oldMenuView != null) {
    228                 ((View) oldMenuView).removeOnAttachStateChangeListener(mAttachStateChangeListener);
    229             }
    230             ((View) result).addOnAttachStateChangeListener(mAttachStateChangeListener);
    231         }
    232         return result;
    233     }
    234 
    235     @Override
    236     public View getItemView(final MenuItemImpl item, View convertView, ViewGroup parent) {
    237         View actionView = item.getActionView();
    238         if (actionView == null || item.hasCollapsibleActionView()) {
    239             actionView = super.getItemView(item, convertView, parent);
    240         }
    241         actionView.setVisibility(item.isActionViewExpanded() ? View.GONE : View.VISIBLE);
    242 
    243         final ActionMenuView menuParent = (ActionMenuView) parent;
    244         final ViewGroup.LayoutParams lp = actionView.getLayoutParams();
    245         if (!menuParent.checkLayoutParams(lp)) {
    246             actionView.setLayoutParams(menuParent.generateLayoutParams(lp));
    247         }
    248         return actionView;
    249     }
    250 
    251     @Override
    252     public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
    253         itemView.initialize(item, 0);
    254 
    255         final ActionMenuView menuView = (ActionMenuView) mMenuView;
    256         final ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;
    257         actionItemView.setItemInvoker(menuView);
    258 
    259         if (mPopupCallback == null) {
    260             mPopupCallback = new ActionMenuPopupCallback();
    261         }
    262         actionItemView.setPopupCallback(mPopupCallback);
    263     }
    264 
    265     @Override
    266     public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
    267         return item.isActionButton();
    268     }
    269 
    270     /**
    271      * Store layout information about current items in the menu. This is stored for
    272      * both pre- and post-layout phases and compared in runItemAnimations() to determine
    273      * the animations that need to be run on any item changes.
    274      *
    275      * @param preLayout Whether this is being called in the pre-layout phase. This is passed
    276      * into the MenuItemLayoutInfo structure to store the appropriate position values.
    277      */
    278     private void computeMenuItemAnimationInfo(boolean preLayout) {
    279         final ViewGroup menuView = (ViewGroup) mMenuView;
    280         final int count = menuView.getChildCount();
    281         SparseArray items = preLayout ? mPreLayoutItems : mPostLayoutItems;
    282         for (int i = 0; i < count; ++i) {
    283             View child = menuView.getChildAt(i);
    284             final int id = child.getId();
    285             if (id > 0 && child.getWidth() != 0 && child.getHeight() != 0) {
    286                 MenuItemLayoutInfo info = new MenuItemLayoutInfo(child, preLayout);
    287                 items.put(id, info);
    288             }
    289         }
    290     }
    291 
    292     /**
    293      * This method is called once both the pre-layout and post-layout steps have
    294      * happened. It figures out which views are new (didn't exist prior to layout),
    295      * gone (existed pre-layout, but are now gone), or changed (exist in both,
    296      * but in a different location) and runs appropriate animations on those views.
    297      * Items are tracked by ids, since the underlying views that represent items
    298      * pre- and post-layout may be different.
    299      */
    300     private void runItemAnimations() {
    301         for (int i = 0; i < mPreLayoutItems.size(); ++i) {
    302             int id = mPreLayoutItems.keyAt(i);
    303             final MenuItemLayoutInfo menuItemLayoutInfoPre = mPreLayoutItems.get(id);
    304             final int postLayoutIndex = mPostLayoutItems.indexOfKey(id);
    305             if (postLayoutIndex >= 0) {
    306                 // item exists pre and post: see if it's changed
    307                 final MenuItemLayoutInfo menuItemLayoutInfoPost =
    308                         mPostLayoutItems.valueAt(postLayoutIndex);
    309                 PropertyValuesHolder pvhX = null;
    310                 PropertyValuesHolder pvhY = null;
    311                 if (menuItemLayoutInfoPre.left != menuItemLayoutInfoPost.left) {
    312                     pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X,
    313                             (menuItemLayoutInfoPre.left - menuItemLayoutInfoPost.left), 0);
    314                 }
    315                 if (menuItemLayoutInfoPre.top != menuItemLayoutInfoPost.top) {
    316                     pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
    317                             menuItemLayoutInfoPre.top - menuItemLayoutInfoPost.top, 0);
    318                 }
    319                 if (pvhX != null || pvhY != null) {
    320                     for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
    321                         ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
    322                         if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.MOVE) {
    323                             oldInfo.animator.cancel();
    324                         }
    325                     }
    326                     ObjectAnimator anim;
    327                     if (pvhX != null) {
    328                         if (pvhY != null) {
    329                             anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view,
    330                                     pvhX, pvhY);
    331                         } else {
    332                             anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, pvhX);
    333                         }
    334                     } else {
    335                         anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, pvhY);
    336                     }
    337                     anim.setDuration(ITEM_ANIMATION_DURATION);
    338                     anim.start();
    339                     ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfoPost, anim,
    340                             ItemAnimationInfo.MOVE);
    341                     mRunningItemAnimations.add(info);
    342                     anim.addListener(new AnimatorListenerAdapter() {
    343                         @Override
    344                         public void onAnimationEnd(Animator animation) {
    345                             for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
    346                                 if (mRunningItemAnimations.get(j).animator == animation) {
    347                                     mRunningItemAnimations.remove(j);
    348                                     break;
    349                                 }
    350                             }
    351                         }
    352                     });
    353                 }
    354                 mPostLayoutItems.remove(id);
    355             } else {
    356                 // item used to be there, is now gone
    357                 float oldAlpha = 1;
    358                 for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
    359                     ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
    360                     if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.FADE_IN) {
    361                         oldAlpha = oldInfo.menuItemLayoutInfo.view.getAlpha();
    362                         oldInfo.animator.cancel();
    363                     }
    364                 }
    365                 ObjectAnimator anim = ObjectAnimator.ofFloat(menuItemLayoutInfoPre.view, View.ALPHA,
    366                         oldAlpha, 0);
    367                 // Re-using the view from pre-layout assumes no view recycling
    368                 ((ViewGroup) mMenuView).getOverlay().add(menuItemLayoutInfoPre.view);
    369                 anim.setDuration(ITEM_ANIMATION_DURATION);
    370                 anim.start();
    371                 ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfoPre, anim, ItemAnimationInfo.FADE_OUT);
    372                 mRunningItemAnimations.add(info);
    373                 anim.addListener(new AnimatorListenerAdapter() {
    374                     @Override
    375                     public void onAnimationEnd(Animator animation) {
    376                         for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
    377                             if (mRunningItemAnimations.get(j).animator == animation) {
    378                                 mRunningItemAnimations.remove(j);
    379                                 break;
    380                             }
    381                         }
    382                         ((ViewGroup) mMenuView).getOverlay().remove(menuItemLayoutInfoPre.view);
    383                     }
    384                 });
    385             }
    386         }
    387         for (int i = 0; i < mPostLayoutItems.size(); ++i) {
    388             int id = mPostLayoutItems.keyAt(i);
    389             final int postLayoutIndex = mPostLayoutItems.indexOfKey(id);
    390             if (postLayoutIndex >= 0) {
    391                 // item is new
    392                 final MenuItemLayoutInfo menuItemLayoutInfo =
    393                         mPostLayoutItems.valueAt(postLayoutIndex);
    394                 float oldAlpha = 0;
    395                 for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
    396                     ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
    397                     if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.FADE_OUT) {
    398                         oldAlpha = oldInfo.menuItemLayoutInfo.view.getAlpha();
    399                         oldInfo.animator.cancel();
    400                     }
    401                 }
    402                 ObjectAnimator anim = ObjectAnimator.ofFloat(menuItemLayoutInfo.view, View.ALPHA,
    403                         oldAlpha, 1);
    404                 anim.start();
    405                 anim.setDuration(ITEM_ANIMATION_DURATION);
    406                 ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfo, anim, ItemAnimationInfo.FADE_IN);
    407                 mRunningItemAnimations.add(info);
    408                 anim.addListener(new AnimatorListenerAdapter() {
    409                     @Override
    410                     public void onAnimationEnd(Animator animation) {
    411                         for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
    412                             if (mRunningItemAnimations.get(j).animator == animation) {
    413                                 mRunningItemAnimations.remove(j);
    414                                 break;
    415                             }
    416                         }
    417                     }
    418                 });
    419             }
    420         }
    421         mPreLayoutItems.clear();
    422         mPostLayoutItems.clear();
    423     }
    424 
    425     /**
    426      * Gets position/existence information on menu items before and after layout,
    427      * which is then fed into runItemAnimations()
    428      */
    429     private void setupItemAnimations() {
    430         computeMenuItemAnimationInfo(true);
    431         ((View) mMenuView).getViewTreeObserver().
    432                 addOnPreDrawListener(mItemAnimationPreDrawListener);
    433     }
    434 
    435     @Override
    436     public void updateMenuView(boolean cleared) {
    437         final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
    438         if (menuViewParent != null && ACTIONBAR_ANIMATIONS_ENABLED) {
    439             setupItemAnimations();
    440         }
    441         super.updateMenuView(cleared);
    442 
    443         ((View) mMenuView).requestLayout();
    444 
    445         if (mMenu != null) {
    446             final ArrayList<MenuItemImpl> actionItems = mMenu.getActionItems();
    447             final int count = actionItems.size();
    448             for (int i = 0; i < count; i++) {
    449                 final ActionProvider provider = actionItems.get(i).getActionProvider();
    450                 if (provider != null) {
    451                     provider.setSubUiVisibilityListener(this);
    452                 }
    453             }
    454         }
    455 
    456         final ArrayList<MenuItemImpl> nonActionItems = mMenu != null ?
    457                 mMenu.getNonActionItems() : null;
    458 
    459         boolean hasOverflow = false;
    460         if (mReserveOverflow && nonActionItems != null) {
    461             final int count = nonActionItems.size();
    462             if (count == 1) {
    463                 hasOverflow = !nonActionItems.get(0).isActionViewExpanded();
    464             } else {
    465                 hasOverflow = count > 0;
    466             }
    467         }
    468 
    469         if (hasOverflow) {
    470             if (mOverflowButton == null) {
    471                 mOverflowButton = new OverflowMenuButton(mSystemContext);
    472             }
    473             ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
    474             if (parent != mMenuView) {
    475                 if (parent != null) {
    476                     parent.removeView(mOverflowButton);
    477                 }
    478                 ActionMenuView menuView = (ActionMenuView) mMenuView;
    479                 menuView.addView(mOverflowButton, menuView.generateOverflowButtonLayoutParams());
    480             }
    481         } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
    482             ((ViewGroup) mMenuView).removeView(mOverflowButton);
    483         }
    484 
    485         ((ActionMenuView) mMenuView).setOverflowReserved(mReserveOverflow);
    486     }
    487 
    488     @Override
    489     public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
    490         if (parent.getChildAt(childIndex) == mOverflowButton) return false;
    491         return super.filterLeftoverView(parent, childIndex);
    492     }
    493 
    494     public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
    495         if (!subMenu.hasVisibleItems()) return false;
    496 
    497         SubMenuBuilder topSubMenu = subMenu;
    498         while (topSubMenu.getParentMenu() != mMenu) {
    499             topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
    500         }
    501         View anchor = findViewForItem(topSubMenu.getItem());
    502         if (anchor == null) {
    503             // This means the submenu was opened from an overflow menu item, indicating the
    504             // MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to
    505             // ensure that the MenuPopup acts as presenter for the submenu, and acts on its
    506             // responsibility to display the new submenu.
    507             return false;
    508         }
    509 
    510         mOpenSubMenuId = subMenu.getItem().getItemId();
    511 
    512         boolean preserveIconSpacing = false;
    513         final int count = subMenu.size();
    514         for (int i = 0; i < count; i++) {
    515             MenuItem childItem = subMenu.getItem(i);
    516             if (childItem.isVisible() && childItem.getIcon() != null) {
    517                 preserveIconSpacing = true;
    518                 break;
    519             }
    520         }
    521 
    522         mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor);
    523         mActionButtonPopup.setForceShowIcon(preserveIconSpacing);
    524         mActionButtonPopup.show();
    525 
    526         super.onSubMenuSelected(subMenu);
    527         return true;
    528     }
    529 
    530     private View findViewForItem(MenuItem item) {
    531         final ViewGroup parent = (ViewGroup) mMenuView;
    532         if (parent == null) return null;
    533 
    534         final int count = parent.getChildCount();
    535         for (int i = 0; i < count; i++) {
    536             final View child = parent.getChildAt(i);
    537             if (child instanceof MenuView.ItemView &&
    538                     ((MenuView.ItemView) child).getItemData() == item) {
    539                 return child;
    540             }
    541         }
    542         return null;
    543     }
    544 
    545     /**
    546      * Display the overflow menu if one is present.
    547      * @return true if the overflow menu was shown, false otherwise.
    548      */
    549     public boolean showOverflowMenu() {
    550         if (mReserveOverflow && !isOverflowMenuShowing() && mMenu != null && mMenuView != null &&
    551                 mPostedOpenRunnable == null && !mMenu.getNonActionItems().isEmpty()) {
    552             OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
    553             mPostedOpenRunnable = new OpenOverflowRunnable(popup);
    554             // Post this for later; we might still need a layout for the anchor to be right.
    555             ((View) mMenuView).post(mPostedOpenRunnable);
    556 
    557             // ActionMenuPresenter uses null as a callback argument here
    558             // to indicate overflow is opening.
    559             super.onSubMenuSelected(null);
    560 
    561             return true;
    562         }
    563         return false;
    564     }
    565 
    566     /**
    567      * Hide the overflow menu if it is currently showing.
    568      *
    569      * @return true if the overflow menu was hidden, false otherwise.
    570      */
    571     public boolean hideOverflowMenu() {
    572         if (mPostedOpenRunnable != null && mMenuView != null) {
    573             ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
    574             mPostedOpenRunnable = null;
    575             return true;
    576         }
    577 
    578         MenuPopupHelper popup = mOverflowPopup;
    579         if (popup != null) {
    580             popup.dismiss();
    581             return true;
    582         }
    583         return false;
    584     }
    585 
    586     /**
    587      * Dismiss all popup menus - overflow and submenus.
    588      * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
    589      */
    590     @UnsupportedAppUsage
    591     public boolean dismissPopupMenus() {
    592         boolean result = hideOverflowMenu();
    593         result |= hideSubMenus();
    594         return result;
    595     }
    596 
    597     /**
    598      * Dismiss all submenu popups.
    599      *
    600      * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
    601      */
    602     public boolean hideSubMenus() {
    603         if (mActionButtonPopup != null) {
    604             mActionButtonPopup.dismiss();
    605             return true;
    606         }
    607         return false;
    608     }
    609 
    610     /**
    611      * @return true if the overflow menu is currently showing
    612      */
    613     @UnsupportedAppUsage
    614     public boolean isOverflowMenuShowing() {
    615         return mOverflowPopup != null && mOverflowPopup.isShowing();
    616     }
    617 
    618     public boolean isOverflowMenuShowPending() {
    619         return mPostedOpenRunnable != null || isOverflowMenuShowing();
    620     }
    621 
    622     /**
    623      * @return true if space has been reserved in the action menu for an overflow item.
    624      */
    625     public boolean isOverflowReserved() {
    626         return mReserveOverflow;
    627     }
    628 
    629     public boolean flagActionItems() {
    630         final ArrayList<MenuItemImpl> visibleItems;
    631         final int itemsSize;
    632         if (mMenu != null) {
    633             visibleItems = mMenu.getVisibleItems();
    634             itemsSize = visibleItems.size();
    635         } else {
    636             visibleItems = null;
    637             itemsSize = 0;
    638         }
    639 
    640         int maxActions = mMaxItems;
    641         int widthLimit = mActionItemWidthLimit;
    642         final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    643         final ViewGroup parent = (ViewGroup) mMenuView;
    644 
    645         int requiredItems = 0;
    646         int requestedItems = 0;
    647         int firstActionWidth = 0;
    648         boolean hasOverflow = false;
    649         for (int i = 0; i < itemsSize; i++) {
    650             MenuItemImpl item = visibleItems.get(i);
    651             if (item.requiresActionButton()) {
    652                 requiredItems++;
    653             } else if (item.requestsActionButton()) {
    654                 requestedItems++;
    655             } else {
    656                 hasOverflow = true;
    657             }
    658             if (mExpandedActionViewsExclusive && item.isActionViewExpanded()) {
    659                 // Overflow everything if we have an expanded action view and we're
    660                 // space constrained.
    661                 maxActions = 0;
    662             }
    663         }
    664 
    665         // Reserve a spot for the overflow item if needed.
    666         if (mReserveOverflow &&
    667                 (hasOverflow || requiredItems + requestedItems > maxActions)) {
    668             maxActions--;
    669         }
    670         maxActions -= requiredItems;
    671 
    672         final SparseBooleanArray seenGroups = mActionButtonGroups;
    673         seenGroups.clear();
    674 
    675         int cellSize = 0;
    676         int cellsRemaining = 0;
    677         if (mStrictWidthLimit) {
    678             cellsRemaining = widthLimit / mMinCellSize;
    679             final int cellSizeRemaining = widthLimit % mMinCellSize;
    680             cellSize = mMinCellSize + cellSizeRemaining / cellsRemaining;
    681         }
    682 
    683         // Flag as many more requested items as will fit.
    684         for (int i = 0; i < itemsSize; i++) {
    685             MenuItemImpl item = visibleItems.get(i);
    686 
    687             if (item.requiresActionButton()) {
    688                 View v = getItemView(item, null, parent);
    689                 if (mStrictWidthLimit) {
    690                     cellsRemaining -= ActionMenuView.measureChildForCells(v,
    691                             cellSize, cellsRemaining, querySpec, 0);
    692                 } else {
    693                     v.measure(querySpec, querySpec);
    694                 }
    695                 final int measuredWidth = v.getMeasuredWidth();
    696                 widthLimit -= measuredWidth;
    697                 if (firstActionWidth == 0) {
    698                     firstActionWidth = measuredWidth;
    699                 }
    700                 final int groupId = item.getGroupId();
    701                 if (groupId != 0) {
    702                     seenGroups.put(groupId, true);
    703                 }
    704                 item.setIsActionButton(true);
    705             } else if (item.requestsActionButton()) {
    706                 // Items in a group with other items that already have an action slot
    707                 // can break the max actions rule, but not the width limit.
    708                 final int groupId = item.getGroupId();
    709                 final boolean inGroup = seenGroups.get(groupId);
    710                 boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0 &&
    711                         (!mStrictWidthLimit || cellsRemaining > 0);
    712 
    713                 if (isAction) {
    714                     View v = getItemView(item, null, parent);
    715                     if (mStrictWidthLimit) {
    716                         final int cells = ActionMenuView.measureChildForCells(v,
    717                                 cellSize, cellsRemaining, querySpec, 0);
    718                         cellsRemaining -= cells;
    719                         if (cells == 0) {
    720                             isAction = false;
    721                         }
    722                     } else {
    723                         v.measure(querySpec, querySpec);
    724                     }
    725                     final int measuredWidth = v.getMeasuredWidth();
    726                     widthLimit -= measuredWidth;
    727                     if (firstActionWidth == 0) {
    728                         firstActionWidth = measuredWidth;
    729                     }
    730 
    731                     if (mStrictWidthLimit) {
    732                         isAction &= widthLimit >= 0;
    733                     } else {
    734                         // Did this push the entire first item past the limit?
    735                         isAction &= widthLimit + firstActionWidth > 0;
    736                     }
    737                 }
    738 
    739                 if (isAction && groupId != 0) {
    740                     seenGroups.put(groupId, true);
    741                 } else if (inGroup) {
    742                     // We broke the width limit. Demote the whole group, they all overflow now.
    743                     seenGroups.put(groupId, false);
    744                     for (int j = 0; j < i; j++) {
    745                         MenuItemImpl areYouMyGroupie = visibleItems.get(j);
    746                         if (areYouMyGroupie.getGroupId() == groupId) {
    747                             // Give back the action slot
    748                             if (areYouMyGroupie.isActionButton()) maxActions++;
    749                             areYouMyGroupie.setIsActionButton(false);
    750                         }
    751                     }
    752                 }
    753 
    754                 if (isAction) maxActions--;
    755 
    756                 item.setIsActionButton(isAction);
    757             } else {
    758                 // Neither requires nor requests an action button.
    759                 item.setIsActionButton(false);
    760             }
    761         }
    762         return true;
    763     }
    764 
    765     @Override
    766     public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
    767         dismissPopupMenus();
    768         super.onCloseMenu(menu, allMenusAreClosing);
    769     }
    770 
    771     @Override
    772     @UnsupportedAppUsage
    773     public Parcelable onSaveInstanceState() {
    774         SavedState state = new SavedState();
    775         state.openSubMenuId = mOpenSubMenuId;
    776         return state;
    777     }
    778 
    779     @Override
    780     @UnsupportedAppUsage
    781     public void onRestoreInstanceState(Parcelable state) {
    782         SavedState saved = (SavedState) state;
    783         if (saved.openSubMenuId > 0) {
    784             MenuItem item = mMenu.findItem(saved.openSubMenuId);
    785             if (item != null) {
    786                 SubMenuBuilder subMenu = (SubMenuBuilder) item.getSubMenu();
    787                 onSubMenuSelected(subMenu);
    788             }
    789         }
    790     }
    791 
    792     @Override
    793     public void onSubUiVisibilityChanged(boolean isVisible) {
    794         if (isVisible) {
    795             // Not a submenu, but treat it like one.
    796             super.onSubMenuSelected(null);
    797         } else if (mMenu != null) {
    798             mMenu.close(false /* closeAllMenus */);
    799         }
    800     }
    801 
    802     public void setMenuView(ActionMenuView menuView) {
    803         if (menuView != mMenuView) {
    804             if (mMenuView != null) {
    805                 ((View) mMenuView).removeOnAttachStateChangeListener(mAttachStateChangeListener);
    806             }
    807             mMenuView = menuView;
    808             menuView.initialize(mMenu);
    809             menuView.addOnAttachStateChangeListener(mAttachStateChangeListener);
    810         }
    811     }
    812 
    813     private static class SavedState implements Parcelable {
    814         public int openSubMenuId;
    815 
    816         SavedState() {
    817         }
    818 
    819         SavedState(Parcel in) {
    820             openSubMenuId = in.readInt();
    821         }
    822 
    823         @Override
    824         public int describeContents() {
    825             return 0;
    826         }
    827 
    828         @Override
    829         public void writeToParcel(Parcel dest, int flags) {
    830             dest.writeInt(openSubMenuId);
    831         }
    832 
    833         public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR
    834                 = new Parcelable.Creator<SavedState>() {
    835             public SavedState createFromParcel(Parcel in) {
    836                 return new SavedState(in);
    837             }
    838 
    839             public SavedState[] newArray(int size) {
    840                 return new SavedState[size];
    841             }
    842         };
    843     }
    844 
    845     private class OverflowMenuButton extends ImageButton implements ActionMenuView.ActionMenuChildView {
    846         public OverflowMenuButton(Context context) {
    847             super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
    848 
    849             setClickable(true);
    850             setFocusable(true);
    851             setVisibility(VISIBLE);
    852             setEnabled(true);
    853 
    854             setOnTouchListener(new ForwardingListener(this) {
    855                 @Override
    856                 public ShowableListMenu getPopup() {
    857                     if (mOverflowPopup == null) {
    858                         return null;
    859                     }
    860 
    861                     return mOverflowPopup.getPopup();
    862                 }
    863 
    864                 @Override
    865                 public boolean onForwardingStarted() {
    866                     showOverflowMenu();
    867                     return true;
    868                 }
    869 
    870                 @Override
    871                 public boolean onForwardingStopped() {
    872                     // Displaying the popup occurs asynchronously, so wait for
    873                     // the runnable to finish before deciding whether to stop
    874                     // forwarding.
    875                     if (mPostedOpenRunnable != null) {
    876                         return false;
    877                     }
    878 
    879                     hideOverflowMenu();
    880                     return true;
    881                 }
    882             });
    883         }
    884 
    885         @Override
    886         public boolean performClick() {
    887             if (super.performClick()) {
    888                 return true;
    889             }
    890 
    891             playSoundEffect(SoundEffectConstants.CLICK);
    892             showOverflowMenu();
    893             return true;
    894         }
    895 
    896         @Override
    897         public boolean needsDividerBefore() {
    898             return false;
    899         }
    900 
    901         @Override
    902         public boolean needsDividerAfter() {
    903             return false;
    904         }
    905 
    906     /** @hide */
    907         @Override
    908         public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
    909             super.onInitializeAccessibilityNodeInfoInternal(info);
    910             info.setCanOpenPopup(true);
    911         }
    912 
    913         @Override
    914         protected boolean setFrame(int l, int t, int r, int b) {
    915             final boolean changed = super.setFrame(l, t, r, b);
    916 
    917             // Set up the hotspot bounds to square and centered on the image.
    918             final Drawable d = getDrawable();
    919             final Drawable bg = getBackground();
    920             if (d != null && bg != null) {
    921                 final int width = getWidth();
    922                 final int height = getHeight();
    923                 final int halfEdge = Math.max(width, height) / 2;
    924                 final int offsetX = getPaddingLeft() - getPaddingRight();
    925                 final int offsetY = getPaddingTop() - getPaddingBottom();
    926                 final int centerX = (width + offsetX) / 2;
    927                 final int centerY = (height + offsetY) / 2;
    928                 bg.setHotspotBounds(centerX - halfEdge, centerY - halfEdge,
    929                         centerX + halfEdge, centerY + halfEdge);
    930             }
    931 
    932             return changed;
    933         }
    934     }
    935 
    936     private class OverflowPopup extends MenuPopupHelper {
    937         public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
    938                 boolean overflowOnly) {
    939             super(context, menu, anchorView, overflowOnly,
    940                     com.android.internal.R.attr.actionOverflowMenuStyle);
    941             setGravity(Gravity.END);
    942             setPresenterCallback(mPopupPresenterCallback);
    943         }
    944 
    945         @Override
    946         protected void onDismiss() {
    947             if (mMenu != null) {
    948                 mMenu.close();
    949             }
    950             mOverflowPopup = null;
    951 
    952             super.onDismiss();
    953         }
    954     }
    955 
    956     private class ActionButtonSubmenu extends MenuPopupHelper {
    957         public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) {
    958             super(context, subMenu, anchorView, false,
    959                     com.android.internal.R.attr.actionOverflowMenuStyle);
    960 
    961             MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
    962             if (!item.isActionButton()) {
    963                 // Give a reasonable anchor to nested submenus.
    964                 setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
    965             }
    966 
    967             setPresenterCallback(mPopupPresenterCallback);
    968         }
    969 
    970         @Override
    971         protected void onDismiss() {
    972             mActionButtonPopup = null;
    973             mOpenSubMenuId = 0;
    974 
    975             super.onDismiss();
    976         }
    977     }
    978 
    979     private class PopupPresenterCallback implements Callback {
    980 
    981         @Override
    982         public boolean onOpenSubMenu(MenuBuilder subMenu) {
    983             if (subMenu == null) return false;
    984 
    985             mOpenSubMenuId = ((SubMenuBuilder) subMenu).getItem().getItemId();
    986             final Callback cb = getCallback();
    987             return cb != null ? cb.onOpenSubMenu(subMenu) : false;
    988         }
    989 
    990         @Override
    991         public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
    992             if (menu instanceof SubMenuBuilder) {
    993                 menu.getRootMenu().close(false /* closeAllMenus */);
    994             }
    995             final Callback cb = getCallback();
    996             if (cb != null) {
    997                 cb.onCloseMenu(menu, allMenusAreClosing);
    998             }
    999         }
   1000     }
   1001 
   1002     private class OpenOverflowRunnable implements Runnable {
   1003         private OverflowPopup mPopup;
   1004 
   1005         public OpenOverflowRunnable(OverflowPopup popup) {
   1006             mPopup = popup;
   1007         }
   1008 
   1009         public void run() {
   1010             if (mMenu != null) {
   1011                 mMenu.changeMenuMode();
   1012             }
   1013             final View menuView = (View) mMenuView;
   1014             if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
   1015                 mOverflowPopup = mPopup;
   1016             }
   1017             mPostedOpenRunnable = null;
   1018         }
   1019     }
   1020 
   1021     private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
   1022         @Override
   1023         public ShowableListMenu getPopup() {
   1024             return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
   1025         }
   1026     }
   1027 
   1028     /**
   1029      * This class holds layout information for a menu item. This is used to determine
   1030      * pre- and post-layout information about menu items, which will then be used to
   1031      * determine appropriate item animations.
   1032      */
   1033     private static class MenuItemLayoutInfo {
   1034         View view;
   1035         int left;
   1036         int top;
   1037 
   1038         MenuItemLayoutInfo(View view, boolean preLayout) {
   1039             left = view.getLeft();
   1040             top = view.getTop();
   1041             if (preLayout) {
   1042                 // We track translation for pre-layout because a view might be mid-animation
   1043                 // and we need this information to know where to animate from
   1044                 left += view.getTranslationX();
   1045                 top += view.getTranslationY();
   1046             }
   1047             this.view = view;
   1048         }
   1049     }
   1050 
   1051     /**
   1052      * This class is used to store information about currently-running item animations.
   1053      * This is used when new animations are scheduled to determine whether any existing
   1054      * animations need to be canceled, based on whether the running animations overlap
   1055      * with any new animations. For example, if an item is currently animating from
   1056      * location A to B and another change dictates that it be animated to C, then the current
   1057      * A-B animation will be canceled and a new animation to C will be started.
   1058      */
   1059     private static class ItemAnimationInfo {
   1060         int id;
   1061         MenuItemLayoutInfo menuItemLayoutInfo;
   1062         Animator animator;
   1063         int animType;
   1064         static final int MOVE = 0;
   1065         static final int FADE_IN = 1;
   1066         static final int FADE_OUT = 2;
   1067 
   1068         ItemAnimationInfo(int id, MenuItemLayoutInfo info, Animator anim, int animType) {
   1069             this.id = id;
   1070             menuItemLayoutInfo = info;
   1071             animator = anim;
   1072             this.animType = animType;
   1073         }
   1074     }
   1075 }
   1076