14267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell/*
24267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * Copyright (C) 2010 The Android Open Source Project
34267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell *
44267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * Licensed under the Apache License, Version 2.0 (the "License");
54267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * you may not use this file except in compliance with the License.
64267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * You may obtain a copy of the License at
74267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell *
84267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell *      http://www.apache.org/licenses/LICENSE-2.0
94267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell *
104267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * Unless required by applicable law or agreed to in writing, software
114267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * distributed under the License is distributed on an "AS IS" BASIS,
124267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * See the License for the specific language governing permissions and
144267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell * limitations under the License.
154267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell */
164267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
174267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellpackage com.android.internal.view.menu;
184267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
199916282bac8ac8fab7ce5b649c049842acffa29bOren Blasbergimport com.android.internal.view.menu.MenuPresenter.Callback;
209916282bac8ac8fab7ce5b649c049842acffa29bOren Blasberg
21708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viveretteimport android.annotation.AttrRes;
22708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viveretteimport android.annotation.NonNull;
23708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viveretteimport android.annotation.Nullable;
24708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viveretteimport android.annotation.StyleRes;
254267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport android.content.Context;
2693c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasbergimport android.graphics.Point;
2791098574f90277128415e9593cce1e495cc51465Alan Viveretteimport android.graphics.Rect;
2891098574f90277128415e9593cce1e495cc51465Alan Viveretteimport android.util.DisplayMetrics;
2993c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasbergimport android.view.Display;
3054c94dea8a26e66fa59a31fd9170ca221052d3aaAdam Powellimport android.view.Gravity;
314267534d1c42af847ed0cefd1c88c99f66b36571Adam Powellimport android.view.View;
3293c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasbergimport android.view.WindowManager;
33708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viveretteimport android.widget.PopupWindow.OnDismissListener;
344267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
354267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell/**
36696cba573e651b0e4f18a4718627c8ccecb3bda0Adam Powell * Presents a menu as a small, simple popup anchored to another view.
374267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell */
38021627eb5875372dea57ba91fa782fffbfbbc559Alan Viverettepublic class MenuPopupHelper implements MenuHelper {
3991098574f90277128415e9593cce1e495cc51465Alan Viverette    private static final int TOUCH_EPICENTER_SIZE_DP = 48;
4091098574f90277128415e9593cce1e495cc51465Alan Viverette
410bce6ab8cd8aadc9784dda2f3f4cc9dd91b796c7Alan Viverette    private final Context mContext;
42708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette
43708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    // Immutable cached popup menu properties.
440bce6ab8cd8aadc9784dda2f3f4cc9dd91b796c7Alan Viverette    private final MenuBuilder mMenu;
450bce6ab8cd8aadc9784dda2f3f4cc9dd91b796c7Alan Viverette    private final boolean mOverflowOnly;
46560f17098f90b15c8894cce127f2fed85f7aeb6bAlan Viverette    private final int mPopupStyleAttr;
4729632521c36548d71b610fc4de864281bab4dfb4Alan Viverette    private final int mPopupStyleRes;
480bce6ab8cd8aadc9784dda2f3f4cc9dd91b796c7Alan Viverette
49708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    // Mutable cached popup menu properties.
504afd62b18c52a55371ab923d54f93615ad68fd7aAdam Powell    private View mAnchorView;
51d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette    private int mDropDownGravity = Gravity.START;
52ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg    private boolean mForceShowIcon;
539916282bac8ac8fab7ce5b649c049842acffa29bOren Blasberg    private Callback mPresenterCallback;
54d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette
55708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    private MenuPopup mPopup;
56708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    private OnDismissListener mOnDismissListener;
57d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette
58708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
5929632521c36548d71b610fc4de864281bab4dfb4Alan Viverette        this(context, menu, null, false, com.android.internal.R.attr.popupMenuStyle, 0);
608028dd32a4a04154050220dd0693583d5b750330Adam Powell    }
614267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
62708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
63708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            @NonNull View anchorView) {
6429632521c36548d71b610fc4de864281bab4dfb4Alan Viverette        this(context, menu, anchorView, false, com.android.internal.R.attr.popupMenuStyle, 0);
658028dd32a4a04154050220dd0693583d5b750330Adam Powell    }
668028dd32a4a04154050220dd0693583d5b750330Adam Powell
67708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
68708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            @NonNull View anchorView,
69708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            boolean overflowOnly, @AttrRes int popupStyleAttr) {
7029632521c36548d71b610fc4de864281bab4dfb4Alan Viverette        this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
7129632521c36548d71b610fc4de864281bab4dfb4Alan Viverette    }
7229632521c36548d71b610fc4de864281bab4dfb4Alan Viverette
73708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
74708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
75708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            @StyleRes int popupStyleRes) {
764267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell        mContext = context;
778028dd32a4a04154050220dd0693583d5b750330Adam Powell        mMenu = menu;
78708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        mAnchorView = anchorView;
798028dd32a4a04154050220dd0693583d5b750330Adam Powell        mOverflowOnly = overflowOnly;
80560f17098f90b15c8894cce127f2fed85f7aeb6bAlan Viverette        mPopupStyleAttr = popupStyleAttr;
8129632521c36548d71b610fc4de864281bab4dfb4Alan Viverette        mPopupStyleRes = popupStyleRes;
82b23976efdd6ffe42cb3b8fe6650fc77bd9a161e8Oren Blasberg    }
83696cba573e651b0e4f18a4718627c8ccecb3bda0Adam Powell
84708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    public void setOnDismissListener(@Nullable OnDismissListener listener) {
85708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        mOnDismissListener = listener;
864267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    }
874267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
88708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    /**
89708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      * Sets the view to which the popup window is anchored.
90708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      * <p>
91708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      * Changes take effect on the next call to show().
92708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      *
93708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      * @param anchor the view to which the popup window should be anchored
94708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      */
95708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    public void setAnchorView(@NonNull View anchor) {
964afd62b18c52a55371ab923d54f93615ad68fd7aAdam Powell        mAnchorView = anchor;
97f0ad6e6eaf48ac8f4007232ad0a8511a7b5cfc0eAdam Powell    }
98f0ad6e6eaf48ac8f4007232ad0a8511a7b5cfc0eAdam Powell
99708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    /**
100708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * Sets whether the popup menu's adapter is forced to show icons in the
101708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * menu item views.
102708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * <p>
103708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * Changes take effect on the next call to show().
104708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     *
105708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * @param forceShowIcon {@code true} to force icons to be shown, or
106708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     *                  {@code false} for icons to be optionally shown
107708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     */
108708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    public void setForceShowIcon(boolean forceShowIcon) {
109708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        mForceShowIcon = forceShowIcon;
1107b457455c40a6f159d9d82b2fe2e03bc26e9c55eOren Blasberg        if (mPopup != null) {
1117b457455c40a6f159d9d82b2fe2e03bc26e9c55eOren Blasberg            mPopup.setForceShowIcon(forceShowIcon);
1127b457455c40a6f159d9d82b2fe2e03bc26e9c55eOren Blasberg        }
1139151103ff20d28e8db2a2cc0386d57b8dad4b5d5Adam Powell    }
1149151103ff20d28e8db2a2cc0386d57b8dad4b5d5Adam Powell
115708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    /**
116708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      * Sets the alignment of the popup window relative to the anchor view.
117708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      * <p>
118708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      * Changes take effect on the next call to show().
119708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      *
120708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      * @param gravity alignment of the popup relative to the anchor
121708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette      */
12254c94dea8a26e66fa59a31fd9170ca221052d3aaAdam Powell    public void setGravity(int gravity) {
12354c94dea8a26e66fa59a31fd9170ca221052d3aaAdam Powell        mDropDownGravity = gravity;
12454c94dea8a26e66fa59a31fd9170ca221052d3aaAdam Powell    }
12554c94dea8a26e66fa59a31fd9170ca221052d3aaAdam Powell
126708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    /**
127708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * @return alignment of the popup relative to the anchor
128708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     */
12975d837954c3673647e3a899f03cd56c0892066e0Alan Viverette    public int getGravity() {
13075d837954c3673647e3a899f03cd56c0892066e0Alan Viverette        return mDropDownGravity;
13175d837954c3673647e3a899f03cd56c0892066e0Alan Viverette    }
13275d837954c3673647e3a899f03cd56c0892066e0Alan Viverette
1334267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    public void show() {
1345e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell        if (!tryShow()) {
1355e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
1365e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell        }
1375e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell    }
1385e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell
139ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg    public void show(int x, int y) {
140ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg        if (!tryShow(x, y)) {
141ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg            throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
142ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg        }
143ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg    }
144ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg
145708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    @NonNull
146708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    public MenuPopup getPopup() {
147708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        if (mPopup == null) {
148708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            mPopup = createPopup();
149708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        }
150ca6a3611cdb28a514834adba35fcce2da6f2e7c2Alan Viverette        return mPopup;
151ca6a3611cdb28a514834adba35fcce2da6f2e7c2Alan Viverette    }
152ca6a3611cdb28a514834adba35fcce2da6f2e7c2Alan Viverette
1538fd949e680c15d397084430d4907c16cedfacddaAlan Viverette    /**
154b23976efdd6ffe42cb3b8fe6650fc77bd9a161e8Oren Blasberg     * Attempts to show the popup anchored to the view specified by {@link #setAnchorView(View)}.
1558fd949e680c15d397084430d4907c16cedfacddaAlan Viverette     *
156b23976efdd6ffe42cb3b8fe6650fc77bd9a161e8Oren Blasberg     * @return {@code true} if the popup was shown or was already showing prior to calling this
157b23976efdd6ffe42cb3b8fe6650fc77bd9a161e8Oren Blasberg     *         method, {@code false} otherwise
1588fd949e680c15d397084430d4907c16cedfacddaAlan Viverette     */
1595e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell    public boolean tryShow() {
1608fd949e680c15d397084430d4907c16cedfacddaAlan Viverette        if (isShowing()) {
1618fd949e680c15d397084430d4907c16cedfacddaAlan Viverette            return true;
1628fd949e680c15d397084430d4907c16cedfacddaAlan Viverette        }
1638fd949e680c15d397084430d4907c16cedfacddaAlan Viverette
1649916282bac8ac8fab7ce5b649c049842acffa29bOren Blasberg        if (mAnchorView == null) {
1655e3f284baa271cb0fbf90e504d19fdd2e385382eAdam Powell            return false;
1668028dd32a4a04154050220dd0693583d5b750330Adam Powell        }
1674267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
168708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        showPopup(0, 0, false, false);
169ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg        return true;
170ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg    }
171ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg
172d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette    /**
173d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * Shows the popup menu and makes a best-effort to anchor it to the
174d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * specified (x,y) coordinate relative to the anchor view.
175d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * <p>
17691098574f90277128415e9593cce1e495cc51465Alan Viverette     * Additionally, the popup's transition epicenter (see
17791098574f90277128415e9593cce1e495cc51465Alan Viverette     * {@link android.widget.PopupWindow#setEpicenterBounds(Rect)} will be
17891098574f90277128415e9593cce1e495cc51465Alan Viverette     * centered on the specified coordinate, rather than using the bounds of
17991098574f90277128415e9593cce1e495cc51465Alan Viverette     * the anchor view.
18091098574f90277128415e9593cce1e495cc51465Alan Viverette     * <p>
181d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
182d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * display the popup with its top-left corner at (x,y) relative to the
183d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
184d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * popup's top-right corner will be at (x,y).
185d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * <p>
186d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * If the popup cannot be displayed fully on-screen, this method will
187d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * attempt to scroll the anchor view's ancestors and/or offset the popup
188d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * such that it may be displayed fully on-screen.
189d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     *
190d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * @param x x coordinate relative to the anchor view
191d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * @param y y coordinate relative to the anchor view
192d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     * @return {@code true} if the popup was shown or was already showing prior
193d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     *         to calling this method, {@code false} otherwise
194d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette     */
195ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg    public boolean tryShow(int x, int y) {
196ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg        if (isShowing()) {
197ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg            return true;
198ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg        }
199ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg
200ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg        if (mAnchorView == null) {
201ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg            return false;
202ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg        }
203ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg
204708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        showPopup(x, y, true, true);
205ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg        return true;
206ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg    }
207ed3912692f0ba8a647d795462e20fcdb67adbacbOren Blasberg
208708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    /**
209708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * Creates the popup and assigns cached properties.
210708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     *
211708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * @return an initialized popup
212708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     */
213708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    @NonNull
214708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    private MenuPopup createPopup() {
21593c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg        final WindowManager windowManager = (WindowManager) mContext.getSystemService(
21693c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg            Context.WINDOW_SERVICE);
21793c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg        final Display display = windowManager.getDefaultDisplay();
21893c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg        final Point displaySize = new Point();
21993c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg        display.getRealSize(displaySize);
22093c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg
22193c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg        final int smallestWidth = Math.min(displaySize.x, displaySize.y);
22293c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg        final int minSmallestWidthCascading = mContext.getResources().getDimensionPixelSize(
22393c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg            com.android.internal.R.dimen.cascading_menus_min_smallest_width);
22493c195894de323b039ff62eb5c62bf9aeed2abf0Oren Blasberg        final boolean enableCascadingSubmenus = smallestWidth >= minSmallestWidthCascading;
225708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette
226708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        final MenuPopup popup;
227708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        if (enableCascadingSubmenus) {
228708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
229708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette                    mPopupStyleRes, mOverflowOnly);
230708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        } else {
231708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
232708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette                    mPopupStyleRes, mOverflowOnly);
233708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        }
234708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette
235708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        // Assign immutable properties.
236708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        popup.addMenu(mMenu);
237708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        popup.setOnDismissListener(mInternalOnDismissListener);
238708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette
239708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        // Assign mutable properties. These may be reassigned later.
240708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        popup.setAnchorView(mAnchorView);
241708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        popup.setCallback(mPresenterCallback);
242708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        popup.setForceShowIcon(mForceShowIcon);
243708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        popup.setGravity(mDropDownGravity);
244d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette
245708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        return popup;
246708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    }
247708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette
24891098574f90277128415e9593cce1e495cc51465Alan Viverette    private void showPopup(int xOffset, int yOffset, boolean useOffsets, boolean showTitle) {
24991098574f90277128415e9593cce1e495cc51465Alan Viverette        final MenuPopup popup = getPopup();
25091098574f90277128415e9593cce1e495cc51465Alan Viverette        popup.setShowTitle(showTitle);
25191098574f90277128415e9593cce1e495cc51465Alan Viverette
25291098574f90277128415e9593cce1e495cc51465Alan Viverette        if (useOffsets) {
253d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette            // If the resolved drop-down gravity is RIGHT, the popup's right
254d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette            // edge will be aligned with the anchor view. Adjust by the anchor
255d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette            // width such that the top-right corner is at the X offset.
256d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette            final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
257d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette                    mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
258d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette            if (hgrav == Gravity.RIGHT) {
259d959c9d203ea41fede8f6b0a54ebb6ef2790e615Vladislav Kaznacheev                xOffset -= mAnchorView.getWidth();
260d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette            }
26191098574f90277128415e9593cce1e495cc51465Alan Viverette
26291098574f90277128415e9593cce1e495cc51465Alan Viverette            popup.setHorizontalOffset(xOffset);
26391098574f90277128415e9593cce1e495cc51465Alan Viverette            popup.setVerticalOffset(yOffset);
26491098574f90277128415e9593cce1e495cc51465Alan Viverette
26591098574f90277128415e9593cce1e495cc51465Alan Viverette            // Set the transition epicenter to be roughly finger (or mouse
26691098574f90277128415e9593cce1e495cc51465Alan Viverette            // cursor) sized and centered around the offset position. This
26791098574f90277128415e9593cce1e495cc51465Alan Viverette            // will give the appearance that the window is emerging from
26891098574f90277128415e9593cce1e495cc51465Alan Viverette            // the touch point.
26991098574f90277128415e9593cce1e495cc51465Alan Viverette            final float density = mContext.getResources().getDisplayMetrics().density;
27091098574f90277128415e9593cce1e495cc51465Alan Viverette            final int halfSize = (int) (TOUCH_EPICENTER_SIZE_DP * density / 2);
27191098574f90277128415e9593cce1e495cc51465Alan Viverette            final Rect epicenter = new Rect(xOffset - halfSize, yOffset - halfSize,
27291098574f90277128415e9593cce1e495cc51465Alan Viverette                    xOffset + halfSize, yOffset + halfSize);
27391098574f90277128415e9593cce1e495cc51465Alan Viverette            popup.setEpicenterBounds(epicenter);
274d6443f60e1d556a772a8b33ec172d2bae44de63aAlan Viverette        }
2759916282bac8ac8fab7ce5b649c049842acffa29bOren Blasberg
276708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        popup.show();
2774267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    }
2784267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
279708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    /**
280708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * Dismisses the popup, if showing.
281708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     */
282021627eb5875372dea57ba91fa782fffbfbbc559Alan Viverette    @Override
2834267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    public void dismiss() {
2843d3da27ab394108fd51771616cca3279baae99d1Adam Powell        if (isShowing()) {
2853d3da27ab394108fd51771616cca3279baae99d1Adam Powell            mPopup.dismiss();
2863d3da27ab394108fd51771616cca3279baae99d1Adam Powell        }
2878515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell    }
2888515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell
289708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    /**
290708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * Called after the popup has been dismissed.
291708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * <p>
292708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * <strong>Note:</strong> Subclasses should call the super implementation
293708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * last to ensure that any necessary tear down has occurred before the
294708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * listener specified by {@link #setOnDismissListener(OnDismissListener)}
295708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * is called.
296708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     */
297708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    protected void onDismiss() {
2988515ee846bd76aee86ec5ddfcc4dd1e626dd999cAdam Powell        mPopup = null;
299708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette
300708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        if (mOnDismissListener != null) {
301708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            mOnDismissListener.onDismiss();
302708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        }
3034267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell    }
3044267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell
3058028dd32a4a04154050220dd0693583d5b750330Adam Powell    public boolean isShowing() {
3068028dd32a4a04154050220dd0693583d5b750330Adam Powell        return mPopup != null && mPopup.isShowing();
3078028dd32a4a04154050220dd0693583d5b750330Adam Powell    }
3088028dd32a4a04154050220dd0693583d5b750330Adam Powell
309021627eb5875372dea57ba91fa782fffbfbbc559Alan Viverette    @Override
310021627eb5875372dea57ba91fa782fffbfbbc559Alan Viverette    public void setPresenterCallback(@Nullable MenuPresenter.Callback cb) {
3119916282bac8ac8fab7ce5b649c049842acffa29bOren Blasberg        mPresenterCallback = cb;
312708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        if (mPopup != null) {
313708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            mPopup.setCallback(cb);
314708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        }
315696cba573e651b0e4f18a4718627c8ccecb3bda0Adam Powell    }
316708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette
317708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    /**
318708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     * Listener used to proxy dismiss callbacks to the helper's owner.
319708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette     */
320708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
321708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        @Override
322708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        public void onDismiss() {
323708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette            MenuPopupHelper.this.onDismiss();
324708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette        }
325708aa9d96441fe359a4c2a2c89e2ab709b367f8aAlan Viverette    };
3264267534d1c42af847ed0cefd1c88c99f66b36571Adam Powell}
327