Index: chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java |
index e8fa56dbcaea0c5100706dc73328f51bf034b7d3..40c7bc41a22d37824778c08a7339bd53e858fc44 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/appmenu/AppMenu.java |
@@ -4,47 +4,32 @@ |
package org.chromium.chrome.browser.appmenu; |
-import android.animation.TimeAnimator; |
import android.app.Activity; |
import android.content.Context; |
import android.content.res.Resources; |
-import android.graphics.Point; |
import android.graphics.Rect; |
-import android.graphics.drawable.Drawable; |
-import android.os.SystemClock; |
-import android.util.AttributeSet; |
-import android.util.Log; |
-import android.view.Display; |
import android.view.KeyEvent; |
import android.view.LayoutInflater; |
import android.view.Menu; |
import android.view.MenuItem; |
-import android.view.MotionEvent; |
import android.view.Surface; |
import android.view.View; |
import android.view.View.OnClickListener; |
import android.view.View.OnKeyListener; |
-import android.view.View.OnTouchListener; |
-import android.view.ViewConfiguration; |
import android.view.ViewGroup; |
-import android.view.ViewParent; |
import android.widget.AdapterView; |
-import android.widget.BaseAdapter; |
import android.widget.HeaderViewListAdapter; |
import android.widget.ImageButton; |
-import android.widget.ImageView; |
import android.widget.ListPopupWindow; |
import android.widget.ListView; |
import android.widget.ListView.FixedViewInfo; |
import android.widget.PopupWindow; |
import android.widget.PopupWindow.OnDismissListener; |
-import android.widget.TextView; |
import com.google.common.annotations.VisibleForTesting; |
import org.chromium.chrome.R; |
import org.chromium.chrome.browser.BookmarksBridge; |
-import org.chromium.chrome.browser.UmaBridge; |
import org.chromium.chrome.browser.util.KeyNavigationUtil; |
import java.util.ArrayList; |
@@ -57,19 +42,9 @@ import java.util.List; |
* - Disabled items are grayed out. |
*/ |
public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
- private static final String TAG = "AppMenu"; |
- |
private static final float LAST_ITEM_SHOW_FRACTION = 0.5f; |
private static final int DIVIDER_HEIGHT_DP = 1; |
- private static final float AUTO_SCROLL_AREA_MAX_RATIO = 0.25f; |
- private static final int EDGE_SWIPE_IN_ADDITIONAL_SLOP_TIME_MS = 500; |
- |
- // Internally used action constants for dragging. |
- private static final int ITEM_ACTION_HIGHLIGHT = 0; |
- private static final int ITEM_ACTION_PERFORM = 1; |
- private static final int ITEM_ACTION_CLEAR_HIGHLIGHT_ALL = 2; |
- |
private final Menu mMenu; |
private final Activity mActivity; |
private final int mItemRowHeight; |
@@ -79,46 +54,11 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
private ListPopupWindow mPopup; |
private LayoutInflater mInflater; |
private boolean mShowIconRow; |
- private MenuAdapter mAdapter; |
- private ImageButton mBookmarkButton; |
+ private AppMenuAdapter mAdapter; |
private View mIconRowView; |
private AppMenuHandler mHandler; |
- |
- // Dragging related variables, i.e., menu showing initiated by touch down and drag to navigate. |
- private final float mAutoScrollFullVelocity; |
- private final int mEdgeSwipeInSlop; |
- private final int mEdgeSwipeInAdditionalSlop; |
- private final int mEdgeSwipeOutSlop; |
- private int mScaledTouchSlop; |
- private long mHardwareMenuButtonUpTime; |
- private boolean mIsByHardwareButton; |
- private boolean mDragPending; |
- private final TimeAnimator mDragScrolling = new TimeAnimator(); |
- private float mDragScrollOffset; |
- private int mDragScrollOffsetRounded; |
- private volatile float mDragScrollingVelocity; |
- private volatile float mLastTouchX; |
- private volatile float mLastTouchY; |
private int mCurrentScreenRotation = -1; |
- private float mTopTouchMovedBound; |
- private float mBottomTouchMovedBound; |
- private boolean mIsDownScrollable; |
- private boolean mIsUpScrollable; |
- |
- // Sub-UI-controls, backward, forward, bookmark and listView, are getting a touch event first |
- // if the app menu is initiated by hardware menu button. For those cases, we need to |
- // conditionally forward the touch event to our drag scrolling method. |
- private final OnTouchListener mDragScrollTouchEventForwarder = new OnTouchListener() { |
- @Override |
- public boolean onTouch(View view, MotionEvent event) { |
- return AppMenu.this.handleDragging(event); |
- } |
- }; |
- |
- // These are used in a function locally, but defined here to avoid heap allocation on every |
- // touch event. |
- private final Rect mScreenVisibleRect = new Rect(); |
- private final int[] mScreenVisiblePoint = new int[2]; |
+ private boolean mIsByHardwareButton; |
/** |
* Creates and sets up the App Menu. |
@@ -129,8 +69,7 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
AppMenu(Activity activity, Menu menu, int itemRowHeight, AppMenuHandler handler) { |
mActivity = activity; |
mMenu = menu; |
- mScaledTouchSlop = |
- ViewConfiguration.get(mActivity.getApplicationContext()).getScaledTouchSlop(); |
+ |
mItemRowHeight = itemRowHeight; |
assert mItemRowHeight > 0; |
@@ -142,50 +81,6 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
mAdditionalVerticalOffset = res.getDimensionPixelSize(R.dimen.menu_vertical_offset); |
mVerticalFadeDistance = res.getDimensionPixelSize(R.dimen.menu_vertical_fade_distance); |
- mAutoScrollFullVelocity = res.getDimensionPixelSize(R.dimen.auto_scroll_full_velocity); |
- mEdgeSwipeInSlop = res.getDimensionPixelSize(R.dimen.edge_swipe_in_slop); |
- mEdgeSwipeInAdditionalSlop = res.getDimensionPixelSize( |
- R.dimen.edge_swipe_in_additional_slop); |
- mEdgeSwipeOutSlop = res.getDimensionPixelSize(R.dimen.edge_swipe_out_slop); |
- // If user is dragging and the popup ListView is too big to display at once, |
- // mDragScrolling animator scrolls mPopup.getListView() automatically depending on |
- // the user's touch position. |
- mDragScrolling.setTimeListener(new TimeAnimator.TimeListener() { |
- @Override |
- public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { |
- if (mPopup == null || mPopup.getListView() == null) return; |
- |
- // We keep both mDragScrollOffset and mDragScrollOffsetRounded because |
- // the actual scrolling is by the rounded value but at the same time we also |
- // want to keep the precise scroll value in float. |
- mDragScrollOffset += (deltaTime * 0.001f) * mDragScrollingVelocity; |
- int diff = Math.round(mDragScrollOffset - mDragScrollOffsetRounded); |
- mDragScrollOffsetRounded += diff; |
- mPopup.getListView().smoothScrollBy(diff, 0); |
- |
- // Force touch move event to highlight items correctly for the scrolled position. |
- if (!Float.isNaN(mLastTouchX) && !Float.isNaN(mLastTouchY)) { |
- int actionToPerform = isInSwipeOutRegion(mLastTouchX, mLastTouchY) ? |
- ITEM_ACTION_CLEAR_HIGHLIGHT_ALL : ITEM_ACTION_HIGHLIGHT; |
- menuItemAction(Math.round(mLastTouchX), Math.round(mLastTouchY), |
- actionToPerform); |
- } |
- } |
- }); |
- } |
- |
- private void updateBookmarkButton() { |
- final MenuItem bookmarkMenuItem = mMenu.findItem(R.id.bookmark_this_page_id); |
- if (mBookmarkButton == null || bookmarkMenuItem == null) return; |
- if (bookmarkMenuItem.isEnabled()) { |
- mBookmarkButton.setImageResource(R.drawable.star); |
- mBookmarkButton.setContentDescription(mBookmarkButton.getContext().getString( |
- R.string.accessibility_menu_bookmark)); |
- } else { |
- mBookmarkButton.setImageResource(R.drawable.star_lit); |
- mBookmarkButton.setContentDescription(mBookmarkButton.getContext().getString( |
- R.string.accessibility_menu_edit_bookmark)); |
- } |
} |
/** |
@@ -197,13 +92,8 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
* @param showIconRow Whether or not the icon row should be shown, |
* @param isByHardwareButton Whether or not hardware button triggered it. (oppose to software |
* button) |
- * @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 isByHardwareButton is true, this is ignored. |
*/ |
- void show(Context context, View anchorView, boolean showIconRow, |
- boolean isByHardwareButton, boolean startDragging) { |
+ void show(Context context, View anchorView, boolean showIconRow, boolean isByHardwareButton) { |
mPopup = new ListPopupWindow(context, null, android.R.attr.popupMenuStyle); |
mInflater = LayoutInflater.from(context); |
mPopup.setModal(true); |
@@ -215,7 +105,7 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
if (mPopup.getAnchorView() instanceof ImageButton) { |
((ImageButton) mPopup.getAnchorView()).setSelected(false); |
} |
- mHandler.onMenuVisibilityChanged(false, ListView.INVALID_POSITION); |
+ mHandler.onMenuVisibilityChanged(false); |
} |
}); |
mPopup.setWidth(context.getResources().getDimensionPixelSize(R.dimen.menu_width)); |
@@ -236,11 +126,11 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
// A List adapter for visible items in the Menu. The first row is added as a header to the |
// list view. |
- mAdapter = new MenuAdapter(menuItems, mInflater); |
+ mAdapter = new AppMenuAdapter(menuItems, mInflater); |
if (mShowIconRow) { |
mIconRowView = mInflater.inflate(R.layout.menu_icon_row, null); |
// Add click handlers for the header icons. |
- setIconRowEventHandlers(mIconRowView, mActivity); |
+ setIconRowEventHandlers(mIconRowView); |
updateBookmarkButton(); |
// Icon row goes into header of List view. |
@@ -264,7 +154,7 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
mPopup.show(); |
mPopup.getListView().setDividerHeight(mItemDividerHeight); |
- mHandler.onMenuVisibilityChanged(true, getCurrentFocusedPosition()); |
+ mHandler.onMenuVisibilityChanged(true); |
if (mVerticalFadeDistance > 0) { |
mPopup.getListView().setVerticalFadingEdgeEnabled(true); |
@@ -272,35 +162,6 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
} |
mPopup.getListView().setOnKeyListener(this); |
- |
- // Initiate drag related variables and listeners. |
- mLastTouchX = Float.NaN; |
- mLastTouchY = Float.NaN; |
- mDragScrollOffset = 0.0f; |
- mDragScrollOffsetRounded = 0; |
- mDragScrollingVelocity = 0.0f; |
- |
- mDragPending = isByHardwareButton; |
- mIsDownScrollable = !isByHardwareButton; |
- mIsUpScrollable = !isByHardwareButton; |
- |
- mTopTouchMovedBound = Float.POSITIVE_INFINITY; |
- mBottomTouchMovedBound = Float.NEGATIVE_INFINITY; |
- mHardwareMenuButtonUpTime = -1; |
- |
- // Handles dragging related logic. |
- mPopup.getListView().setOnTouchListener(mDragScrollTouchEventForwarder); |
- |
- // We assume that the parent of popup ListView is an instance of View. Otherwise, dragging |
- // from a hardware menu button won't work. |
- ViewParent listViewParent = mPopup.getListView().getParent(); |
- if (listViewParent instanceof View) { |
- ((View) listViewParent).setOnTouchListener(mDragScrollTouchEventForwarder); |
- } else { |
- assert false; |
- } |
- |
- if (!isByHardwareButton && startDragging) mDragScrolling.start(); |
} |
private void setPopupOffset(ListPopupWindow popup, int screenRotation, Rect appRect) { |
@@ -355,15 +216,12 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
mPopup.clearListSelection(); |
requestFocusToEnabledIconRowButton(); |
} |
- |
- mHandler.onKeyboardFocusChanged(getCurrentFocusedPosition()); |
return handled; |
} else if (KeyNavigationUtil.isGoDown(event)) { |
boolean handled = mPopup.onKeyDown(keyCode, event); |
if (listView.getSelectedItemPosition() == ListView.INVALID_POSITION) { |
listView.setSelection(listView.getCount() - 1); |
} |
- mHandler.onKeyboardFocusChanged(getCurrentFocusedPosition()); |
return handled; |
} else if (KeyNavigationUtil.isEnter(event)) { |
int position = getCurrentFocusedPosition(); |
@@ -372,8 +230,7 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
int adjustedPosition = mShowIconRow ? position - 1 : position; |
MenuItem clickedItem = mAdapter.getItem(adjustedPosition); |
dismiss(); |
- mActivity.onOptionsItemSelected(clickedItem); |
- mHandler.onKeyboardActivatedItem(position); |
+ mHandler.onOptionsItemSelected(clickedItem); |
return true; |
} else if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) { |
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { |
@@ -393,285 +250,160 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
return false; |
} |
- /** |
- * @return Visible rect in screen coordinates for the given View. |
- */ |
- private Rect getScreenVisibleRect(View view) { |
- view.getLocalVisibleRect(mScreenVisibleRect); |
- view.getLocationOnScreen(mScreenVisiblePoint); |
- mScreenVisibleRect.offset(mScreenVisiblePoint[0], mScreenVisiblePoint[1]); |
- return mScreenVisibleRect; |
+ @Override |
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
+ // Account for header. MenuAdapter does not know about header, |
+ // but the 'position' includes the header. |
+ int adjustedPosition = mShowIconRow ? position - 1 : position; |
+ MenuItem clickedItem = mAdapter.getItem(adjustedPosition); |
+ if (clickedItem.isEnabled()) { |
+ dismiss(); |
+ mHandler.onOptionsItemSelected(clickedItem); |
+ } |
} |
/** |
- * This is a hint for adjusting edgeSwipeInSlop. For example. If the touch event started |
- * immediately after hardware menu button up, then we use larger edgeSwipeInSlop because it |
- * implies user is swiping in fast. |
+ * Dismisses the app menu and cancels the drag-to-scroll if it is taking place. |
*/ |
- public void hardwareMenuButtonUp() { |
- // There should be only one time hardware menu button up. |
- assert mHardwareMenuButtonUpTime == -1; |
- mHardwareMenuButtonUpTime = SystemClock.uptimeMillis(); |
+ void dismiss() { |
+ mHandler.appMenudismiss(); |
+ if (isShowing()) mPopup.dismiss(); |
} |
/** |
- * @return The shortest distance from the screen edges for the given position rawX, rawY |
- * in screen coordinates. |
+ * @return Whether the app menu is currently showing. |
*/ |
- private float getShortestDistanceFromEdge(float rawX, float rawY) { |
- Display display = mActivity.getWindowManager().getDefaultDisplay(); |
- Point displaySize = new Point(); |
- display.getSize(displaySize); |
- |
- float distance = Math.min( |
- Math.min(rawY, displaySize.y - rawY - 1), |
- Math.min(rawX, displaySize.x - rawX - 1)); |
- if (distance < 0.0f) { |
- Log.d(TAG, "Received touch event out of the screen edge boundary. distance = " + |
- distance); |
+ boolean isShowing() { |
+ if (mPopup == null) { |
+ return false; |
} |
- return Math.abs(distance); |
+ return mPopup.isShowing(); |
} |
- /** |
- * @return The distance from the screen edge that is likely where the hardware menu button is |
- * located at. We assume the hardware menu button is at the bottom in the default, |
- * ROTATION_0, rotation. Note that there is a bug filed for Android API to request |
- * hardware menu button position b/10007237. |
- */ |
- private float getDistanceFromHardwareMenuButtonSideEdge(float rawX, float rawY) { |
- Display display = mActivity.getWindowManager().getDefaultDisplay(); |
- Point displaySize = new Point(); |
- display.getSize(displaySize); |
- |
- float distance; |
- switch (mCurrentScreenRotation) { |
- case Surface.ROTATION_0: |
- distance = displaySize.y - rawY - 1; |
- break; |
- case Surface.ROTATION_180: |
- distance = rawY; |
- break; |
- case Surface.ROTATION_90: |
- distance = displaySize.x - rawX - 1; |
- break; |
- case Surface.ROTATION_270: |
- distance = rawX; |
- break; |
- default: |
- distance = 0.0f; |
- assert false; |
- break; |
- } |
- if (distance < 0.0f) { |
- Log.d(TAG, "Received touch event out of hardware menu button side edge boundary." + |
- " distance = " + distance); |
- } |
- return Math.abs(distance); |
+ @VisibleForTesting |
+ int getCount() { |
+ if (mPopup == null || mPopup.getListView() == null) return 0; |
+ return mPopup.getListView().getCount(); |
} |
- /** |
- * @return Whether or not the position should be considered swiping-out, if ACTION_UP happens |
- * at the position. |
- */ |
- private boolean isInSwipeOutRegion(float rawX, float rawY) { |
- return getShortestDistanceFromEdge(rawX, rawY) < mEdgeSwipeOutSlop; |
+ ListPopupWindow getPopup() { |
+ return mPopup; |
} |
- /** |
- * Computes Edge-swipe-in-slop and returns it. |
- * |
- * When user swipes in from a hardware menu button, because the swiping-in touch event doesn't |
- * necessarily start form the exact edge, we should also consider slightly more inside touch |
- * event as swiping-in. This value, Edge-swipe-in-slop, is the threshold distance from the |
- * edge that separates swiping-in and normal touch. |
- * |
- * @param event Touch event that eventually made this call. |
- * @return Edge-swipe-in-slop. |
- */ |
- private float getEdgeSwipeInSlop(MotionEvent event) { |
- float edgeSwipeInSlope = mEdgeSwipeInSlop; |
- if (mHardwareMenuButtonUpTime == -1) { |
- // Hardware menu hasn't even had UP event yet. That means, user is swiping in really |
- // really fast. So use large edgeSwipeInSlope. |
- edgeSwipeInSlope += mEdgeSwipeInAdditionalSlop; |
- } else { |
- // If it's right after we had hardware menu button UP event, use large edgeSwipeInSlop, |
- // Otherwise, use small edgeSwipeInSlop. |
- float additionalEdgeSwipeInSlop = ((mHardwareMenuButtonUpTime - event.getEventTime() |
- + EDGE_SWIPE_IN_ADDITIONAL_SLOP_TIME_MS) * 0.001f) |
- * mEdgeSwipeInAdditionalSlop; |
- edgeSwipeInSlope += Math.max(0.0f, additionalEdgeSwipeInSlop); |
- } |
- return edgeSwipeInSlope; |
+ boolean isShowingIconRow() { |
+ return mShowIconRow; |
} |
- /** |
- * Gets all the touch events and updates dragging related logic. Note that if this app menu |
- * is initiated by software UI control, then the control should set onTouchListener and forward |
- * all the events to this method because the initial UI control that processed ACTION_DOWN will |
- * continue to get all the subsequent events. |
- * |
- * @param event Touch event to be processed. |
- * @return Whether the event is handled. |
- */ |
- boolean handleDragging(MotionEvent event) { |
- if (!isShowing() || (!mDragPending && !mDragScrolling.isRunning())) return false; |
- |
- // We will only use the screen space coordinate (rawX, rawY) to reduce confusion. |
- // This code works across many different controls, so using local coordinates will be |
- // a disaster. |
- |
- final float rawX = event.getRawX(); |
- final float rawY = event.getRawY(); |
- final int roundedRawX = Math.round(rawX); |
- final int roundedRawY = Math.round(rawY); |
- final int eventActionMasked = event.getActionMasked(); |
- final ListView listView = mPopup.getListView(); |
- |
- mLastTouchX = rawX; |
- mLastTouchY = rawY; |
- |
- // Because (hardware) menu button can be right or left side of the screen, if we just |
- // trigger auto scrolling based on Y inside the listView, it might be scrolled |
- // unintentionally. Therefore, we will require touch position to move up or down a certain |
- // amount of distance to trigger auto scrolling up or down. |
- mTopTouchMovedBound = Math.min(mTopTouchMovedBound, rawY); |
- mBottomTouchMovedBound = Math.max(mBottomTouchMovedBound, rawY); |
- if (rawY <= mBottomTouchMovedBound - mScaledTouchSlop) { |
- mIsUpScrollable = true; |
- } |
- if (rawY >= mTopTouchMovedBound + mScaledTouchSlop) { |
- mIsDownScrollable = true; |
- } |
+ View getIconRowView() { |
+ return mIconRowView; |
+ } |
- if (eventActionMasked == MotionEvent.ACTION_CANCEL) { |
- dismiss(); |
- return true; |
- } |
+ int getItemRowHeight() { |
+ return mItemRowHeight; |
+ } |
- if (eventActionMasked == MotionEvent.ACTION_DOWN) { |
- assert mIsByHardwareButton != mDragScrolling.isStarted(); |
- if (mIsByHardwareButton) { |
- if (mDragPending && getDistanceFromHardwareMenuButtonSideEdge(rawX, rawY) < |
- getEdgeSwipeInSlop(event)) { |
- mDragScrolling.start(); |
- mDragPending = false; |
- UmaBridge.usingMenu(true, true); |
- } else { |
- if (!getScreenVisibleRect(listView).contains(roundedRawX, roundedRawY)) { |
- dismiss(); |
- } |
- mDragPending = false; |
- UmaBridge.usingMenu(true, false); |
- return false; |
- } |
- } |
+ private void updateBookmarkButton() { |
+ final MenuItem bookmarkMenuItem = mMenu.findItem(R.id.bookmark_this_page_id); |
+ ImageButton bookmarkButton = |
+ (ImageButton) mIconRowView.findViewById(R.id.menu_item_bookmark); |
+ if (bookmarkMenuItem.isEnabled()) { |
+ bookmarkButton.setImageResource(R.drawable.star); |
+ bookmarkButton.setContentDescription(bookmarkButton.getContext().getString( |
+ R.string.accessibility_menu_bookmark)); |
+ } else { |
+ bookmarkButton.setImageResource(R.drawable.star_lit); |
+ bookmarkButton.setContentDescription(bookmarkButton.getContext().getString( |
+ R.string.accessibility_menu_edit_bookmark)); |
} |
+ } |
- // After this line, drag scrolling is happening. |
- if (!mDragScrolling.isRunning()) return false; |
+ @VisibleForTesting |
+ int getCurrentFocusedPosition() { |
+ if (mPopup == null || mPopup.getListView() == null) return ListView.INVALID_POSITION; |
+ ListView listView = mPopup.getListView(); |
+ int position = listView.getSelectedItemPosition(); |
- boolean didPerformClick = false; |
- int itemAction = ITEM_ACTION_CLEAR_HIGHLIGHT_ALL; |
- if (!isInSwipeOutRegion(rawX, rawY)) { |
- switch (eventActionMasked) { |
- case MotionEvent.ACTION_DOWN: |
- case MotionEvent.ACTION_MOVE: |
- itemAction = ITEM_ACTION_HIGHLIGHT; |
- break; |
- case MotionEvent.ACTION_UP: |
- itemAction = ITEM_ACTION_PERFORM; |
- break; |
- default: |
- break; |
+ // Check if any of the icon row icons are focused. |
+ if (mShowIconRow) { |
+ if (mIconRowView.findViewById(R.id.menu_item_back).isFocused() || |
+ mIconRowView.findViewById(R.id.menu_item_forward).isFocused() || |
+ mIconRowView.findViewById(R.id.menu_item_bookmark).isFocused()) { |
+ return 0; |
} |
} |
- didPerformClick = menuItemAction(roundedRawX, roundedRawY, itemAction); |
+ return position; |
+ } |
- if (eventActionMasked == MotionEvent.ACTION_UP && !didPerformClick) { |
- dismiss(); |
- } else if (eventActionMasked == MotionEvent.ACTION_MOVE) { |
- // Auto scrolling on the top or the bottom of the listView. |
- if (listView.getHeight() > 0) { |
- float autoScrollAreaRatio = Math.min(AUTO_SCROLL_AREA_MAX_RATIO, |
- mItemRowHeight * 1.2f / listView.getHeight()); |
- float normalizedY = |
- (rawY - getScreenVisibleRect(listView).top) / listView.getHeight(); |
- if (mIsUpScrollable && normalizedY < autoScrollAreaRatio) { |
- // Top |
- mDragScrollingVelocity = (normalizedY / autoScrollAreaRatio - 1.0f) |
- * mAutoScrollFullVelocity; |
- } else if (mIsDownScrollable && normalizedY > 1.0f - autoScrollAreaRatio) { |
- // Bottom |
- mDragScrollingVelocity = ((normalizedY - 1.0f) / autoScrollAreaRatio + 1.0f) |
- * mAutoScrollFullVelocity; |
- } else { |
- // Middle or not scrollable. |
- mDragScrollingVelocity = 0.0f; |
- } |
+ @VisibleForTesting |
+ int getCurrentFocusedItemId() { |
+ int position = getCurrentFocusedPosition(); |
+ if (position == ListView.INVALID_POSITION) return -1; |
+ if (mShowIconRow && position == 0) { |
+ if (mIconRowView.findViewById(R.id.menu_item_back).isFocused()) { |
+ return R.id.back_menu_id; |
+ } else if (mIconRowView.findViewById(R.id.menu_item_forward).isFocused()) { |
+ return R.id.forward_menu_id; |
+ } else if (mIconRowView.findViewById(R.id.menu_item_bookmark).isFocused()) { |
+ return R.id.bookmark_this_page_id; |
} |
} |
- |
- return true; |
+ int adjustedPosition = mShowIconRow ? position - 1 : position; |
+ return mAdapter.getItem(adjustedPosition).getItemId(); |
} |
/** |
- * Performs the specified action on the menu item specified by the screen coordinate position. |
- * @param screenX X in screen space coordinate. |
- * @param screenY Y in screen space coordinate. |
- * @param action Action type to perform, it should be one of ITEM_ACTION_* constants. |
- * @return true whether or not a menu item is performed (executed). |
+ * Adds click handlers for items in the icon row. |
+ * Also disable/enable the view based on the menu item. |
+ * We assume that we have Back, Forward and Bookmark-star icons in this view. |
*/ |
- private boolean menuItemAction(int screenX, int screenY, int action) { |
- ListView listView = mPopup.getListView(); |
- |
- ArrayList<View> itemViews = new ArrayList<View>(); |
- for (int i = 0; i < listView.getChildCount(); ++i) { |
- itemViews.add(listView.getChildAt(i)); |
- } |
- |
- if (mIconRowView != null && mShowIconRow) { |
- itemViews.add(mIconRowView.findViewById(R.id.menu_item_back)); |
- itemViews.add(mIconRowView.findViewById(R.id.menu_item_forward)); |
- itemViews.add(mIconRowView.findViewById(R.id.menu_item_bookmark)); |
- } |
+ private void setIconRowEventHandlers(View iconRowView) { |
+ OnKeyListener keyListener = new OnKeyListener() { |
+ @Override |
+ public boolean onKey(View v, int keyCode, KeyEvent event) { |
+ if (isShowing() && v.isFocused()) { |
+ if (keyCode == KeyEvent.KEYCODE_MENU) { |
+ v.setSelected(false); |
+ dismiss(); |
+ return true; |
+ } else if (KeyNavigationUtil.isGoUp(event)) { |
+ // Catch attempts to move out of bounds. |
+ return true; |
+ } else if (KeyNavigationUtil.isGoDown(event)) { |
+ // Requesting focus on the mPopup.getListView().getChildAt(0) does not work. |
+ // Requesting the focus on the ListView focuses the first non-header item. |
+ mPopup.getListView().requestFocus(); |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ }; |
- boolean didPerformClick = false; |
- for (int i = 0; i < itemViews.size(); ++i) { |
- View itemView = itemViews.get(i); |
+ MenuItem menuItem = mMenu.findItem(R.id.back_menu_id); |
+ setIconRowItemEventHandlers(menuItem, iconRowView.findViewById(R.id.menu_item_back), |
+ keyListener, menuItem.isEnabled()); |
- // Skip the icon row that belongs to the listView because that doesn't really |
- // exist as an item. |
- int listViewPositionIndex = listView.getFirstVisiblePosition() + i; |
- if (mShowIconRow && listViewPositionIndex == 0) continue; |
+ menuItem = mMenu.findItem(R.id.forward_menu_id); |
+ setIconRowItemEventHandlers(menuItem, iconRowView.findViewById(R.id.menu_item_forward), |
+ keyListener, menuItem.isEnabled()); |
- boolean shouldPerform = itemView.isEnabled() && itemView.isShown() && |
- getScreenVisibleRect(itemView).contains(screenX, screenY); |
+ menuItem = mMenu.findItem(R.id.bookmark_this_page_id); |
+ setIconRowItemEventHandlers(menuItem, iconRowView.findViewById(R.id.menu_item_bookmark), |
+ keyListener, BookmarksBridge.isEditBookmarksEnabled()); |
+ } |
- switch (action) { |
- case ITEM_ACTION_HIGHLIGHT: |
- itemView.setPressed(shouldPerform); |
- break; |
- case ITEM_ACTION_PERFORM: |
- if (shouldPerform) { |
- if (itemView.getParent() == listView) { |
- listView.performItemClick(itemView, listViewPositionIndex, 0); |
- } else { |
- itemView.performClick(); |
- } |
- didPerformClick = true; |
- } |
- break; |
- case ITEM_ACTION_CLEAR_HIGHLIGHT_ALL: |
- itemView.setPressed(false); |
- break; |
- default: |
- assert false; |
- break; |
+ private void setIconRowItemEventHandlers(final MenuItem menuItem, View iconView, |
+ OnKeyListener keyListener, boolean enabled) { |
+ iconView.setEnabled(enabled); |
+ iconView.setFocusable(enabled); |
+ iconView.setOnKeyListener(keyListener); |
+ iconView.setOnClickListener(new OnClickListener() { |
+ @Override |
+ public void onClick(View v) { |
+ dismiss(); |
+ mHandler.onOptionsItemSelected(menuItem); |
} |
- } |
- return didPerformClick; |
+ }); |
} |
/** |
@@ -738,268 +470,4 @@ public class AppMenu implements AdapterView.OnItemClickListener, OnKeyListener { |
headerInfoList.add(viewInfo); |
return headerInfoList; |
} |
- |
- /** |
- * Adds click handlers for items in the icon row. |
- * Also disable/enable the view based on the menu item. |
- * We assume that we have Back, Forward and Bookmark-star icons in this view. |
- */ |
- private void setIconRowEventHandlers(View iconRowView, final Activity activity) { |
- final MenuItem backMenuItem = mMenu.findItem(R.id.back_menu_id); |
- final MenuItem forwardMenuItem = mMenu.findItem(R.id.forward_menu_id); |
- final MenuItem bookmarkMenuItem = mMenu.findItem(R.id.bookmark_this_page_id); |
- |
- View.OnFocusChangeListener focusListener = new View.OnFocusChangeListener() { |
- @Override |
- public void onFocusChange(View v, boolean hasFocus) { |
- mHandler.onKeyboardFocusChanged(getCurrentFocusedPosition()); |
- } |
- }; |
- |
- OnKeyListener keyListener = new OnKeyListener() { |
- @Override |
- public boolean onKey(View v, int keyCode, KeyEvent event) { |
- if (isShowing() && v.isFocused()) { |
- if (keyCode == KeyEvent.KEYCODE_MENU) { |
- v.setSelected(false); |
- dismiss(); |
- return true; |
- } else if (KeyNavigationUtil.isGoUp(event)) { |
- // Catch attempts to move out of bounds. |
- mHandler.onKeyboardFocusChanged(getCurrentFocusedPosition()); |
- return true; |
- } else if (KeyNavigationUtil.isGoDown(event)) { |
- // Requesting focus on the mPopup.getListView().getChildAt(0) does not work. |
- // Requesting the focus on the ListView focuses the first non-header item. |
- mPopup.getListView().requestFocus(); |
- mHandler.onKeyboardFocusChanged(getCurrentFocusedPosition()); |
- return true; |
- } else if (KeyNavigationUtil.isEnter(event)) { |
- mHandler.onKeyboardActivatedItem(getCurrentFocusedPosition()); |
- } |
- } |
- return false; |
- } |
- }; |
- |
- View backIcon = iconRowView.findViewById(R.id.menu_item_back); |
- backIcon.setEnabled(backMenuItem.isEnabled()); |
- backIcon.setFocusable(backMenuItem.isEnabled()); |
- backIcon.setOnKeyListener(keyListener); |
- backIcon.setOnFocusChangeListener(focusListener); |
- backIcon.setOnTouchListener(mDragScrollTouchEventForwarder); |
- backIcon.setOnClickListener(new OnClickListener() { |
- @Override |
- public void onClick(View v) { |
- dismiss(); |
- activity.onOptionsItemSelected(backMenuItem); |
- } |
- }); |
- |
- View forwardIcon = iconRowView.findViewById(R.id.menu_item_forward); |
- forwardIcon.setEnabled(forwardMenuItem.isEnabled()); |
- forwardIcon.setFocusable(forwardMenuItem.isEnabled()); |
- forwardIcon.setOnKeyListener(keyListener); |
- forwardIcon.setOnFocusChangeListener(focusListener); |
- forwardIcon.setOnTouchListener(mDragScrollTouchEventForwarder); |
- forwardIcon.setOnClickListener(new OnClickListener() { |
- @Override |
- public void onClick(View v) { |
- dismiss(); |
- activity.onOptionsItemSelected(forwardMenuItem); |
- } |
- }); |
- |
- // The bookmark button is assumed to be always enabled and focusable when navigating the |
- // menu using a keyboard. |
- mBookmarkButton = (ImageButton) iconRowView.findViewById(R.id.menu_item_bookmark); |
- mBookmarkButton.setEnabled(BookmarksBridge.isEditBookmarksEnabled()); |
- mBookmarkButton.setOnKeyListener(keyListener); |
- mBookmarkButton.setOnFocusChangeListener(focusListener); |
- mBookmarkButton.setOnTouchListener(mDragScrollTouchEventForwarder); |
- mBookmarkButton.setOnClickListener(new OnClickListener() { |
- @Override |
- public void onClick(View v) { |
- dismiss(); |
- activity.onOptionsItemSelected(bookmarkMenuItem); |
- } |
- }); |
- } |
- |
- /** |
- * Dismisses the app menu and cancels the drag-to-scroll if it is taking place. |
- */ |
- void dismiss() { |
- mDragScrolling.cancel(); |
- if (isShowing()) { |
- mPopup.dismiss(); |
- } |
- } |
- |
- /** |
- * @return Whether the app menu is currently showing. |
- */ |
- public boolean isShowing() { |
- if (mPopup == null) { |
- return false; |
- } |
- return mPopup.isShowing(); |
- } |
- |
- private int getCurrentFocusedPosition() { |
- if (mPopup == null || mPopup.getListView() == null) return ListView.INVALID_POSITION; |
- ListView listView = mPopup.getListView(); |
- int position = listView.getSelectedItemPosition(); |
- |
- // Check if any of the icon row icons are focused. |
- if (mShowIconRow) { |
- if (mIconRowView.findViewById(R.id.menu_item_back).isFocused() || |
- mIconRowView.findViewById(R.id.menu_item_forward).isFocused() || |
- mBookmarkButton.isFocused()) { |
- return 0; |
- } |
- } |
- return position; |
- } |
- |
- @Override |
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |
- // Account for header. MenuAdapter does not know about header, |
- // but the 'position' includes the header. |
- int adjustedPosition = mShowIconRow ? position - 1 : position; |
- MenuItem clickedItem = mAdapter.getItem(adjustedPosition); |
- if (clickedItem.isEnabled()) { |
- dismiss(); |
- mActivity.onOptionsItemSelected(clickedItem); |
- } |
- } |
- |
- @VisibleForTesting |
- int getCount() { |
- if (mPopup == null || mPopup.getListView() == null) return 0; |
- return mPopup.getListView().getCount(); |
- } |
- |
- /** |
- * ListAdapter to customize the view of items in the list. |
- */ |
- private static class MenuAdapter extends BaseAdapter { |
- private static final int VIEW_TYPE_MENUITEM = 0; |
- private static final int VIEW_TYPE_COUNT = 1; |
- |
- private final LayoutInflater mInflater; |
- private final List<MenuItem> mMenuItems; |
- private final int mNumMenuItems; |
- |
- public MenuAdapter(List<MenuItem> menuItems, LayoutInflater inflater) { |
- mMenuItems = menuItems; |
- mInflater = inflater; |
- mNumMenuItems = menuItems.size(); |
- } |
- |
- @Override |
- public int getCount() { |
- return mNumMenuItems; |
- } |
- |
- @Override |
- public int getViewTypeCount() { |
- return VIEW_TYPE_COUNT; |
- } |
- |
- @Override |
- public int getItemViewType(int position) { |
- return VIEW_TYPE_MENUITEM; |
- } |
- |
- @Override |
- public long getItemId(int position) { |
- return getItem(position).getItemId(); |
- } |
- |
- @Override |
- public MenuItem getItem(int position) { |
- if (position == ListView.INVALID_POSITION) return null; |
- assert position >= 0; |
- assert position < mMenuItems.size(); |
- return mMenuItems.get(position); |
- } |
- |
- @Override |
- public View getView(int position, View convertView, ViewGroup parent) { |
- View rowView = convertView; |
- // A ViewHolder keeps references to children views to avoid unneccessary calls |
- // to findViewById() on each row. |
- ViewHolder holder = null; |
- |
- // When convertView is not null, we can reuse it directly, there is no need |
- // to reinflate it. |
- if (rowView == null) { |
- holder = new ViewHolder(); |
- rowView = mInflater.inflate(R.layout.menu_item, null); |
- holder.text = (TextView) rowView.findViewById(R.id.menu_item_text); |
- holder.image = (MenuItemIcon) rowView.findViewById(R.id.menu_item_icon); |
- rowView.setTag(holder); |
- } else { |
- holder = (ViewHolder) convertView.getTag(); |
- } |
- MenuItem item = getItem(position); |
- |
- // Set up the icon. |
- Drawable icon = item.getIcon(); |
- holder.image.setImageDrawable(icon); |
- holder.image.setVisibility(icon == null ? View.GONE : View.VISIBLE); |
- holder.image.setChecked(item.isChecked()); |
- |
- holder.text.setText(item.getTitle()); |
- boolean isEnabled = item.isEnabled(); |
- // Set the text color (using a color state list). |
- holder.text.setEnabled(isEnabled); |
- // This will ensure that the item is not highlighted when selected. |
- rowView.setEnabled(isEnabled); |
- return rowView; |
- } |
- |
- static class ViewHolder { |
- TextView text; |
- MenuItemIcon image; |
- } |
- } |
- |
- /** |
- * A menu icon that supports the checkable state. |
- */ |
- static class MenuItemIcon extends ImageView { |
- private static final int[] CHECKED_STATE_SET = new int[] {android.R.attr.state_checked}; |
- private boolean mCheckedState; |
- |
- public MenuItemIcon(Context context, AttributeSet attrs) { |
- super(context, attrs); |
- } |
- |
- /** |
- * Sets whether the item is checked and refreshes the View if necessary. |
- */ |
- protected void setChecked(boolean state) { |
- if (state == mCheckedState) return; |
- mCheckedState = state; |
- refreshDrawableState(); |
- } |
- |
- @Override |
- public void setPressed(boolean state) { |
- // We don't want to highlight the checkbox icon since the parent item is already |
- // highlighted. |
- return; |
- } |
- |
- @Override |
- public int[] onCreateDrawableState(int extraSpace) { |
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); |
- if (mCheckedState) { |
- mergeDrawableStates(drawableState, CHECKED_STATE_SET); |
- } |
- return drawableState; |
- } |
- } |
-} |
+} |