1// Copyright 2013 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
5package org.chromium.chrome.browser.appmenu;
6
7import android.app.Activity;
8import android.content.res.TypedArray;
9import android.graphics.Point;
10import android.graphics.Rect;
11import android.graphics.drawable.Drawable;
12import android.view.ContextThemeWrapper;
13import android.view.Menu;
14import android.view.MenuItem;
15import android.view.View;
16import android.widget.PopupMenu;
17
18import org.chromium.base.VisibleForTesting;
19import org.chromium.chrome.browser.UmaBridge;
20
21import java.util.ArrayList;
22
23/**
24 * Object responsible for handling the creation, showing, hiding of the AppMenu and notifying the
25 * AppMenuObservers about these actions.
26 */
27public class AppMenuHandler {
28    private AppMenu mAppMenu;
29    private AppMenuDragHelper mAppMenuDragHelper;
30    private Menu mMenu;
31    private final ArrayList<AppMenuObserver> mObservers;
32    private final int mMenuResourceId;
33
34    private final AppMenuPropertiesDelegate mDelegate;
35    private final Activity mActivity;
36
37    /**
38     * Constructs an AppMenuHandler object.
39     * @param activity Activity that is using the AppMenu.
40     * @param delegate Delegate used to check the desired AppMenu properties on show.
41     * @param menuResourceId Resource Id that should be used as the source for the menu items.
42     *            It is assumed to have back_menu_id, forward_menu_id, bookmark_this_page_id.
43     */
44    public AppMenuHandler(Activity activity, AppMenuPropertiesDelegate delegate,
45            int menuResourceId) {
46        mActivity = activity;
47        mDelegate = delegate;
48        mObservers = new ArrayList<AppMenuObserver>();
49        mMenuResourceId = menuResourceId;
50    }
51
52    /**
53     * Show the app menu.
54     * @param anchorView         Anchor view (usually a menu button) to be used for the popup.
55     * @param isByHardwareButton True if hardware button triggered it. (oppose to software
56     *                           button)
57     * @param startDragging      Whether dragging is started. For example, if the app menu is
58     *                           showed by tapping on a button, this should be false. If it is
59     *                           showed by start dragging down on the menu button, this should
60     *                           be true. Note that if isByHardwareButton is true, this must
61     *                           be false since we no longer support hardware menu button
62     *                           dragging.
63     * @return True, if the menu is shown, false, if menu is not shown, example reasons:
64     *         the menu is not yet available to be shown, or the menu is already showing.
65     */
66    public boolean showAppMenu(View anchorView, boolean isByHardwareButton, boolean startDragging) {
67        assert !(isByHardwareButton && startDragging);
68        if (!mDelegate.shouldShowAppMenu() || isAppMenuShowing()) return false;
69
70        if (mMenu == null) {
71            // Use a PopupMenu to create the Menu object. Note this is not the same as the
72            // AppMenu (mAppMenu) created below.
73            PopupMenu tempMenu = new PopupMenu(mActivity, anchorView);
74            tempMenu.inflate(mMenuResourceId);
75            mMenu = tempMenu.getMenu();
76        }
77        mDelegate.prepareMenu(mMenu);
78
79        ContextThemeWrapper wrapper = new ContextThemeWrapper(mActivity,
80                mDelegate.getMenuThemeResourceId());
81
82        if (mAppMenu == null) {
83            TypedArray a = wrapper.obtainStyledAttributes(new int[]
84                    {android.R.attr.listPreferredItemHeightSmall, android.R.attr.listDivider});
85            int itemRowHeight = a.getDimensionPixelSize(0, 0);
86            Drawable itemDivider = a.getDrawable(1);
87            int itemDividerHeight = itemDivider != null ? itemDivider.getIntrinsicHeight() : 0;
88            a.recycle();
89            mAppMenu = new AppMenu(mMenu, itemRowHeight, itemDividerHeight, this,
90                    mActivity.getResources());
91            mAppMenuDragHelper = new AppMenuDragHelper(mActivity, mAppMenu, itemRowHeight);
92        }
93
94        // Get the height and width of the display.
95        Rect appRect = new Rect();
96        mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(appRect);
97
98        // Use full size of window for abnormal appRect.
99        if (appRect.left < 0 && appRect.top < 0) {
100            appRect.left = 0;
101            appRect.top = 0;
102            appRect.right = mActivity.getWindow().getDecorView().getWidth();
103            appRect.bottom = mActivity.getWindow().getDecorView().getHeight();
104        }
105        int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
106        Point pt = new Point();
107        mActivity.getWindowManager().getDefaultDisplay().getSize(pt);
108        mAppMenu.show(wrapper, anchorView, isByHardwareButton, rotation, appRect, pt.y);
109        mAppMenuDragHelper.onShow(startDragging);
110        UmaBridge.menuShow();
111        return true;
112    }
113
114    void appMenuDismissed() {
115        mAppMenuDragHelper.finishDragging();
116    }
117
118    /**
119     * @return Whether the App Menu is currently showing.
120     */
121    public boolean isAppMenuShowing() {
122        return mAppMenu != null && mAppMenu.isShowing();
123    }
124
125    /**
126     * @return The App Menu that the menu handler is interacting with.
127     */
128    @VisibleForTesting
129    AppMenu getAppMenu() {
130        return mAppMenu;
131    }
132
133    AppMenuDragHelper getAppMenuDragHelper() {
134        return mAppMenuDragHelper;
135    }
136
137    /**
138     * Requests to hide the App Menu.
139     */
140    public void hideAppMenu() {
141        if (mAppMenu != null && mAppMenu.isShowing()) mAppMenu.dismiss();
142    }
143
144    /**
145     * Adds the observer to App Menu.
146     * @param observer Observer that should be notified about App Menu changes.
147     */
148    public void addObserver(AppMenuObserver observer) {
149        mObservers.add(observer);
150    }
151
152    /**
153     * Removes the observer from the App Menu.
154     * @param observer Observer that should no longer be notified about App Menu changes.
155     */
156    public void removeObserver(AppMenuObserver observer) {
157        mObservers.remove(observer);
158    }
159
160    void onOptionsItemSelected(MenuItem item) {
161        mActivity.onOptionsItemSelected(item);
162    }
163
164    /**
165     * Called by AppMenu to report that the App Menu visibility has changed.
166     * @param isVisible Whether the App Menu is showing.
167     */
168    void onMenuVisibilityChanged(boolean isVisible) {
169        for (int i = 0; i < mObservers.size(); ++i) {
170            mObservers.get(i).onMenuVisibilityChanged(isVisible);
171        }
172    }
173}
174