// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.chrome.browser.appmenu; import android.annotation.SuppressLint; import android.app.Activity; import android.content.res.TypedArray; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.view.ContextThemeWrapper; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.PopupMenu; import org.chromium.base.metrics.RecordUserAction; import org.chromium.chrome.R; import java.util.ArrayList; /** * Object responsible for handling the creation, showing, hiding of the AppMenu and notifying the * AppMenuObservers about these actions. */ public class AppMenuHandler { private AppMenu mAppMenu; private AppMenuDragHelper mAppMenuDragHelper; private Menu mMenu; private final ArrayList<AppMenuObserver> mObservers; private final int mMenuResourceId; private final View mHardwareButtonMenuAnchor; private final AppMenuPropertiesDelegate mDelegate; private final Activity mActivity; /** * Constructs an AppMenuHandler object. * @param activity Activity that is using the AppMenu. * @param delegate Delegate used to check the desired AppMenu properties on show. * @param menuResourceId Resource Id that should be used as the source for the menu items. * It is assumed to have back_menu_id, forward_menu_id, bookmark_this_page_id. */ public AppMenuHandler(Activity activity, AppMenuPropertiesDelegate delegate, int menuResourceId) { mActivity = activity; mDelegate = delegate; mObservers = new ArrayList<AppMenuObserver>(); mMenuResourceId = menuResourceId; mHardwareButtonMenuAnchor = activity.findViewById(R.id.menu_anchor_stub); assert mHardwareButtonMenuAnchor != null : "Using AppMenu requires to have menu_anchor_stub view"; } /** * Notifies the menu that the contents of the menu item specified by {@code menuRowId} have * changed. This should be called if icons, titles, etc. are changing for a particular menu * item while the menu is open. * @param menuRowId The id of the menu item to change. This must be a row id and not a child * id. */ public void menuItemContentChanged(int menuRowId) { if (mAppMenu != null) mAppMenu.menuItemContentChanged(menuRowId); } /** * Show the app menu. * @param anchorView Anchor view (usually a menu button) to be used for the popup, if * null is passed then hardware menu button anchor will be used. * @param startDragging Whether dragging is started. For example, if the app menu is * showed by tapping on a button, this should be false. If it is * showed by start dragging down on the menu button, this should * be true. Note that if anchorView is null, this must * be false since we no longer support hardware menu button * dragging. * @return True, if the menu is shown, false, if menu is not shown, example reasons: * the menu is not yet available to be shown, or the menu is already showing. */ // TODO(crbug.com/635567): Fix this properly. @SuppressLint("ResourceType") public boolean showAppMenu(View anchorView, boolean startDragging) { if (!mDelegate.shouldShowAppMenu() || isAppMenuShowing()) return false; boolean isByPermanentButton = false; int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation(); if (anchorView == null) { // This fixes the bug where the bottom of the menu starts at the top of // the keyboard, instead of overlapping the keyboard as it should. int displayHeight = mActivity.getResources().getDisplayMetrics().heightPixels; Rect rect = new Rect(); mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); int statusBarHeight = rect.top; mHardwareButtonMenuAnchor.setY((displayHeight - statusBarHeight)); anchorView = mHardwareButtonMenuAnchor; isByPermanentButton = true; } assert !(isByPermanentButton && startDragging); if (mMenu == null) { // Use a PopupMenu to create the Menu object. Note this is not the same as the // AppMenu (mAppMenu) created below. PopupMenu tempMenu = new PopupMenu(mActivity, anchorView); tempMenu.inflate(mMenuResourceId); mMenu = tempMenu.getMenu(); } mDelegate.prepareMenu(mMenu); ContextThemeWrapper wrapper = new ContextThemeWrapper(mActivity, R.style.OverflowMenuTheme); if (mAppMenu == null) { TypedArray a = wrapper.obtainStyledAttributes(new int[] {android.R.attr.listPreferredItemHeightSmall, android.R.attr.listDivider}); int itemRowHeight = a.getDimensionPixelSize(0, 0); Drawable itemDivider = a.getDrawable(1); int itemDividerHeight = itemDivider != null ? itemDivider.getIntrinsicHeight() : 0; a.recycle(); mAppMenu = new AppMenu(mMenu, itemRowHeight, itemDividerHeight, this, mActivity.getResources()); mAppMenuDragHelper = new AppMenuDragHelper(mActivity, mAppMenu, itemRowHeight); } // Get the height and width of the display. Rect appRect = new Rect(); mActivity.getWindow().getDecorView().getWindowVisibleDisplayFrame(appRect); // Use full size of window for abnormal appRect. if (appRect.left < 0 && appRect.top < 0) { appRect.left = 0; appRect.top = 0; appRect.right = mActivity.getWindow().getDecorView().getWidth(); appRect.bottom = mActivity.getWindow().getDecorView().getHeight(); } Point pt = new Point(); mActivity.getWindowManager().getDefaultDisplay().getSize(pt); mAppMenu.show(wrapper, anchorView, isByPermanentButton, rotation, appRect, pt.y, mDelegate.getFooterResourceId()); mAppMenuDragHelper.onShow(startDragging); RecordUserAction.record("MobileMenuShow"); return true; } void appMenuDismissed() { mAppMenuDragHelper.finishDragging(); } /** * @return Whether the App Menu is currently showing. */ public boolean isAppMenuShowing() { return mAppMenu != null && mAppMenu.isShowing(); } /** * @return The App Menu that the menu handler is interacting with. */ public AppMenu getAppMenu() { return mAppMenu; } AppMenuDragHelper getAppMenuDragHelper() { return mAppMenuDragHelper; } /** * Requests to hide the App Menu. */ public void hideAppMenu() { if (mAppMenu != null && mAppMenu.isShowing()) mAppMenu.dismiss(); } /** * Adds the observer to App Menu. * @param observer Observer that should be notified about App Menu changes. */ public void addObserver(AppMenuObserver observer) { mObservers.add(observer); } /** * Removes the observer from the App Menu. * @param observer Observer that should no longer be notified about App Menu changes. */ public void removeObserver(AppMenuObserver observer) { mObservers.remove(observer); } void onOptionsItemSelected(MenuItem item) { mActivity.onOptionsItemSelected(item); } /** * Called by AppMenu to report that the App Menu visibility has changed. * @param isVisible Whether the App Menu is showing. */ void onMenuVisibilityChanged(boolean isVisible) { for (int i = 0; i < mObservers.size(); ++i) { mObservers.get(i).onMenuVisibilityChanged(isVisible); } } }