1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package android.widget;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.content.res.Resources;
23 import android.transition.Transition;
24 import android.util.AttributeSet;
25 import android.view.KeyEvent;
26 import android.view.MenuItem;
27 import android.view.MotionEvent;
28 import android.view.View;
29 
30 import com.android.internal.view.menu.ListMenuItemView;
31 import com.android.internal.view.menu.MenuAdapter;
32 import com.android.internal.view.menu.MenuBuilder;
33 
34 /**
35  * A MenuPopupWindow represents the popup window for menu.
36  *
37  * MenuPopupWindow is mostly same as ListPopupWindow, but it has customized
38  * behaviors specific to menus,
39  *
40  * @hide
41  */
42 public class MenuPopupWindow extends ListPopupWindow implements MenuItemHoverListener {
43     private MenuItemHoverListener mHoverListener;
44 
MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)45     public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
46         super(context, attrs, defStyleAttr, defStyleRes);
47     }
48 
49     @Override
createDropDownListView(Context context, boolean hijackFocus)50     DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
51         MenuDropDownListView view = new MenuDropDownListView(context, hijackFocus);
52         view.setHoverListener(this);
53         return view;
54     }
55 
setEnterTransition(Transition enterTransition)56     public void setEnterTransition(Transition enterTransition) {
57         mPopup.setEnterTransition(enterTransition);
58     }
59 
setExitTransition(Transition exitTransition)60     public void setExitTransition(Transition exitTransition) {
61         mPopup.setExitTransition(exitTransition);
62     }
63 
setHoverListener(MenuItemHoverListener hoverListener)64     public void setHoverListener(MenuItemHoverListener hoverListener) {
65         mHoverListener = hoverListener;
66     }
67 
68     /**
69      * Set whether this window is touch modal or if outside touches will be sent to
70      * other windows behind it.
71      */
setTouchModal(boolean touchModal)72     public void setTouchModal(boolean touchModal) {
73         mPopup.setTouchModal(touchModal);
74     }
75 
76     @Override
onItemHoverEnter(@onNull MenuBuilder menu, @NonNull MenuItem item)77     public void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
78         // Forward up the chain
79         if (mHoverListener != null) {
80             mHoverListener.onItemHoverEnter(menu, item);
81         }
82     }
83 
84     @Override
onItemHoverExit(@onNull MenuBuilder menu, @NonNull MenuItem item)85     public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
86         // Forward up the chain
87         if (mHoverListener != null) {
88             mHoverListener.onItemHoverExit(menu, item);
89         }
90     }
91 
92     /**
93      * @hide
94      */
95     public static class MenuDropDownListView extends DropDownListView {
96         final int mAdvanceKey;
97         final int mRetreatKey;
98 
99         private MenuItemHoverListener mHoverListener;
100         private MenuItem mHoveredMenuItem;
101 
MenuDropDownListView(Context context, boolean hijackFocus)102         public MenuDropDownListView(Context context, boolean hijackFocus) {
103             super(context, hijackFocus);
104 
105             final Resources res = context.getResources();
106             final Configuration config = res.getConfiguration();
107             if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
108                 mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT;
109                 mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT;
110             } else {
111                 mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT;
112                 mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT;
113             }
114         }
115 
setHoverListener(MenuItemHoverListener hoverListener)116         public void setHoverListener(MenuItemHoverListener hoverListener) {
117             mHoverListener = hoverListener;
118         }
119 
clearSelection()120         public void clearSelection() {
121             setSelectedPositionInt(INVALID_POSITION);
122             setNextSelectedPositionInt(INVALID_POSITION);
123         }
124 
125         @Override
onKeyDown(int keyCode, KeyEvent event)126         public boolean onKeyDown(int keyCode, KeyEvent event) {
127             ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView();
128             if (selectedItem != null && keyCode == mAdvanceKey) {
129                 if (selectedItem.isEnabled() && selectedItem.getItemData().hasSubMenu()) {
130                     performItemClick(
131                             selectedItem,
132                             getSelectedItemPosition(),
133                             getSelectedItemId());
134                 }
135                 return true;
136             } else if (selectedItem != null && keyCode == mRetreatKey) {
137                 setSelectedPositionInt(INVALID_POSITION);
138                 setNextSelectedPositionInt(INVALID_POSITION);
139 
140                 // Close only the top-level menu.
141                 ((MenuAdapter) getAdapter()).getAdapterMenu().close(false /* closeAllMenus */);
142                 return true;
143             }
144             return super.onKeyDown(keyCode, event);
145         }
146 
147         @Override
onHoverEvent(MotionEvent ev)148         public boolean onHoverEvent(MotionEvent ev) {
149             // Dispatch any changes in hovered item index to the listener.
150             if (mHoverListener != null) {
151                 // The adapter may be wrapped. Adjust the index if necessary.
152                 final int headersCount;
153                 final MenuAdapter menuAdapter;
154                 final ListAdapter adapter = getAdapter();
155                 if (adapter instanceof HeaderViewListAdapter) {
156                     final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) adapter;
157                     headersCount = headerAdapter.getHeadersCount();
158                     menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
159                 } else {
160                     headersCount = 0;
161                     menuAdapter = (MenuAdapter) adapter;
162                 }
163 
164                 // Find the menu item for the view at the event coordinates.
165                 MenuItem menuItem = null;
166                 if (ev.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
167                     final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
168                     if (position != INVALID_POSITION) {
169                         final int itemPosition = position - headersCount;
170                         if (itemPosition >= 0 && itemPosition < menuAdapter.getCount()) {
171                             menuItem = menuAdapter.getItem(itemPosition);
172                         }
173                     }
174                 }
175 
176                 final MenuItem oldMenuItem = mHoveredMenuItem;
177                 if (oldMenuItem != menuItem) {
178                     final MenuBuilder menu = menuAdapter.getAdapterMenu();
179                     if (oldMenuItem != null) {
180                         mHoverListener.onItemHoverExit(menu, oldMenuItem);
181                     }
182 
183                     mHoveredMenuItem = menuItem;
184 
185                     if (menuItem != null) {
186                         mHoverListener.onItemHoverEnter(menu, menuItem);
187                     }
188                 }
189             }
190 
191             return super.onHoverEvent(ev);
192         }
193     }
194 }