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
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.appcompat.view.menu;
18bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
19ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
208e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas
21bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powellimport android.content.Context;
22d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banesimport android.graphics.Point;
236142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.graphics.Rect;
24d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banesimport android.os.Build;
253de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport android.view.Display;
263de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport android.view.Gravity;
273de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport android.view.View;
283de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport android.view.WindowManager;
293de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport android.widget.ListView;
303de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport android.widget.PopupWindow.OnDismissListener;
313de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikas
32ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.AttrRes;
33ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.NonNull;
34ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.Nullable;
35ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.RestrictTo;
36ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.StyleRes;
373de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.appcompat.R;
38ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.view.GravityCompat;
39ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.view.ViewCompat;
40bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
41bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell/**
42bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * Presents a menu as a small, simple popup anchored to another view.
43ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani *
44bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell * @hide
45bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell */
468e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas@RestrictTo(LIBRARY_GROUP)
476142a54baae3289f734947c6b5375b12eb0fb722Chris Banespublic class MenuPopupHelper implements MenuHelper {
486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private static final int TOUCH_EPICENTER_SIZE_DP = 48;
49bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
5049c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    private final Context mContext;
516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    // Immutable cached popup menu properties.
5349c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    private final MenuBuilder mMenu;
5449c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    private final boolean mOverflowOnly;
5549c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    private final int mPopupStyleAttr;
56a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette    private final int mPopupStyleRes;
5749c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    // Mutable cached popup menu properties.
59ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    private View mAnchorView;
606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private int mDropDownGravity = Gravity.START;
616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private boolean mForceShowIcon;
626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private MenuPresenter.Callback mPresenterCallback;
6349c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private MenuPopup mPopup;
656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private OnDismissListener mOnDismissListener;
6649c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        this(context, menu, null, false, R.attr.popupMenuStyle, 0);
69ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
70bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            @NonNull View anchorView) {
736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        this(context, menu, anchorView, false, R.attr.popupMenuStyle, 0);
74ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
75bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            @NonNull View anchorView,
786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            boolean overflowOnly, @AttrRes int popupStyleAttr) {
79a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette        this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
80a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette    }
81a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette
826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            @StyleRes int popupStyleRes) {
85ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        mContext = context;
86ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        mMenu = menu;
876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mAnchorView = anchorView;
88ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        mOverflowOnly = overflowOnly;
8949c78900da0d43140fb602431fb93212bd7f6c70Chris Banes        mPopupStyleAttr = popupStyleAttr;
90a9cf27da7f78d81db5ad482003a10d1a6562107cAlan Viverette        mPopupStyleRes = popupStyleRes;
916142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
92bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setOnDismissListener(@Nullable OnDismissListener listener) {
946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mOnDismissListener = listener;
95ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
96bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
976142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * Sets the view to which the popup window is anchored.
996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * <p>
1006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * Changes take effect on the next call to show().
1016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      *
1026142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * @param anchor the view to which the popup window should be anchored
1036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      */
1046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setAnchorView(@NonNull View anchor) {
105ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        mAnchorView = anchor;
106ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
107bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
1086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1096142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Sets whether the popup menu's adapter is forced to show icons in the
1106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * menu item views.
1116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
1126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Changes take effect on the next call to show().
1136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *
1146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @param forceShowIcon {@code true} to force icons to be shown, or
1156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *                  {@code false} for icons to be optionally shown
1166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
1176142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setForceShowIcon(boolean forceShowIcon) {
1186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mForceShowIcon = forceShowIcon;
119d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        if (mPopup != null) {
120d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes            mPopup.setForceShowIcon(forceShowIcon);
121d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        }
122bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
123ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
1246142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1256142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * Sets the alignment of the popup window relative to the anchor view.
1266142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * <p>
1276142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * Changes take effect on the next call to show().
1286142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      *
1296142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      * @param gravity alignment of the popup relative to the anchor
1306142a54baae3289f734947c6b5375b12eb0fb722Chris Banes      */
13149c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    public void setGravity(int gravity) {
13249c78900da0d43140fb602431fb93212bd7f6c70Chris Banes        mDropDownGravity = gravity;
13349c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    }
13449c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
1356142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @return alignment of the popup relative to the anchor
1376142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
13844918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    public int getGravity() {
13944918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes        return mDropDownGravity;
14044918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes    }
14144918a92e1d66a01a03063e2c5e68b2570f64b03Chris Banes
142ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    public void show() {
143ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        if (!tryShow()) {
144ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
145ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
146bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
147bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
1486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void show(int x, int y) {
1496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (!tryShow(x, y)) {
1506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
1516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
1536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    @NonNull
1556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopup getPopup() {
1566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mPopup == null) {
1576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mPopup = createPopup();
1586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
15949c78900da0d43140fb602431fb93212bd7f6c70Chris Banes        return mPopup;
16049c78900da0d43140fb602431fb93212bd7f6c70Chris Banes    }
16149c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
1626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Attempts to show the popup anchored to the view specified by {@link #setAnchorView(View)}.
1646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *
1656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @return {@code true} if the popup was shown or was already showing prior to calling this
1666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *         method, {@code false} otherwise
1676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
168ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    public boolean tryShow() {
1696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (isShowing()) {
1706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return true;
171ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
172bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
1736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mAnchorView == null) {
1746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return false;
17549c78900da0d43140fb602431fb93212bd7f6c70Chris Banes        }
17649c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
1776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        showPopup(0, 0, false, false);
178ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        return true;
179bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
180ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
1816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Shows the popup menu and makes a best-effort to anchor it to the
1836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * specified (x,y) coordinate relative to the anchor view.
1846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
1856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Additionally, the popup's transition epicenter (see
1866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * {@link android.widget.PopupWindow#setEpicenterBounds(Rect)} will be
1876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * centered on the specified coordinate, rather than using the bounds of
1886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * the anchor view.
1896142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
1906142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
1916142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * display the popup with its top-left corner at (x,y) relative to the
1926142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
1936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * popup's top-right corner will be at (x,y).
1946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
1956142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * If the popup cannot be displayed fully on-screen, this method will
1966142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * attempt to scroll the anchor view's ancestors and/or offset the popup
1976142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * such that it may be displayed fully on-screen.
1986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *
1996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @param x x coordinate relative to the anchor view
2006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @param y y coordinate relative to the anchor view
2016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @return {@code true} if the popup was shown or was already showing prior
2026142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *         to calling this method, {@code false} otherwise
2036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
2046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public boolean tryShow(int x, int y) {
205ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        if (isShowing()) {
2066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return true;
207ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
208ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
2096142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mAnchorView == null) {
2106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return false;
211ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
212ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
2136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        showPopup(x, y, true, true);
2146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        return true;
215ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
216bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2176142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
2186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Creates the popup and assigns cached properties.
2196142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     *
2206142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @return an initialized popup
2216142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
2226142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    @NonNull
2236142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private MenuPopup createPopup() {
224d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        final WindowManager windowManager = (WindowManager) mContext.getSystemService(
225d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes                Context.WINDOW_SERVICE);
226d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        final Display display = windowManager.getDefaultDisplay();
227d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        final Point displaySize = new Point();
228d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes
229d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        if (Build.VERSION.SDK_INT >= 17) {
230d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes            display.getRealSize(displaySize);
231d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        } else {
232722327e27579c196b92883c07f4b47e9efada8adKirill Grouchnikov            display.getSize(displaySize);
233d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        }
234d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes
235d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        final int smallestWidth = Math.min(displaySize.x, displaySize.y);
236d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        final int minSmallestWidthCascading = mContext.getResources().getDimensionPixelSize(
237d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes                R.dimen.abc_cascading_menus_min_smallest_width);
238d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes        final boolean enableCascadingSubmenus = smallestWidth >= minSmallestWidthCascading;
2396142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2406142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        final MenuPopup popup;
2416142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (enableCascadingSubmenus) {
2426142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
2436142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    mPopupStyleRes, mOverflowOnly);
2446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        } else {
2456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
2466142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    mPopupStyleRes, mOverflowOnly);
247ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
248ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
2496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        // Assign immutable properties.
2506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.addMenu(mMenu);
2516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setOnDismissListener(mInternalOnDismissListener);
25249c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
2536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        // Assign mutable properties. These may be reassigned later.
2546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setAnchorView(mAnchorView);
2556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setCallback(mPresenterCallback);
2566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setForceShowIcon(mForceShowIcon);
2576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setGravity(mDropDownGravity);
25849c78900da0d43140fb602431fb93212bd7f6c70Chris Banes
2596142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        return popup;
260ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
261bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private void showPopup(int xOffset, int yOffset, boolean useOffsets, boolean showTitle) {
2636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        final MenuPopup popup = getPopup();
2646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.setShowTitle(showTitle);
2656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (useOffsets) {
2676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // If the resolved drop-down gravity is RIGHT, the popup's right
2686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // edge will be aligned with the anchor view. Adjust by the anchor
2696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // width such that the top-right corner is at the X offset.
2706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            final int hgrav = GravityCompat.getAbsoluteGravity(mDropDownGravity,
2716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    ViewCompat.getLayoutDirection(mAnchorView)) & Gravity.HORIZONTAL_GRAVITY_MASK;
2726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            if (hgrav == Gravity.RIGHT) {
273bda13fd1495c9162efbde73d812c5a31f811673aVladislav Kaznacheev                xOffset -= mAnchorView.getWidth();
274ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani            }
275bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup.setHorizontalOffset(xOffset);
2776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup.setVerticalOffset(yOffset);
2786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // Set the transition epicenter to be roughly finger (or mouse
2806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // cursor) sized and centered around the offset position. This
2816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // will give the appearance that the window is emerging from
2826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // the touch point.
2836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            final float density = mContext.getResources().getDisplayMetrics().density;
2846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            final int halfSize = (int) (TOUCH_EPICENTER_SIZE_DP * density / 2);
2856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            final Rect epicenter = new Rect(xOffset - halfSize, yOffset - halfSize,
2866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    xOffset + halfSize, yOffset + halfSize);
2876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            popup.setEpicenterBounds(epicenter);
28820ac724a3a836bfd362c911f7dc55a61c02b4d44Trevor Johns        }
289bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2906142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        popup.show();
291bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
292bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
2936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
2946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Dismisses the popup, if showing.
2956142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
296ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    @Override
2976142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void dismiss() {
2986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (isShowing()) {
2996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mPopup.dismiss();
300ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
301bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
302bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
3036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
3046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Called after the popup has been dismissed.
3056142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <p>
3066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * <strong>Note:</strong> Subclasses should call the super implementation
3076142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * last to ensure that any necessary tear down has occurred before the
3086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * listener specified by {@link #setOnDismissListener(OnDismissListener)}
3096142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * is called.
3106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
3116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    protected void onDismiss() {
3126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mPopup = null;
313ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
3146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mOnDismissListener != null) {
3156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mOnDismissListener.onDismiss();
316bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell        }
317bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell    }
318bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell
3196142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public boolean isShowing() {
3206142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        return mPopup != null && mPopup.isShowing();
321ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    }
322ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
323ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani    @Override
3246142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setPresenterCallback(@Nullable MenuPresenter.Callback cb) {
3256142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mPresenterCallback = cb;
3266142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mPopup != null) {
3276142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mPopup.setCallback(cb);
328ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
3296142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
330ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani
3316142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
3326142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Listener used to proxy dismiss callbacks to the helper's owner.
3336142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
3346142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
335ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        @Override
3366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        public void onDismiss() {
3376142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            MenuPopupHelper.this.onDismiss();
338ced50ab7536cd3d3573f03310fc899f10c414d37Anirudh Dewani        }
3396142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    };
340ef148f4e0f0c3e9b947f9c3b76ae85bf9777d891Vladislav Kaznacheev
341ef148f4e0f0c3e9b947f9c3b76ae85bf9777d891Vladislav Kaznacheev    /**
342ef148f4e0f0c3e9b947f9c3b76ae85bf9777d891Vladislav Kaznacheev     * API to get the underlying ListView of the Popup
343ef148f4e0f0c3e9b947f9c3b76ae85bf9777d891Vladislav Kaznacheev     */
344ef148f4e0f0c3e9b947f9c3b76ae85bf9777d891Vladislav Kaznacheev    public ListView getListView() {
345ef148f4e0f0c3e9b947f9c3b76ae85bf9777d891Vladislav Kaznacheev        return getPopup().getListView();
346ef148f4e0f0c3e9b947f9c3b76ae85bf9777d891Vladislav Kaznacheev    }
347bbbb8f39d1b1d1b317c5f9237f20fe6b1d9eb17fAdam Powell}
348