Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / appmenu / AppMenu.java
1 // Copyright 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.appmenu;
6
7 import android.animation.Animator;
8 import android.animation.AnimatorSet;
9 import android.content.Context;
10 import android.content.res.Resources;
11 import android.graphics.Rect;
12 import android.view.KeyEvent;
13 import android.view.LayoutInflater;
14 import android.view.Menu;
15 import android.view.MenuItem;
16 import android.view.Surface;
17 import android.view.View;
18 import android.view.View.OnKeyListener;
19 import android.view.ViewGroup;
20 import android.widget.AdapterView;
21 import android.widget.AdapterView.OnItemClickListener;
22 import android.widget.ImageButton;
23 import android.widget.ListPopupWindow;
24 import android.widget.PopupWindow;
25 import android.widget.PopupWindow.OnDismissListener;
26
27 import org.chromium.base.SysUtils;
28 import org.chromium.chrome.R;
29
30 import java.util.ArrayList;
31 import java.util.List;
32
33 /**
34  * Shows a popup of menuitems anchored to a host view. When a item is selected we call
35  * Activity.onOptionsItemSelected with the appropriate MenuItem.
36  *   - Only visible MenuItems are shown.
37  *   - Disabled items are grayed out.
38  */
39 public class AppMenu implements OnItemClickListener, OnKeyListener {
40     /** Whether or not to show the software menu button in the menu. */
41     private static final boolean SHOW_SW_MENU_BUTTON = true;
42
43     private static final float LAST_ITEM_SHOW_FRACTION = 0.5f;
44
45     private final Menu mMenu;
46     private final int mItemRowHeight;
47     private final int mItemDividerHeight;
48     private final int mVerticalFadeDistance;
49     private final int mAdditionalVerticalOffset;
50     private ListPopupWindow mPopup;
51     private AppMenuAdapter mAdapter;
52     private AppMenuHandler mHandler;
53     private int mCurrentScreenRotation = -1;
54     private boolean mIsByHardwareButton;
55
56     /**
57      * Creates and sets up the App Menu.
58      * @param menu Original menu created by the framework.
59      * @param itemRowHeight Desired height for each app menu row.
60      * @param itemDividerHeight Desired height for the divider between app menu items.
61      * @param handler AppMenuHandler receives callbacks from AppMenu.
62      * @param res Resources object used to get dimensions and style attributes.
63      */
64     AppMenu(Menu menu, int itemRowHeight, int itemDividerHeight, AppMenuHandler handler,
65             Resources res) {
66         mMenu = menu;
67
68         mItemRowHeight = itemRowHeight;
69         assert mItemRowHeight > 0;
70
71         mHandler = handler;
72
73         mItemDividerHeight = itemDividerHeight;
74         assert mItemDividerHeight >= 0;
75
76         mAdditionalVerticalOffset =
77                 res.getDimensionPixelSize(R.dimen.menu_software_vertical_offset);
78         mVerticalFadeDistance = res.getDimensionPixelSize(R.dimen.menu_vertical_fade_distance);
79     }
80
81     /**
82      * Creates and shows the app menu anchored to the specified view.
83      *
84      * @param context             The context of the AppMenu (ensure the proper theme is set on
85      *                            this context).
86      * @param anchorView          The anchor {@link View} of the {@link ListPopupWindow}.
87      * @param isByHardwareButton  Whether or not hardware button triggered it. (oppose to software
88      *                            button)
89      * @param screenRotation      Current device screen rotation.
90      * @param visibleDisplayFrame The display area rect in which AppMenu is supposed to fit in.
91      * @param screenHeight        Current device screen height.
92      */
93     void show(Context context, View anchorView, boolean isByHardwareButton, int screenRotation,
94             Rect visibleDisplayFrame, int screenHeight) {
95         mPopup = new ListPopupWindow(context, null, android.R.attr.popupMenuStyle);
96         mPopup.setModal(true);
97         mPopup.setAnchorView(anchorView);
98         mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
99         mPopup.setOnDismissListener(new OnDismissListener() {
100             @Override
101             public void onDismiss() {
102                 if (mPopup.getAnchorView() instanceof ImageButton) {
103                     ((ImageButton) mPopup.getAnchorView()).setSelected(false);
104                 }
105                 mHandler.onMenuVisibilityChanged(false);
106             }
107         });
108
109         // Need to explicitly set the background here.  Relying on it being set in the style caused
110         // an incorrectly drawn background.
111         if (isByHardwareButton) {
112             mPopup.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.menu_bg));
113         } else {
114             mPopup.setBackgroundDrawable(
115                     context.getResources().getDrawable(R.drawable.edge_menu_bg));
116             mPopup.setAnimationStyle(R.style.OverflowMenuAnim);
117         }
118
119         // Turn off window animations for low end devices.
120         if (SysUtils.isLowEndDevice()) mPopup.setAnimationStyle(0);
121
122         Rect bgPadding = new Rect();
123         mPopup.getBackground().getPadding(bgPadding);
124
125         int popupWidth = context.getResources().getDimensionPixelSize(R.dimen.menu_width) +
126                 bgPadding.left + bgPadding.right;
127
128         mPopup.setWidth(popupWidth);
129
130         mCurrentScreenRotation = screenRotation;
131         mIsByHardwareButton = isByHardwareButton;
132
133         // Extract visible items from the Menu.
134         int numItems = mMenu.size();
135         List<MenuItem> menuItems = new ArrayList<MenuItem>();
136         for (int i = 0; i < numItems; ++i) {
137             MenuItem item = mMenu.getItem(i);
138             if (item.isVisible()) {
139                 menuItems.add(item);
140             }
141         }
142
143         boolean showMenuButton = !mIsByHardwareButton;
144         if (!SHOW_SW_MENU_BUTTON) showMenuButton = false;
145         // A List adapter for visible items in the Menu. The first row is added as a header to the
146         // list view.
147         mAdapter = new AppMenuAdapter(
148                 this, menuItems, LayoutInflater.from(context), showMenuButton);
149         mPopup.setAdapter(mAdapter);
150
151         setMenuHeight(menuItems.size(), visibleDisplayFrame, screenHeight);
152         setPopupOffset(mPopup, mCurrentScreenRotation, visibleDisplayFrame);
153         mPopup.setOnItemClickListener(this);
154         mPopup.show();
155         mPopup.getListView().setItemsCanFocus(true);
156         mPopup.getListView().setOnKeyListener(this);
157
158         mHandler.onMenuVisibilityChanged(true);
159
160         if (mVerticalFadeDistance > 0) {
161             mPopup.getListView().setVerticalFadingEdgeEnabled(true);
162             mPopup.getListView().setFadingEdgeLength(mVerticalFadeDistance);
163         }
164
165         // Don't animate the menu items for low end devices.
166         if (!SysUtils.isLowEndDevice()) {
167             mPopup.getListView().addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
168                 @Override
169                 public void onLayoutChange(View v, int left, int top, int right, int bottom,
170                         int oldLeft, int oldTop, int oldRight, int oldBottom) {
171                     mPopup.getListView().removeOnLayoutChangeListener(this);
172                     runMenuItemEnterAnimations();
173                 }
174             });
175         }
176     }
177
178     private void setPopupOffset(ListPopupWindow popup, int screenRotation, Rect appRect) {
179         Rect paddingRect = new Rect();
180         popup.getBackground().getPadding(paddingRect);
181         int[] anchorLocation = new int[2];
182         popup.getAnchorView().getLocationInWindow(anchorLocation);
183         int anchorHeight = popup.getAnchorView().getHeight();
184
185         // If we have a hardware menu button, locate the app menu closer to the estimated
186         // hardware menu button location.
187         if (mIsByHardwareButton) {
188             int horizontalOffset = -anchorLocation[0];
189             switch (screenRotation) {
190                 case Surface.ROTATION_0:
191                 case Surface.ROTATION_180:
192                     horizontalOffset += (appRect.width() - mPopup.getWidth()) / 2;
193                     break;
194                 case Surface.ROTATION_90:
195                     horizontalOffset += appRect.width() - mPopup.getWidth();
196                     break;
197                 case Surface.ROTATION_270:
198                     break;
199                 default:
200                     assert false;
201                     break;
202             }
203             popup.setHorizontalOffset(horizontalOffset);
204             // The menu is displayed above the anchored view, so shift the menu up by the top
205             // padding of the background.
206             popup.setVerticalOffset(-paddingRect.bottom);
207         } else {
208             // The menu is displayed over and below the anchored view, so shift the menu up by the
209             // height of the anchor view.
210             popup.setVerticalOffset(mAdditionalVerticalOffset - anchorHeight);
211         }
212     }
213
214     /**
215      * Handles clicks on the AppMenu popup.
216      * @param menuItem The menu item in the popup that was clicked.
217      */
218     void onItemClick(MenuItem menuItem) {
219         if (menuItem.isEnabled()) {
220             dismiss();
221             mHandler.onOptionsItemSelected(menuItem);
222         }
223     }
224
225     @Override
226     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
227         onItemClick(mAdapter.getItem(position));
228     }
229
230     @Override
231     public boolean onKey(View v, int keyCode, KeyEvent event) {
232         if (mPopup == null || mPopup.getListView() == null) return false;
233
234         if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
235             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
236                 event.startTracking();
237                 v.getKeyDispatcherState().startTracking(event, this);
238                 return true;
239             } else if (event.getAction() == KeyEvent.ACTION_UP) {
240                 v.getKeyDispatcherState().handleUpEvent(event);
241                 if (event.isTracking() && !event.isCanceled()) {
242                     dismiss();
243                     return true;
244                 }
245             }
246         }
247         return false;
248     }
249
250     /**
251      * Dismisses the app menu and cancels the drag-to-scroll if it is taking place.
252      */
253     void dismiss() {
254         mHandler.appMenuDismissed();
255         if (isShowing()) {
256             mPopup.dismiss();
257         }
258     }
259
260     /**
261      * @return Whether the app menu is currently showing.
262      */
263     boolean isShowing() {
264         if (mPopup == null) {
265             return false;
266         }
267         return mPopup.isShowing();
268     }
269
270     /**
271      * @return ListPopupWindow that displays all the menu options.
272      */
273     ListPopupWindow getPopup() {
274         return mPopup;
275     }
276
277     private void setMenuHeight(int numMenuItems, Rect appDimensions, int screenHeight) {
278         assert mPopup.getAnchorView() != null;
279         View anchorView = mPopup.getAnchorView();
280         int[] anchorViewLocation = new int[2];
281         anchorView.getLocationOnScreen(anchorViewLocation);
282         anchorViewLocation[1] -= appDimensions.top;
283         int anchorViewImpactHeight = mIsByHardwareButton ? anchorView.getHeight() : 0;
284
285         // Set appDimensions.height() for abnormal anchorViewLocation.
286         if (anchorViewLocation[1] > screenHeight) {
287             anchorViewLocation[1] = appDimensions.height();
288         }
289         int availableScreenSpace = Math.max(anchorViewLocation[1],
290                 appDimensions.height() - anchorViewLocation[1] - anchorViewImpactHeight);
291
292         Rect padding = new Rect();
293         mPopup.getBackground().getPadding(padding);
294         availableScreenSpace -= mIsByHardwareButton ? padding.top : padding.bottom;
295
296         int numCanFit = availableScreenSpace / (mItemRowHeight + mItemDividerHeight);
297
298         // Fade out the last item if we cannot fit all items.
299         if (numCanFit < numMenuItems) {
300             int spaceForFullItems = numCanFit * (mItemRowHeight + mItemDividerHeight);
301             int spaceForPartialItem = (int) (LAST_ITEM_SHOW_FRACTION * mItemRowHeight);
302             // Determine which item needs hiding.
303             if (spaceForFullItems + spaceForPartialItem < availableScreenSpace) {
304                 mPopup.setHeight(spaceForFullItems + spaceForPartialItem +
305                         padding.top + padding.bottom);
306             } else {
307                 mPopup.setHeight(spaceForFullItems - mItemRowHeight + spaceForPartialItem +
308                         padding.top + padding.bottom);
309             }
310         } else {
311             mPopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
312         }
313     }
314
315     private void runMenuItemEnterAnimations() {
316         AnimatorSet animation = new AnimatorSet();
317         AnimatorSet.Builder builder = null;
318
319         ViewGroup list = mPopup.getListView();
320         for (int i = 0; i < list.getChildCount(); i++) {
321             View view = list.getChildAt(i);
322             Object animatorObject = view.getTag(R.id.menu_item_enter_anim_id);
323             if (animatorObject != null) {
324                 if (builder == null) {
325                     builder = animation.play((Animator) animatorObject);
326                 } else {
327                     builder.with((Animator) animatorObject);
328                 }
329             }
330         }
331
332         animation.start();
333     }
334 }