MenuPopupHelper.java revision 6142a54baae3289f734947c6b5375b12eb0fb722
1bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell/*
2bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * Copyright (C) 2010 The Android Open Source Project
3bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell *
4bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * Licensed under the Apache License, Version 2.0 (the "License");
5bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * you may not use this file except in compliance with the License.
6bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * You may obtain a copy of the License at
7bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell *
8bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell *      http://www.apache.org/licenses/LICENSE-2.0
9bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell *
10bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * Unless required by applicable law or agreed to in writing, software
11bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * distributed under the License is distributed on an "AS IS" BASIS,
12bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * See the License for the specific language governing permissions and
14bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * limitations under the License.
15bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell */
16bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
1766698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banespackage android.support.v7.view.menu;
18bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
19bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powellimport android.content.Context;
206142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.graphics.Rect;
216142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.support.annotation.AttrRes;
226142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.support.annotation.NonNull;
236142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.support.annotation.Nullable;
246142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.support.annotation.StyleRes;
256142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.support.v4.view.GravityCompat;
266142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.support.v4.view.ViewCompat;
27da10fdd1400ecfd8d7f2e55651dd528d0614dfc5Jeff Brownimport android.support.v7.appcompat.R;
2849c78900da0d43140fb602431fb93212bd7f6c70Chris Banesimport android.view.Gravity;
29bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powellimport android.view.View;
306142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.PopupWindow.OnDismissListener;
31bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
32bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell/**
33bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * Presents a menu as a small, simple popup anchored to another view.
34ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani *
35bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * @hide
36bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell */
376142a54baae3289f734947c6b5375b12eb0fb722Chris Banespublic class MenuPopupHelper implements MenuHelper {
386142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private static final int TOUCH_EPICENTER_SIZE_DP = 48;
39bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
4049c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    private final Context mContext;
416142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
426142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    // Immutable cached popup menu properties.
4349c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    private final MenuBuilder mMenu;
4449c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    private final boolean mOverflowOnly;
4549c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    private final int mPopupStyleAttr;
46a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette    private final int mPopupStyleRes;
4749c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    // Mutable cached popup menu properties.
49ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    private View mAnchorView;
506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private int mDropDownGravity = Gravity.START;
516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private boolean mForceShowIcon;
526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private MenuPresenter.Callback mPresenterCallback;
5349c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private MenuPopup mPopup;
556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private OnDismissListener mOnDismissListener;
5649c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        this(context, menu, null, false, R.attr.popupMenuStyle, 0);
59ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
60bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            @NonNull View anchorView) {
636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        this(context, menu, anchorView, false, R.attr.popupMenuStyle, 0);
64ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
65bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            @NonNull View anchorView,
686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            boolean overflowOnly, @AttrRes int popupStyleAttr) {
69a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette        this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
70a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette    }
71a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette
726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            @StyleRes int popupStyleRes) {
75ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        mContext = context;
76ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        mMenu = menu;
776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mAnchorView = anchorView;
78ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        mOverflowOnly = overflowOnly;
7949c78900da0d43140fb602431fb93212bd7f6c70Chris Banes        mPopupStyleAttr = popupStyleAttr;
80a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette        mPopupStyleRes = popupStyleRes;
816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
82bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setOnDismissListener(@Nullable OnDismissListener listener) {
846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mOnDismissListener = listener;
85ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
86bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * Sets the view to which the popup window is anchored.
896142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * <p>
906142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * Changes take effect on the next call to show().
916142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      *
926142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * @param anchor the view to which the popup window should be anchored
936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      */
946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setAnchorView(@NonNull View anchor) {
95ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        mAnchorView = anchor;
96ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
97bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Sets whether the popup menu's adapter is forced to show icons in the
1006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * menu item views.
1016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
1026142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Changes take effect on the next call to show().
1036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *
1046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @param forceShowIcon {@code true} to force icons to be shown, or
1056142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *                  {@code false} for icons to be optionally shown
1066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
1076142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setForceShowIcon(boolean forceShowIcon) {
1086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mForceShowIcon = forceShowIcon;
109bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
110ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
1116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * Sets the alignment of the popup window relative to the anchor view.
1136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * <p>
1146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * Changes take effect on the next call to show().
1156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      *
1166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * @param gravity alignment of the popup relative to the anchor
1176142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      */
11849c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    public void setGravity(int gravity) {
11949c78900da0d43140fb602431fb93212bd7f6c70Chris Banes        mDropDownGravity = gravity;
12049c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    }
12149c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
1226142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1236142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @return alignment of the popup relative to the anchor
1246142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
12544918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    public int getGravity() {
12644918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        return mDropDownGravity;
12744918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    }
12844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes
129ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    public void show() {
130ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        if (!tryShow()) {
131ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
132ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
133bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
134bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
1356142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void show(int x, int y) {
1366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (!tryShow(x, y)) {
1376142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
1386142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1396142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
1406142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1416142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    @NonNull
1426142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopup getPopup() {
1436142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mPopup == null) {
1446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mPopup = createPopup();
1456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
14649c78900da0d43140fb602431fb93212bd7f6c70Chris Banes        return mPopup;
14749c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    }
14849c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
1496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Attempts to show the popup anchored to the view specified by {@link #setAnchorView(View)}.
1516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *
1526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @return {@code true} if the popup was shown or was already showing prior to calling this
1536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *         method, {@code false} otherwise
1546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
155ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    public boolean tryShow() {
1566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (isShowing()) {
1576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return true;
158ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
159bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
1606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mAnchorView == null) {
1616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return false;
16249c78900da0d43140fb602431fb93212bd7f6c70Chris Banes        }
16349c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
1646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        showPopup(0, 0, false, false);
165ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        return true;
166bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
167ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
1686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Shows the popup menu and makes a best-effort to anchor it to the
1706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * specified (x,y) coordinate relative to the anchor view.
1716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
1726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Additionally, the popup's transition epicenter (see
1736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * {@link android.widget.PopupWindow#setEpicenterBounds(Rect)} will be
1746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * centered on the specified coordinate, rather than using the bounds of
1756142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * the anchor view.
1766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
1776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
1786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * display the popup with its top-left corner at (x,y) relative to the
1796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
1806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * popup's top-right corner will be at (x,y).
1816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
1826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * If the popup cannot be displayed fully on-screen, this method will
1836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * attempt to scroll the anchor view's ancestors and/or offset the popup
1846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * such that it may be displayed fully on-screen.
1856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *
1866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @param x x coordinate relative to the anchor view
1876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @param y y coordinate relative to the anchor view
1886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @return {@code true} if the popup was shown or was already showing prior
1896142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *         to calling this method, {@code false} otherwise
1906142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
1916142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public boolean tryShow(int x, int y) {
192ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        if (isShowing()) {
1936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return true;
194ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
195ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
1966142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mAnchorView == null) {
1976142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return false;
198ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
199ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
2006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        showPopup(x, y, true, true);
2016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        return true;
202ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
203bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
2056142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Creates the popup and assigns cached properties.
2066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *
2076142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @return an initialized popup
2086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
2096142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    @NonNull
2106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private MenuPopup createPopup() {
2116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        final boolean enableCascadingSubmenus = mContext.getResources().getBoolean(
2126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                R.bool.abc_config_enableCascadingSubmenus);
2136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        final MenuPopup popup;
2156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (enableCascadingSubmenus) {
2166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
2176142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    mPopupStyleRes, mOverflowOnly);
2186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        } else {
2196142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
2206142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    mPopupStyleRes, mOverflowOnly);
221ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
222ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
2236142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        // Assign immutable properties.
2246142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.addMenu(mMenu);
2256142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setOnDismissListener(mInternalOnDismissListener);
22649c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
2276142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        // Assign mutable properties. These may be reassigned later.
2286142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setAnchorView(mAnchorView);
2296142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setCallback(mPresenterCallback);
2306142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setForceShowIcon(mForceShowIcon);
2316142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setGravity(mDropDownGravity);
23249c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
2336142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        return popup;
234ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
235bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private void showPopup(int xOffset, int yOffset, boolean useOffsets, boolean showTitle) {
2376142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        final MenuPopup popup = getPopup();
2386142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setShowTitle(showTitle);
2396142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2406142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (useOffsets) {
2416142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // If the resolved drop-down gravity is RIGHT, the popup's right
2426142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // edge will be aligned with the anchor view. Adjust by the anchor
2436142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // width such that the top-right corner is at the X offset.
2446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            final int hgrav = GravityCompat.getAbsoluteGravity(mDropDownGravity,
2456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    ViewCompat.getLayoutDirection(mAnchorView)) & Gravity.HORIZONTAL_GRAVITY_MASK;
2466142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            if (hgrav == Gravity.RIGHT) {
2476142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                xOffset -= mAnchorView.getWidth();
248ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani            }
249bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup.setHorizontalOffset(xOffset);
2516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup.setVerticalOffset(yOffset);
2526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // Set the transition epicenter to be roughly finger (or mouse
2546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // cursor) sized and centered around the offset position. This
2556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // will give the appearance that the window is emerging from
2566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // the touch point.
2576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            final float density = mContext.getResources().getDisplayMetrics().density;
2586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            final int halfSize = (int) (TOUCH_EPICENTER_SIZE_DP * density / 2);
2596142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            final Rect epicenter = new Rect(xOffset - halfSize, yOffset - halfSize,
2606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    xOffset + halfSize, yOffset + halfSize);
2616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup.setEpicenterBounds(epicenter);
26220ac724a3a836bfd362c911f7dc55a61c02b4d44Trevor Johns        }
263bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.show();
265bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
266bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
2686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Dismisses the popup, if showing.
2696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
270ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    @Override
2716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void dismiss() {
2726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (isShowing()) {
2736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mPopup.dismiss();
274ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
275bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
276bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
2786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Called after the popup has been dismissed.
2796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
2806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <strong>Note:</strong> Subclasses should call the super implementation
2816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * last to ensure that any necessary tear down has occurred before the
2826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * listener specified by {@link #setOnDismissListener(OnDismissListener)}
2836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * is called.
2846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
2856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    protected void onDismiss() {
2866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mPopup = null;
287ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
2886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mOnDismissListener != null) {
2896142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mOnDismissListener.onDismiss();
290bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell        }
291bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
292bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public boolean isShowing() {
2946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        return mPopup != null && mPopup.isShowing();
295ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
296ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
297ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    @Override
2986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setPresenterCallback(@Nullable MenuPresenter.Callback cb) {
2996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mPresenterCallback = cb;
3006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mPopup != null) {
3016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mPopup.setCallback(cb);
302ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
3036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
304ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
3056142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
3066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Listener used to proxy dismiss callbacks to the helper's owner.
3076142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
3086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
309ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        @Override
3106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        public void onDismiss() {
3116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            MenuPopupHelper.this.onDismiss();
312ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
3136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    };
314bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell}
315