blob: f9d13626195d54c7ccd3f5ffaa088803b31af82a [file] [log] [blame]
// 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.os.Bundle;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.View.OnTouchListener;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import org.chromium.base.metrics.RecordUserAction;
/**
* A helper class for a menu button to decide when to show the app menu and forward touch
* events.
*
* Simply construct this class and pass the class instance to a menu button as TouchListener.
* Then this class will handle everything regarding showing app menu for you.
*/
public class AppMenuButtonHelper extends AccessibilityDelegate implements OnTouchListener {
private final AppMenuHandler mMenuHandler;
private Runnable mOnAppMenuShownListener;
private boolean mIsTouchEventsBeingProcessed;
private boolean mShowMenuOnUp;
private boolean mMenuShowsFromBottom;
private Runnable mOnClickRunnable;
/**
* @param menuHandler MenuHandler implementation that can show and get the app menu.
*/
public AppMenuButtonHelper(AppMenuHandler menuHandler) {
mMenuHandler = menuHandler;
}
/**
* @param showsFromBottom Whether the menu shows from the bottom by default.
*/
public void setMenuShowsFromBottom(boolean showsFromBottom) {
mMenuShowsFromBottom = showsFromBottom;
}
/**
* @param onAppMenuShownListener This is called when the app menu is shown by this class.
*/
public void setOnAppMenuShownListener(Runnable onAppMenuShownListener) {
mOnAppMenuShownListener = onAppMenuShownListener;
}
/**
* @param showMenuOnUp Whether app menu should show on the up action.
* If false, the app menu will be shown on the down action.
*/
public void setShowMenuOnUp(boolean showMenuOnUp) {
mShowMenuOnUp = showMenuOnUp;
}
/**
* Shows the app menu if it is not already shown.
* @param view View that initiated showing this menu. Normally it is a menu button.
* @param startDragging Whether dragging is started.
* @return Whether or not if the app menu is successfully shown.
*/
private boolean showAppMenu(View view, boolean startDragging) {
if (!mMenuHandler.isAppMenuShowing()
&& mMenuHandler.showAppMenu(view, startDragging, mMenuShowsFromBottom)) {
// Initial start dragging can be canceled in case if it was just single tap.
// So we only record non-dragging here, and will deal with those dragging cases in
// AppMenuDragHelper class.
if (!startDragging) RecordUserAction.record("MobileUsingMenuBySwButtonTap");
if (mOnAppMenuShownListener != null) {
mOnAppMenuShownListener.run();
}
return true;
}
return false;
}
/**
* @return Whether app menu is active. That is, AppMenu is showing or menu button is consuming
* touch events to prepare AppMenu showing.
*/
public boolean isAppMenuActive() {
return mIsTouchEventsBeingProcessed || mMenuHandler.isAppMenuShowing();
}
/**
* Handle the key press event on a menu button.
* @param view View that received the enter key press event.
* @return Whether the app menu was shown as a result of this action.
*/
public boolean onEnterKeyPress(View view) {
return showAppMenu(view, false);
}
/**
* Set whether touch event is being processed and view is pressed on touch event.
* @param view View that received a touch event.
* @param isActionDown Whether the touch event is a down action.
*/
private void updateTouchEvent(View view, boolean isActionDown) {
mIsTouchEventsBeingProcessed = isActionDown;
view.setPressed(isActionDown);
}
/**
* Set a runnable for click events on the menu button. This runnable is triggered with the down
* motion event rather than click specifically. This is primarily used to track interaction with
* the menu button.
* @param clickRunnable The {@link Runnable} to be executed on a down event.
*/
public void setOnClickRunnable(Runnable clickRunnable) {
mOnClickRunnable = clickRunnable;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View view, MotionEvent event) {
boolean isTouchEventConsumed = false;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (!mShowMenuOnUp) {
isTouchEventConsumed |= true;
updateTouchEvent(view, true);
if (mOnClickRunnable != null) mOnClickRunnable.run();
showAppMenu(view, true);
}
break;
case MotionEvent.ACTION_UP:
isTouchEventConsumed |= true;
updateTouchEvent(view, false);
if (mShowMenuOnUp) showAppMenu(view, false);
break;
case MotionEvent.ACTION_CANCEL:
isTouchEventConsumed |= true;
updateTouchEvent(view, false);
break;
default:
}
// If user starts to drag on this menu button, ACTION_DOWN and all the subsequent touch
// events are received here. We need to forward this event to the app menu to handle
// dragging correctly.
AppMenuDragHelper dragHelper = mMenuHandler.getAppMenuDragHelper();
if (dragHelper != null) {
isTouchEventConsumed |= dragHelper.handleDragging(event, view);
}
return isTouchEventConsumed;
}
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
if (action == AccessibilityNodeInfo.ACTION_CLICK) {
if (!mMenuHandler.isAppMenuShowing()) {
showAppMenu(host, false);
} else {
mMenuHandler.hideAppMenu();
}
host.playSoundEffect(SoundEffectConstants.CLICK);
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return true;
}
return super.performAccessibilityAction(host, action, args);
}
}