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}