Home | History | Annotate | Download | only in menu
      1 package com.android.internal.view.menu;
      2 
      3 import java.lang.annotation.Retention;
      4 import java.lang.annotation.RetentionPolicy;
      5 import java.util.ArrayList;
      6 import java.util.LinkedList;
      7 import java.util.List;
      8 
      9 import android.annotation.AttrRes;
     10 import android.annotation.IntDef;
     11 import android.annotation.NonNull;
     12 import android.annotation.Nullable;
     13 import android.annotation.StyleRes;
     14 import android.content.Context;
     15 import android.content.res.Resources;
     16 import android.graphics.Rect;
     17 import android.os.Handler;
     18 import android.os.Parcelable;
     19 import android.os.SystemClock;
     20 import android.view.Gravity;
     21 import android.view.KeyEvent;
     22 import android.view.LayoutInflater;
     23 import android.view.MenuItem;
     24 import android.view.View;
     25 import android.view.ViewTreeObserver;
     26 import android.view.View.OnAttachStateChangeListener;
     27 import android.view.View.OnKeyListener;
     28 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
     29 import android.widget.AbsListView;
     30 import android.widget.FrameLayout;
     31 import android.widget.HeaderViewListAdapter;
     32 import android.widget.ListAdapter;
     33 import android.widget.MenuItemHoverListener;
     34 import android.widget.ListView;
     35 import android.widget.MenuPopupWindow;
     36 import android.widget.PopupWindow;
     37 import android.widget.PopupWindow.OnDismissListener;
     38 import android.widget.TextView;
     39 
     40 import com.android.internal.R;
     41 import com.android.internal.util.Preconditions;
     42 
     43 /**
     44  * A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by
     45  * side.
     46  * @hide
     47  */
     48 final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKeyListener,
     49         PopupWindow.OnDismissListener {
     50 
     51     @Retention(RetentionPolicy.SOURCE)
     52     @IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT})
     53     public @interface HorizPosition {}
     54 
     55     private static final int HORIZ_POSITION_LEFT = 0;
     56     private static final int HORIZ_POSITION_RIGHT = 1;
     57 
     58     /**
     59      * Delay between hovering over a menu item with a mouse and receiving
     60      * side-effects (ex. opening a sub-menu or closing unrelated menus).
     61      */
     62     private static final int SUBMENU_TIMEOUT_MS = 200;
     63 
     64     private final Context mContext;
     65     private final int mMenuMaxWidth;
     66     private final int mPopupStyleAttr;
     67     private final int mPopupStyleRes;
     68     private final boolean mOverflowOnly;
     69     private final Handler mSubMenuHoverHandler;
     70 
     71     /** List of menus that were added before this popup was shown. */
     72     private final List<MenuBuilder> mPendingMenus = new LinkedList<>();
     73 
     74     /**
     75      * List of open menus. The first item is the root menu and each
     76      * subsequent item is a direct submenu of the previous item.
     77      */
     78     private final List<CascadingMenuInfo> mShowingMenus = new ArrayList<>();
     79 
     80     private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
     81         @Override
     82         public void onGlobalLayout() {
     83             // Only move the popup if it's showing and non-modal. We don't want
     84             // to be moving around the only interactive window, since there's a
     85             // good chance the user is interacting with it.
     86             if (isShowing() && mShowingMenus.size() > 0
     87                     && !mShowingMenus.get(0).window.isModal()) {
     88                 final View anchor = mShownAnchorView;
     89                 if (anchor == null || !anchor.isShown()) {
     90                     dismiss();
     91                 } else {
     92                     // Recompute window sizes and positions.
     93                     for (CascadingMenuInfo info : mShowingMenus) {
     94                         info.window.show();
     95                     }
     96                 }
     97             }
     98         }
     99     };
    100 
    101     private final OnAttachStateChangeListener mAttachStateChangeListener =
    102             new OnAttachStateChangeListener() {
    103                 @Override
    104                 public void onViewAttachedToWindow(View v) {
    105                 }
    106 
    107                 @Override
    108                 public void onViewDetachedFromWindow(View v) {
    109                     if (mTreeObserver != null) {
    110                         if (!mTreeObserver.isAlive()) {
    111                             mTreeObserver = v.getViewTreeObserver();
    112                         }
    113                         mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
    114                     }
    115                     v.removeOnAttachStateChangeListener(this);
    116                 }
    117             };
    118 
    119     private final MenuItemHoverListener mMenuItemHoverListener = new MenuItemHoverListener() {
    120         @Override
    121         public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
    122             // If the mouse moves between two windows, hover enter/exit pairs
    123             // may be received out of order. So, instead of canceling all
    124             // pending runnables, only cancel runnables for the host menu.
    125             mSubMenuHoverHandler.removeCallbacksAndMessages(menu);
    126         }
    127 
    128         @Override
    129         public void onItemHoverEnter(
    130                 @NonNull final MenuBuilder menu, @NonNull final MenuItem item) {
    131             // Something new was hovered, cancel all scheduled runnables.
    132             mSubMenuHoverHandler.removeCallbacksAndMessages(null);
    133 
    134             // Find the position of the hovered menu within the added menus.
    135             int menuIndex = -1;
    136             for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
    137                 if (menu == mShowingMenus.get(i).menu) {
    138                     menuIndex = i;
    139                     break;
    140                 }
    141             }
    142 
    143             if (menuIndex == -1) {
    144                 return;
    145             }
    146 
    147             final CascadingMenuInfo nextInfo;
    148             final int nextIndex = menuIndex + 1;
    149             if (nextIndex < mShowingMenus.size()) {
    150                 nextInfo = mShowingMenus.get(nextIndex);
    151             } else {
    152                 nextInfo = null;
    153             }
    154 
    155             final Runnable runnable = new Runnable() {
    156                 @Override
    157                 public void run() {
    158                     // Close any other submenus that might be open at the
    159                     // current or a deeper level.
    160                     if (nextInfo != null) {
    161                         // Disable exit animations to prevent overlapping
    162                         // fading out submenus.
    163                         mShouldCloseImmediately = true;
    164                         nextInfo.menu.close(false /* closeAllMenus */);
    165                         mShouldCloseImmediately = false;
    166                     }
    167 
    168                     // Then open the selected submenu, if there is one.
    169                     if (item.isEnabled() && item.hasSubMenu()) {
    170                         menu.performItemAction(item, 0);
    171                     }
    172                 }
    173             };
    174             final long uptimeMillis = SystemClock.uptimeMillis() + SUBMENU_TIMEOUT_MS;
    175             mSubMenuHoverHandler.postAtTime(runnable, menu, uptimeMillis);
    176         }
    177     };
    178 
    179     private int mRawDropDownGravity = Gravity.NO_GRAVITY;
    180     private int mDropDownGravity = Gravity.NO_GRAVITY;
    181     private View mAnchorView;
    182     private View mShownAnchorView;
    183     private int mLastPosition;
    184     private boolean mHasXOffset;
    185     private boolean mHasYOffset;
    186     private int mXOffset;
    187     private int mYOffset;
    188     private boolean mForceShowIcon;
    189     private boolean mShowTitle;
    190     private Callback mPresenterCallback;
    191     private ViewTreeObserver mTreeObserver;
    192     private PopupWindow.OnDismissListener mOnDismissListener;
    193 
    194     /** Whether popup menus should disable exit animations when closing. */
    195     private boolean mShouldCloseImmediately;
    196 
    197     /**
    198      * Initializes a new cascading-capable menu popup.
    199      *
    200      * @param anchor A parent view to get the {@link android.view.View#getWindowToken()} token from.
    201      */
    202     public CascadingMenuPopup(@NonNull Context context, @NonNull View anchor,
    203             @AttrRes int popupStyleAttr, @StyleRes int popupStyleRes, boolean overflowOnly) {
    204         mContext = Preconditions.checkNotNull(context);
    205         mAnchorView = Preconditions.checkNotNull(anchor);
    206         mPopupStyleAttr = popupStyleAttr;
    207         mPopupStyleRes = popupStyleRes;
    208         mOverflowOnly = overflowOnly;
    209 
    210         mForceShowIcon = false;
    211         mLastPosition = getInitialMenuPosition();
    212 
    213         final Resources res = context.getResources();
    214         mMenuMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
    215                 res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
    216 
    217         mSubMenuHoverHandler = new Handler();
    218     }
    219 
    220     @Override
    221     public void setForceShowIcon(boolean forceShow) {
    222         mForceShowIcon = forceShow;
    223     }
    224 
    225     private MenuPopupWindow createPopupWindow() {
    226         MenuPopupWindow popupWindow = new MenuPopupWindow(
    227                 mContext, null, mPopupStyleAttr, mPopupStyleRes);
    228         popupWindow.setHoverListener(mMenuItemHoverListener);
    229         popupWindow.setOnItemClickListener(this);
    230         popupWindow.setOnDismissListener(this);
    231         popupWindow.setAnchorView(mAnchorView);
    232         popupWindow.setDropDownGravity(mDropDownGravity);
    233         popupWindow.setModal(true);
    234         popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
    235         return popupWindow;
    236     }
    237 
    238     @Override
    239     public void show() {
    240         if (isShowing()) {
    241             return;
    242         }
    243 
    244         // Display all pending menus.
    245         for (MenuBuilder menu : mPendingMenus) {
    246             showMenu(menu);
    247         }
    248         mPendingMenus.clear();
    249 
    250         mShownAnchorView = mAnchorView;
    251 
    252         if (mShownAnchorView != null) {
    253             final boolean addGlobalListener = mTreeObserver == null;
    254             mTreeObserver = mShownAnchorView.getViewTreeObserver(); // Refresh to latest
    255             if (addGlobalListener) {
    256                 mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
    257             }
    258             mShownAnchorView.addOnAttachStateChangeListener(mAttachStateChangeListener);
    259         }
    260     }
    261 
    262     @Override
    263     public void dismiss() {
    264         // Need to make another list to avoid a concurrent modification
    265         // exception, as #onDismiss may clear mPopupWindows while we are
    266         // iterating. Remove from the last added menu so that the callbacks
    267         // are received in order from foreground to background.
    268         final int length = mShowingMenus.size();
    269         if (length > 0) {
    270             final CascadingMenuInfo[] addedMenus =
    271                     mShowingMenus.toArray(new CascadingMenuInfo[length]);
    272             for (int i = length - 1; i >= 0; i--) {
    273                 final CascadingMenuInfo info = addedMenus[i];
    274                 if (info.window.isShowing()) {
    275                     info.window.dismiss();
    276                 }
    277             }
    278         }
    279     }
    280 
    281     @Override
    282     public boolean onKey(View v, int keyCode, KeyEvent event) {
    283         if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
    284             dismiss();
    285             return true;
    286         }
    287         return false;
    288     }
    289 
    290     /**
    291      * Determines the proper initial menu position for the current LTR/RTL configuration.
    292      * @return The initial position.
    293      */
    294     @HorizPosition
    295     private int getInitialMenuPosition() {
    296         final int layoutDirection = mAnchorView.getLayoutDirection();
    297         return layoutDirection == View.LAYOUT_DIRECTION_RTL ? HORIZ_POSITION_LEFT :
    298                 HORIZ_POSITION_RIGHT;
    299     }
    300 
    301     /**
    302      * Determines whether the next submenu (of the given width) should display on the right or on
    303      * the left of the most recent menu.
    304      *
    305      * @param nextMenuWidth Width of the next submenu to display.
    306      * @return The position to display it.
    307      */
    308     @HorizPosition
    309     private int getNextMenuPosition(int nextMenuWidth) {
    310         ListView lastListView = mShowingMenus.get(mShowingMenus.size() - 1).getListView();
    311 
    312         final int[] screenLocation = new int[2];
    313         lastListView.getLocationOnScreen(screenLocation);
    314 
    315         final Rect displayFrame = new Rect();
    316         mShownAnchorView.getWindowVisibleDisplayFrame(displayFrame);
    317 
    318         if (mLastPosition == HORIZ_POSITION_RIGHT) {
    319             final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth;
    320             if (right > displayFrame.right) {
    321                 return HORIZ_POSITION_LEFT;
    322             }
    323             return HORIZ_POSITION_RIGHT;
    324         } else { // LEFT
    325             final int left = screenLocation[0] - nextMenuWidth;
    326             if (left < 0) {
    327                 return HORIZ_POSITION_RIGHT;
    328             }
    329             return HORIZ_POSITION_LEFT;
    330         }
    331     }
    332 
    333     @Override
    334     public void addMenu(MenuBuilder menu) {
    335         menu.addMenuPresenter(this, mContext);
    336 
    337         if (isShowing()) {
    338             showMenu(menu);
    339         } else {
    340             mPendingMenus.add(menu);
    341         }
    342     }
    343 
    344     /**
    345      * Prepares and shows the specified menu immediately.
    346      *
    347      * @param menu the menu to show
    348      */
    349     private void showMenu(@NonNull MenuBuilder menu) {
    350         final LayoutInflater inflater = LayoutInflater.from(mContext);
    351         final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly);
    352 
    353         // Apply "force show icon" setting. There are 3 cases:
    354         // (1) This is the top level menu and icon spacing is forced. Add spacing.
    355         // (2) This is a submenu. Add spacing if any of the visible menu items has an icon.
    356         // (3) This is the top level menu and icon spacing isn't forced. Do not add spacing.
    357         if (!isShowing() && mForceShowIcon) {
    358           // Case 1
    359           adapter.setForceShowIcon(true);
    360         } else if (isShowing()) {
    361           // Case 2
    362           adapter.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(menu));
    363         }
    364         // Case 3: Else, don't allow spacing for icons (default behavior; do nothing).
    365 
    366         final int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth);
    367         final MenuPopupWindow popupWindow = createPopupWindow();
    368         popupWindow.setAdapter(adapter);
    369         popupWindow.setContentWidth(menuWidth);
    370         popupWindow.setDropDownGravity(mDropDownGravity);
    371 
    372         final CascadingMenuInfo parentInfo;
    373         final View parentView;
    374         if (mShowingMenus.size() > 0) {
    375             parentInfo = mShowingMenus.get(mShowingMenus.size() - 1);
    376             parentView = findParentViewForSubmenu(parentInfo, menu);
    377         } else {
    378             parentInfo = null;
    379             parentView = null;
    380         }
    381 
    382         if (parentView != null) {
    383             // This menu is a cascading submenu anchored to a parent view.
    384             popupWindow.setAnchorView(parentView);
    385             popupWindow.setTouchModal(false);
    386             popupWindow.setEnterTransition(null);
    387 
    388             final @HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth);
    389             final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
    390             mLastPosition = nextMenuPosition;
    391 
    392             // Compute the horizontal offset to display the submenu to the right or to the left
    393             // of the parent item.
    394             // By now, mDropDownGravity is the resolved absolute gravity, so
    395             // this should work in both LTR and RTL.
    396             final int x;
    397             if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
    398                 if (showOnRight) {
    399                     x = menuWidth;
    400                 } else {
    401                     x = -parentView.getWidth();
    402                 }
    403             } else {
    404                 if (showOnRight) {
    405                     x = parentView.getWidth();
    406                 } else {
    407                     x = -menuWidth;
    408                 }
    409             }
    410             popupWindow.setHorizontalOffset(x);
    411 
    412             // Align with the top edge of the parent view (or the bottom edge when the submenu is
    413             // flipped vertically).
    414             popupWindow.setOverlapAnchor(true);
    415             popupWindow.setVerticalOffset(0);
    416         } else {
    417             if (mHasXOffset) {
    418                 popupWindow.setHorizontalOffset(mXOffset);
    419             }
    420             if (mHasYOffset) {
    421                 popupWindow.setVerticalOffset(mYOffset);
    422             }
    423             final Rect epicenterBounds = getEpicenterBounds();
    424             popupWindow.setEpicenterBounds(epicenterBounds);
    425         }
    426 
    427 
    428         final CascadingMenuInfo menuInfo = new CascadingMenuInfo(popupWindow, menu, mLastPosition);
    429         mShowingMenus.add(menuInfo);
    430 
    431         popupWindow.show();
    432 
    433         final ListView listView = popupWindow.getListView();
    434         listView.setOnKeyListener(this);
    435 
    436         // If this is the root menu, show the title if requested.
    437         if (parentInfo == null && mShowTitle && menu.getHeaderTitle() != null) {
    438             final FrameLayout titleItemView = (FrameLayout) inflater.inflate(
    439                     R.layout.popup_menu_header_item_layout, listView, false);
    440             final TextView titleView = (TextView) titleItemView.findViewById(R.id.title);
    441             titleItemView.setEnabled(false);
    442             titleView.setText(menu.getHeaderTitle());
    443             listView.addHeaderView(titleItemView, null, false);
    444 
    445             // Show again to update the title.
    446             popupWindow.show();
    447         }
    448     }
    449 
    450     /**
    451      * Returns the menu item within the specified parent menu that owns
    452      * specified submenu.
    453      *
    454      * @param parent the parent menu
    455      * @param submenu the submenu for which the index should be returned
    456      * @return the menu item that owns the submenu, or {@code null} if not
    457      *         present
    458      */
    459     private MenuItem findMenuItemForSubmenu(
    460             @NonNull MenuBuilder parent, @NonNull MenuBuilder submenu) {
    461         for (int i = 0, count = parent.size(); i < count; i++) {
    462             final MenuItem item = parent.getItem(i);
    463             if (item.hasSubMenu() && submenu == item.getSubMenu()) {
    464                 return item;
    465             }
    466         }
    467 
    468         return null;
    469     }
    470 
    471     /**
    472      * Attempts to find the view for the menu item that owns the specified
    473      * submenu.
    474      *
    475      * @param parentInfo info for the parent menu
    476      * @param submenu the submenu whose parent view should be obtained
    477      * @return the parent view, or {@code null} if one could not be found
    478      */
    479     @Nullable
    480     private View findParentViewForSubmenu(
    481             @NonNull CascadingMenuInfo parentInfo, @NonNull MenuBuilder submenu) {
    482         final MenuItem owner = findMenuItemForSubmenu(parentInfo.menu, submenu);
    483         if (owner == null) {
    484             // Couldn't find the submenu owner.
    485             return null;
    486         }
    487 
    488         // The adapter may be wrapped. Adjust the index if necessary.
    489         final int headersCount;
    490         final MenuAdapter menuAdapter;
    491         final ListView listView = parentInfo.getListView();
    492         final ListAdapter listAdapter = listView.getAdapter();
    493         if (listAdapter instanceof HeaderViewListAdapter) {
    494             final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) listAdapter;
    495             headersCount = headerAdapter.getHeadersCount();
    496             menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
    497         } else {
    498             headersCount = 0;
    499             menuAdapter = (MenuAdapter) listAdapter;
    500         }
    501 
    502         // Find the index within the menu adapter's data set of the menu item.
    503         int ownerPosition = AbsListView.INVALID_POSITION;
    504         for (int i = 0, count = menuAdapter.getCount(); i < count; i++) {
    505             if (owner == menuAdapter.getItem(i)) {
    506                 ownerPosition = i;
    507                 break;
    508             }
    509         }
    510         if (ownerPosition == AbsListView.INVALID_POSITION) {
    511             // Couldn't find the owner within the menu adapter.
    512             return null;
    513         }
    514 
    515         // Adjust the index for the adapter used to display views.
    516         ownerPosition += headersCount;
    517 
    518         // Adjust the index for the visible views.
    519         final int ownerViewPosition = ownerPosition - listView.getFirstVisiblePosition();
    520         if (ownerViewPosition < 0 || ownerViewPosition >= listView.getChildCount()) {
    521             // Not visible on screen.
    522             return null;
    523         }
    524 
    525         return listView.getChildAt(ownerViewPosition);
    526     }
    527 
    528     /**
    529      * @return {@code true} if the popup is currently showing, {@code false} otherwise.
    530      */
    531     @Override
    532     public boolean isShowing() {
    533         return mShowingMenus.size() > 0 && mShowingMenus.get(0).window.isShowing();
    534     }
    535 
    536     /**
    537      * Called when one or more of the popup windows was dismissed.
    538      */
    539     @Override
    540     public void onDismiss() {
    541         // The dismiss listener doesn't pass the calling window, so walk
    542         // through the stack to figure out which one was just dismissed.
    543         CascadingMenuInfo dismissedInfo = null;
    544         for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
    545             final CascadingMenuInfo info = mShowingMenus.get(i);
    546             if (!info.window.isShowing()) {
    547                 dismissedInfo = info;
    548                 break;
    549             }
    550         }
    551 
    552         // Close all menus starting from the dismissed menu, passing false
    553         // since we are manually closing only a subset of windows.
    554         if (dismissedInfo != null) {
    555             dismissedInfo.menu.close(false);
    556         }
    557     }
    558 
    559     @Override
    560     public void updateMenuView(boolean cleared) {
    561         for (CascadingMenuInfo info : mShowingMenus) {
    562             toMenuAdapter(info.getListView().getAdapter()).notifyDataSetChanged();
    563         }
    564     }
    565 
    566     @Override
    567     public void setCallback(Callback cb) {
    568         mPresenterCallback = cb;
    569     }
    570 
    571     @Override
    572     public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
    573         // Don't allow double-opening of the same submenu.
    574         for (CascadingMenuInfo info : mShowingMenus) {
    575             if (subMenu == info.menu) {
    576                 // Just re-focus that one.
    577                 info.getListView().requestFocus();
    578                 return true;
    579             }
    580         }
    581 
    582         if (subMenu.hasVisibleItems()) {
    583             addMenu(subMenu);
    584 
    585             if (mPresenterCallback != null) {
    586                 mPresenterCallback.onOpenSubMenu(subMenu);
    587             }
    588             return true;
    589         }
    590         return false;
    591     }
    592 
    593     /**
    594      * Finds the index of the specified menu within the list of added menus.
    595      *
    596      * @param menu the menu to find
    597      * @return the index of the menu, or {@code -1} if not present
    598      */
    599     private int findIndexOfAddedMenu(@NonNull MenuBuilder menu) {
    600         for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
    601             final CascadingMenuInfo info  = mShowingMenus.get(i);
    602             if (menu == info.menu) {
    603                 return i;
    604             }
    605         }
    606 
    607         return -1;
    608     }
    609 
    610     @Override
    611     public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
    612         final int menuIndex = findIndexOfAddedMenu(menu);
    613         if (menuIndex < 0) {
    614             return;
    615         }
    616 
    617         // Recursively close descendant menus.
    618         final int nextMenuIndex = menuIndex + 1;
    619         if (nextMenuIndex < mShowingMenus.size()) {
    620             final CascadingMenuInfo childInfo = mShowingMenus.get(nextMenuIndex);
    621             childInfo.menu.close(false /* closeAllMenus */);
    622         }
    623 
    624         // Close the target menu.
    625         final CascadingMenuInfo info = mShowingMenus.remove(menuIndex);
    626         info.menu.removeMenuPresenter(this);
    627         if (mShouldCloseImmediately) {
    628             // Disable all exit animations.
    629             info.window.setExitTransition(null);
    630             info.window.setAnimationStyle(0);
    631         }
    632         info.window.dismiss();
    633 
    634         final int count = mShowingMenus.size();
    635         if (count > 0) {
    636             mLastPosition = mShowingMenus.get(count - 1).position;
    637         } else {
    638             mLastPosition = getInitialMenuPosition();
    639         }
    640 
    641         if (count == 0) {
    642             // This was the last window. Clean up.
    643             dismiss();
    644 
    645             if (mPresenterCallback != null) {
    646                 mPresenterCallback.onCloseMenu(menu, true);
    647             }
    648 
    649             if (mTreeObserver != null) {
    650                 if (mTreeObserver.isAlive()) {
    651                     mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
    652                 }
    653                 mTreeObserver = null;
    654             }
    655             mShownAnchorView.removeOnAttachStateChangeListener(mAttachStateChangeListener);
    656 
    657             // If every [sub]menu was dismissed, that means the whole thing was
    658             // dismissed, so notify the owner.
    659             mOnDismissListener.onDismiss();
    660         } else if (allMenusAreClosing) {
    661             // Close all menus starting from the root. This will recursively
    662             // close any remaining menus, so we don't need to propagate the
    663             // "closeAllMenus" flag. The last window will clean up.
    664             final CascadingMenuInfo rootInfo = mShowingMenus.get(0);
    665             rootInfo.menu.close(false /* closeAllMenus */);
    666         }
    667     }
    668 
    669     @Override
    670     public boolean flagActionItems() {
    671         return false;
    672     }
    673 
    674     @Override
    675     public Parcelable onSaveInstanceState() {
    676         return null;
    677     }
    678 
    679     @Override
    680     public void onRestoreInstanceState(Parcelable state) {
    681     }
    682 
    683     @Override
    684     public void setGravity(int dropDownGravity) {
    685         if (mRawDropDownGravity != dropDownGravity) {
    686             mRawDropDownGravity = dropDownGravity;
    687             mDropDownGravity = Gravity.getAbsoluteGravity(
    688                     dropDownGravity, mAnchorView.getLayoutDirection());
    689         }
    690     }
    691 
    692     @Override
    693     public void setAnchorView(@NonNull View anchor) {
    694         if (mAnchorView != anchor) {
    695             mAnchorView = anchor;
    696 
    697             // Gravity resolution may have changed, update from raw gravity.
    698             mDropDownGravity = Gravity.getAbsoluteGravity(
    699                     mRawDropDownGravity, mAnchorView.getLayoutDirection());
    700         }
    701     }
    702 
    703     @Override
    704     public void setOnDismissListener(OnDismissListener listener) {
    705         mOnDismissListener = listener;
    706     }
    707 
    708     @Override
    709     public ListView getListView() {
    710         return mShowingMenus.isEmpty() ? null : mShowingMenus.get(mShowingMenus.size() - 1).getListView();
    711     }
    712 
    713     @Override
    714     public void setHorizontalOffset(int x) {
    715         mHasXOffset = true;
    716         mXOffset = x;
    717     }
    718 
    719     @Override
    720     public void setVerticalOffset(int y) {
    721         mHasYOffset = true;
    722         mYOffset = y;
    723     }
    724 
    725     @Override
    726     public void setShowTitle(boolean showTitle) {
    727         mShowTitle = showTitle;
    728     }
    729 
    730     private static class CascadingMenuInfo {
    731         public final MenuPopupWindow window;
    732         public final MenuBuilder menu;
    733         public final int position;
    734 
    735         public CascadingMenuInfo(@NonNull MenuPopupWindow window, @NonNull MenuBuilder menu,
    736                 int position) {
    737             this.window = window;
    738             this.menu = menu;
    739             this.position = position;
    740         }
    741 
    742         public ListView getListView() {
    743             return window.getListView();
    744         }
    745     }
    746 }
    747