16142a54baae3289f734947c6b5375b12eb0fb722Chris Banes/*
26142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Copyright (C) 2016 The Android Open Source Project
36142a54baae3289f734947c6b5375b12eb0fb722Chris Banes *
46142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Licensed under the Apache License, Version 2.0 (the "License");
56142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * you may not use this file except in compliance with the License.
66142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * You may obtain a copy of the License at
76142a54baae3289f734947c6b5375b12eb0fb722Chris Banes *
86142a54baae3289f734947c6b5375b12eb0fb722Chris Banes *      http://www.apache.org/licenses/LICENSE-2.0
96142a54baae3289f734947c6b5375b12eb0fb722Chris Banes *
106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * Unless required by applicable law or agreed to in writing, software
116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * distributed under the License is distributed on an "AS IS" BASIS,
126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * See the License for the specific language governing permissions and
146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * limitations under the License
156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */
166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.appcompat.widget;
186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
19ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
208e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas
216142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.content.Context;
226142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.content.res.Configuration;
236142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.content.res.Resources;
246142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.os.Build;
256142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.transition.Transition;
266142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.util.AttributeSet;
276142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.util.Log;
286142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.KeyEvent;
296142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.MenuItem;
306142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.view.MotionEvent;
316142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.HeaderViewListAdapter;
326142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.ListAdapter;
336142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport android.widget.PopupWindow;
346142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
353de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.annotation.NonNull;
363de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.annotation.RestrictTo;
373de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.appcompat.view.menu.ListMenuItemView;
383de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.appcompat.view.menu.MenuAdapter;
393de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.appcompat.view.menu.MenuBuilder;
403de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikasimport androidx.core.view.ViewCompat;
413de8a4e8305507475d7890205184946a25cf45e7Aurimas Liutikas
426142a54baae3289f734947c6b5375b12eb0fb722Chris Banesimport java.lang.reflect.Method;
436142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes/**
456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * A MenuPopupWindow represents the popup window for menu.
466142a54baae3289f734947c6b5375b12eb0fb722Chris Banes *
476142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * MenuPopupWindow is mostly same as ListPopupWindow, but it has customized
486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * behaviors specific to menus,
496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes *
506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes * @hide
516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes */
528e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas@RestrictTo(LIBRARY_GROUP)
536142a54baae3289f734947c6b5375b12eb0fb722Chris Banespublic class MenuPopupWindow extends ListPopupWindow implements MenuItemHoverListener {
546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private static final String TAG = "MenuPopupWindow";
556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private static Method sSetTouchModalMethod;
576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    static {
596142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        try {
606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            sSetTouchModalMethod = PopupWindow.class.getDeclaredMethod(
616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    "setTouchModal", boolean.class);
626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        } catch (NoSuchMethodException e) {
636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            Log.i(TAG, "Could not find method setTouchModal() on PopupWindow. Oh well.");
646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    private MenuItemHoverListener mHoverListener;
686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        super(context, attrs, defStyleAttr, defStyleRes);
716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    @Override
746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
756142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        MenuDropDownListView view = new MenuDropDownListView(context, hijackFocus);
766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        view.setHoverListener(this);
776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        return view;
786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setEnterTransition(Object enterTransition) {
816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (Build.VERSION.SDK_INT >= 23) {
826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mPopup.setEnterTransition((Transition) enterTransition);
836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setExitTransition(Object exitTransition) {
876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (Build.VERSION.SDK_INT >= 23) {
886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mPopup.setExitTransition((Transition) exitTransition);
896142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
906142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
916142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
926142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setHoverListener(MenuItemHoverListener hoverListener) {
936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        mHoverListener = hoverListener;
946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
956142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
966142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
976142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * Set whether this window is touch modal or if outside touches will be sent to
986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * other windows behind it.
996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
1006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void setTouchModal(final boolean touchModal) {
1016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (sSetTouchModalMethod != null) {
1026142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            try {
1036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                sSetTouchModalMethod.invoke(mPopup, touchModal);
1046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            } catch (Exception e) {
1056142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                Log.i(TAG, "Could not invoke setTouchModal() on PopupWindow. Oh well.");
1066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            }
1076142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
1096142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    @Override
1116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
1126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        // Forward up the chain
1136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mHoverListener != null) {
1146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mHoverListener.onItemHoverEnter(menu, item);
1156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
1176142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    @Override
1196142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
1206142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        // Forward up the chain
1216142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        if (mHoverListener != null) {
1226142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mHoverListener.onItemHoverExit(menu, item);
1236142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1246142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
1256142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1266142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    /**
1276142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     * @hide
1286142a54baae3289f734947c6b5375b12eb0fb722Chris Banes     */
1298e10080c914d1ad0784394fa3026b85535535847Aurimas Liutikas    @RestrictTo(LIBRARY_GROUP)
1306142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    public static class MenuDropDownListView extends DropDownListView {
1316142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        final int mAdvanceKey;
1326142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        final int mRetreatKey;
1336142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1346142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        private MenuItemHoverListener mHoverListener;
1356142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        private MenuItem mHoveredMenuItem;
1366142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1376142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        public MenuDropDownListView(Context context, boolean hijackFocus) {
1386142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            super(context, hijackFocus);
1396142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
140d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes            final Resources res = context.getResources();
141d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes            final Configuration config = res.getConfiguration();
142d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes            if (Build.VERSION.SDK_INT >= 17
143d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes                    && ViewCompat.LAYOUT_DIRECTION_RTL == config.getLayoutDirection()) {
1446142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT;
1456142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT;
1466142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            } else {
1476142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT;
1486142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT;
1496142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            }
1506142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1516142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1526142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        public void setHoverListener(MenuItemHoverListener hoverListener) {
1536142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            mHoverListener = hoverListener;
1546142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1556142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1566142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        public void clearSelection() {
1576142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            setSelection(INVALID_POSITION);
1586142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1596142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1606142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        @Override
1616142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        public boolean onKeyDown(int keyCode, KeyEvent event) {
1626142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView();
1636142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            if (selectedItem != null && keyCode == mAdvanceKey) {
1646142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                if (selectedItem.isEnabled() && selectedItem.getItemData().hasSubMenu()) {
1656142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    performItemClick(
1666142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                            selectedItem,
1676142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                            getSelectedItemPosition(),
1686142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                            getSelectedItemId());
1696142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                }
1706142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                return true;
1716142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            } else if (selectedItem != null && keyCode == mRetreatKey) {
1726142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                setSelection(INVALID_POSITION);
1736142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1746142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                // Close only the top-level menu.
1756142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                ((MenuAdapter) getAdapter()).getAdapterMenu().close(false /* closeAllMenus */);
1766142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                return true;
1776142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            }
1786142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return super.onKeyDown(keyCode, event);
1796142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
1806142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1816142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        @Override
1826142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        public boolean onHoverEvent(MotionEvent ev) {
1836142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            // Dispatch any changes in hovered item index to the listener.
1846142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            if (mHoverListener != null) {
1856142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                // The adapter may be wrapped. Adjust the index if necessary.
1866142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                final int headersCount;
1876142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                final MenuAdapter menuAdapter;
1886142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                final ListAdapter adapter = getAdapter();
1896142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                if (adapter instanceof HeaderViewListAdapter) {
1906142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) adapter;
1916142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    headersCount = headerAdapter.getHeadersCount();
1926142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
1936142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                } else {
1946142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    headersCount = 0;
1956142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    menuAdapter = (MenuAdapter) adapter;
1966142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                }
1976142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
1986142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                // Find the menu item for the view at the event coordinates.
1996142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                MenuItem menuItem = null;
2006142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                if (ev.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
2016142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
2026142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    if (position != INVALID_POSITION) {
2036142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                        final int itemPosition = position - headersCount;
2046142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                        if (itemPosition >= 0 && itemPosition < menuAdapter.getCount()) {
2056142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                            menuItem = menuAdapter.getItem(itemPosition);
2066142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                        }
2076142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    }
2086142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                }
2096142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2106142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                final MenuItem oldMenuItem = mHoveredMenuItem;
2116142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                if (oldMenuItem != menuItem) {
2126142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    final MenuBuilder menu = menuAdapter.getAdapterMenu();
2136142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    if (oldMenuItem != null) {
2146142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                        mHoverListener.onItemHoverExit(menu, oldMenuItem);
2156142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    }
2166142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2176142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    mHoveredMenuItem = menuItem;
2186142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2196142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    if (menuItem != null) {
2206142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                        mHoverListener.onItemHoverEnter(menu, menuItem);
2216142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                    }
2226142a54baae3289f734947c6b5375b12eb0fb722Chris Banes                }
2236142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            }
2246142a54baae3289f734947c6b5375b12eb0fb722Chris Banes
2256142a54baae3289f734947c6b5375b12eb0fb722Chris Banes            return super.onHoverEvent(ev);
2266142a54baae3289f734947c6b5375b12eb0fb722Chris Banes        }
2276142a54baae3289f734947c6b5375b12eb0fb722Chris Banes    }
2286142a54baae3289f734947c6b5375b12eb0fb722Chris Banes}