Copy AppMenu framework from Chrome

- copied AppMenu framework files from
  org.chromium.chrom.browser.appmenu
  commit-id: fdb3ea17e8436e028e320b1a752249d36423483d

Change-Id: I01e9106cd4107fadee6951c13b7e6ac2329d98e4
diff --git a/res/anim/menu_enter.xml b/res/anim/menu_enter.xml
new file mode 100644
index 0000000..e6c4936
--- /dev/null
+++ b/res/anim/menu_enter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+    <scale android:interpolator="@interpolator/transform_curve_interpolator"
+        android:fromXScale="0"
+        android:toXScale="1"
+        android:fromYScale="0"
+        android:toYScale="1"
+        android:pivotX="@fraction/menu_animation_pivot_x"
+        android:pivotY="5%"
+        android:duration="200" />
+    <alpha android:interpolator="@android:anim/linear_interpolator"
+        android:fromAlpha="0" android:toAlpha="1"
+        android:duration="200" />
+    <translate android:interpolator="@interpolator/transform_curve_interpolator"
+        android:fromYDelta="@dimen/menu_negative_software_vertical_offset"
+        android:toYDelta="0"
+        android:duration="200" />
+</set>
\ No newline at end of file
diff --git a/res/anim/menu_exit.xml b/res/anim/menu_exit.xml
new file mode 100644
index 0000000..5a2a93d
--- /dev/null
+++ b/res/anim/menu_exit.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
+    <scale android:interpolator="@interpolator/fade_out_curve_interpolator"
+        android:fromXScale="1"
+        android:toXScale="0.5"
+        android:fromYScale="1"
+        android:toYScale="0.5"
+        android:pivotX="@fraction/menu_animation_pivot_x"
+        android:pivotY="5%"
+        android:duration="150" />
+    <alpha android:interpolator="@interpolator/fade_out_curve_interpolator"
+        android:fromAlpha="1"
+        android:toAlpha="0"
+        android:duration="150" />
+</set>
\ No newline at end of file
diff --git a/res/interpolator/fade_out_curve_interpolator.xml b/res/interpolator/fade_out_curve_interpolator.xml
new file mode 100644
index 0000000..36cda25
--- /dev/null
+++ b/res/interpolator/fade_out_curve_interpolator.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/res/interpolator/transform_curve_interpolator.xml b/res/interpolator/transform_curve_interpolator.xml
new file mode 100644
index 0000000..a0da2ea
--- /dev/null
+++ b/res/interpolator/transform_curve_interpolator.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+
+<accelerateDecelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/res/layout/four_button_menu_item.xml b/res/layout/four_button_menu_item.xml
new file mode 100644
index 0000000..f1b2fd6
--- /dev/null
+++ b/res/layout/four_button_menu_item.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeightSmall"
+    android:layout_gravity="top|start"
+    android:orientation="horizontal">
+
+    <ImageButton
+        android:id="@+id/button_one"
+        android:layout_width="59dp"
+        android:layout_height="match_parent"
+        android:paddingEnd="11dp"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:scaleType="center" />
+
+    <ImageButton
+        android:id="@+id/button_two"
+        android:layout_width="70dp"
+        android:layout_height="match_parent"
+        android:paddingStart="11dp"
+        android:paddingEnd="11dp"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:scaleType="center" />
+
+    <ImageButton
+        android:id="@+id/button_three"
+        android:layout_width="70dp"
+        android:layout_height="match_parent"
+        android:paddingStart="11dp"
+        android:paddingEnd="11dp"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:scaleType="center" />
+
+    <ImageButton
+        android:id="@+id/button_four"
+        android:layout_width="59dp"
+        android:layout_height="match_parent"
+        android:paddingStart="11dp"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:scaleType="center" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/menu_item.xml b/res/layout/menu_item.xml
new file mode 100644
index 0000000..66bbb82
--- /dev/null
+++ b/res/layout/menu_item.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2011 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.
+-->
+<!-- Layout for each item in the menu popup -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:background="?android:attr/listChoiceBackgroundIndicator">
+    <TextView
+        android:id="@+id/menu_item_text"
+        android:textAppearance="?android:attr/textAppearanceLargePopupMenu"
+        android:layout_weight="1"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:gravity="center_vertical"
+        android:singleLine="true"
+        android:paddingEnd="9dp" />
+    <view
+        class="org.chromium.chrome.browser.appmenu.AppMenuItemIcon"
+        android:id="@+id/menu_item_icon"
+        android:layout_weight="0"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="end"
+        android:gravity="center_vertical" />
+</LinearLayout>
diff --git a/res/layout/three_button_menu_item.xml b/res/layout/three_button_menu_item.xml
new file mode 100644
index 0000000..5fb2d78
--- /dev/null
+++ b/res/layout/three_button_menu_item.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeightSmall"
+    android:layout_gravity="top|start"
+    android:orientation="horizontal">
+
+    <ImageButton
+        android:id="@+id/button_one"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:scaleType="center" />
+
+    <ImageButton
+        android:id="@+id/button_two"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:scaleType="center" />
+
+    <ImageButton
+        android:id="@+id/button_three"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="1"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:scaleType="center" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/title_button_menu_item.xml b/res/layout/title_button_menu_item.xml
new file mode 100644
index 0000000..15c3fe3
--- /dev/null
+++ b/res/layout/title_button_menu_item.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2014 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/listPreferredItemHeightSmall">
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:layout_gravity="start"
+        android:layout_weight="1"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:gravity="center_vertical"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:paddingEnd="9dp"
+        android:singleLine="true"
+        android:textAppearance="?android:attr/textAppearanceLargePopupMenu" />
+
+    <ImageButton
+        android:id="@+id/button"
+        android:layout_width="48dp"
+        android:layout_height="match_parent"
+        android:background="?android:attr/listChoiceBackgroundIndicator"
+        android:padding="10dp"
+        android:scaleType="fitCenter" />
+    
+</LinearLayout>
\ No newline at end of file
diff --git a/src/com/android/browser/appmenu/AppMenu.java b/src/com/android/browser/appmenu/AppMenu.java
new file mode 100644
index 0000000..3f3ac68
--- /dev/null
+++ b/src/com/android/browser/appmenu/AppMenu.java
@@ -0,0 +1,347 @@
+// Copyright 2011 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.animation.Animator;
+import android.animation.AnimatorSet;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.Surface;
+import android.view.View;
+import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ImageButton;
+import android.widget.ListPopupWindow;
+import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+
+import org.chromium.base.SysUtils;
+import org.chromium.chrome.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shows a popup of menuitems anchored to a host view. When a item is selected we call
+ * Activity.onOptionsItemSelected with the appropriate MenuItem.
+ *   - Only visible MenuItems are shown.
+ *   - Disabled items are grayed out.
+ */
+public class AppMenu implements OnItemClickListener, OnKeyListener {
+    /** Whether or not to show the software menu button in the menu. */
+    private static final boolean SHOW_SW_MENU_BUTTON = true;
+
+    private static final float LAST_ITEM_SHOW_FRACTION = 0.5f;
+
+    private final Menu mMenu;
+    private final int mItemRowHeight;
+    private final int mItemDividerHeight;
+    private final int mVerticalFadeDistance;
+    private final int mNegativeSoftwareVerticalOffset;
+    private ListPopupWindow mPopup;
+    private AppMenuAdapter mAdapter;
+    private AppMenuHandler mHandler;
+    private int mCurrentScreenRotation = -1;
+    private boolean mIsByHardwareButton;
+
+    /**
+     * Creates and sets up the App Menu.
+     * @param menu Original menu created by the framework.
+     * @param itemRowHeight Desired height for each app menu row.
+     * @param itemDividerHeight Desired height for the divider between app menu items.
+     * @param handler AppMenuHandler receives callbacks from AppMenu.
+     * @param res Resources object used to get dimensions and style attributes.
+     */
+    AppMenu(Menu menu, int itemRowHeight, int itemDividerHeight, AppMenuHandler handler,
+            Resources res) {
+        mMenu = menu;
+
+        mItemRowHeight = itemRowHeight;
+        assert mItemRowHeight > 0;
+
+        mHandler = handler;
+
+        mItemDividerHeight = itemDividerHeight;
+        assert mItemDividerHeight >= 0;
+
+        mNegativeSoftwareVerticalOffset =
+                res.getDimensionPixelSize(R.dimen.menu_negative_software_vertical_offset);
+        mVerticalFadeDistance = res.getDimensionPixelSize(R.dimen.menu_vertical_fade_distance);
+    }
+
+    /**
+     * Creates and shows the app menu anchored to the specified view.
+     *
+     * @param context             The context of the AppMenu (ensure the proper theme is set on
+     *                            this context).
+     * @param anchorView          The anchor {@link View} of the {@link ListPopupWindow}.
+     * @param isByHardwareButton  Whether or not hardware button triggered it. (oppose to software
+     *                            button)
+     * @param screenRotation      Current device screen rotation.
+     * @param visibleDisplayFrame The display area rect in which AppMenu is supposed to fit in.
+     * @param screenHeight        Current device screen height.
+     */
+    void show(Context context, View anchorView, boolean isByHardwareButton, int screenRotation,
+            Rect visibleDisplayFrame, int screenHeight) {
+        mPopup = new ListPopupWindow(context, null, android.R.attr.popupMenuStyle);
+        mPopup.setModal(true);
+        mPopup.setAnchorView(anchorView);
+        mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+        mPopup.setOnDismissListener(new OnDismissListener() {
+            @Override
+            public void onDismiss() {
+                if (mPopup.getAnchorView() instanceof ImageButton) {
+                    ((ImageButton) mPopup.getAnchorView()).setSelected(false);
+                }
+                mHandler.onMenuVisibilityChanged(false);
+            }
+        });
+
+        // Some OEMs don't actually let us change the background... but they still return the
+        // padding of the new background, which breaks the menu height.  If we still have a
+        // drawable here even though our style says @null we should use this padding instead...
+        Drawable originalBgDrawable = mPopup.getBackground();
+
+        // Need to explicitly set the background here.  Relying on it being set in the style caused
+        // an incorrectly drawn background.
+        if (isByHardwareButton) {
+            mPopup.setBackgroundDrawable(context.getResources().getDrawable(R.drawable.menu_bg));
+        } else {
+            mPopup.setBackgroundDrawable(
+                    context.getResources().getDrawable(R.drawable.edge_menu_bg));
+            mPopup.setAnimationStyle(R.style.OverflowMenuAnim);
+        }
+
+        // Turn off window animations for low end devices.
+        if (SysUtils.isLowEndDevice()) mPopup.setAnimationStyle(0);
+
+        Rect bgPadding = new Rect();
+        mPopup.getBackground().getPadding(bgPadding);
+
+        int popupWidth = context.getResources().getDimensionPixelSize(R.dimen.menu_width) +
+                bgPadding.left + bgPadding.right;
+
+        mPopup.setWidth(popupWidth);
+
+        mCurrentScreenRotation = screenRotation;
+        mIsByHardwareButton = isByHardwareButton;
+
+        // Extract visible items from the Menu.
+        int numItems = mMenu.size();
+        List<MenuItem> menuItems = new ArrayList<MenuItem>();
+        for (int i = 0; i < numItems; ++i) {
+            MenuItem item = mMenu.getItem(i);
+            if (item.isVisible()) {
+                menuItems.add(item);
+            }
+        }
+
+        Rect sizingPadding = new Rect(bgPadding);
+        if (isByHardwareButton && originalBgDrawable != null) {
+            Rect originalPadding = new Rect();
+            originalBgDrawable.getPadding(originalPadding);
+            sizingPadding.top = originalPadding.top;
+            sizingPadding.bottom = originalPadding.bottom;
+        }
+
+        boolean showMenuButton = !mIsByHardwareButton;
+        if (!SHOW_SW_MENU_BUTTON) showMenuButton = false;
+        // A List adapter for visible items in the Menu. The first row is added as a header to the
+        // list view.
+        mAdapter = new AppMenuAdapter(
+                this, menuItems, LayoutInflater.from(context), showMenuButton);
+        mPopup.setAdapter(mAdapter);
+
+        setMenuHeight(menuItems.size(), visibleDisplayFrame, screenHeight, sizingPadding);
+        setPopupOffset(mPopup, mCurrentScreenRotation, visibleDisplayFrame, sizingPadding);
+        mPopup.setOnItemClickListener(this);
+        mPopup.show();
+        mPopup.getListView().setItemsCanFocus(true);
+        mPopup.getListView().setOnKeyListener(this);
+
+        mHandler.onMenuVisibilityChanged(true);
+
+        if (mVerticalFadeDistance > 0) {
+            mPopup.getListView().setVerticalFadingEdgeEnabled(true);
+            mPopup.getListView().setFadingEdgeLength(mVerticalFadeDistance);
+        }
+
+        // Don't animate the menu items for low end devices.
+        if (!SysUtils.isLowEndDevice()) {
+            mPopup.getListView().addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    mPopup.getListView().removeOnLayoutChangeListener(this);
+                    runMenuItemEnterAnimations();
+                }
+            });
+        }
+    }
+
+    private void setPopupOffset(
+            ListPopupWindow popup, int screenRotation, Rect appRect, Rect padding) {
+        int[] anchorLocation = new int[2];
+        popup.getAnchorView().getLocationInWindow(anchorLocation);
+        int anchorHeight = popup.getAnchorView().getHeight();
+
+        // If we have a hardware menu button, locate the app menu closer to the estimated
+        // hardware menu button location.
+        if (mIsByHardwareButton) {
+            int horizontalOffset = -anchorLocation[0];
+            switch (screenRotation) {
+                case Surface.ROTATION_0:
+                case Surface.ROTATION_180:
+                    horizontalOffset += (appRect.width() - mPopup.getWidth()) / 2;
+                    break;
+                case Surface.ROTATION_90:
+                    horizontalOffset += appRect.width() - mPopup.getWidth();
+                    break;
+                case Surface.ROTATION_270:
+                    break;
+                default:
+                    assert false;
+                    break;
+            }
+            popup.setHorizontalOffset(horizontalOffset);
+            // The menu is displayed above the anchored view, so shift the menu up by the bottom
+            // padding of the background.
+            popup.setVerticalOffset(-padding.bottom);
+        } else {
+            // The menu is displayed over and below the anchored view, so shift the menu up by the
+            // height of the anchor view.
+            popup.setVerticalOffset(-mNegativeSoftwareVerticalOffset - anchorHeight);
+        }
+    }
+
+    /**
+     * Handles clicks on the AppMenu popup.
+     * @param menuItem The menu item in the popup that was clicked.
+     */
+    void onItemClick(MenuItem menuItem) {
+        if (menuItem.isEnabled()) {
+            dismiss();
+            mHandler.onOptionsItemSelected(menuItem);
+        }
+    }
+
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        onItemClick(mAdapter.getItem(position));
+    }
+
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (mPopup == null || mPopup.getListView() == null) return false;
+
+        if (event.getKeyCode() == KeyEvent.KEYCODE_MENU) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+                event.startTracking();
+                v.getKeyDispatcherState().startTracking(event, this);
+                return true;
+            } else if (event.getAction() == KeyEvent.ACTION_UP) {
+                v.getKeyDispatcherState().handleUpEvent(event);
+                if (event.isTracking() && !event.isCanceled()) {
+                    dismiss();
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Dismisses the app menu and cancels the drag-to-scroll if it is taking place.
+     */
+    void dismiss() {
+        mHandler.appMenuDismissed();
+        if (isShowing()) {
+            mPopup.dismiss();
+        }
+    }
+
+    /**
+     * @return Whether the app menu is currently showing.
+     */
+    boolean isShowing() {
+        if (mPopup == null) {
+            return false;
+        }
+        return mPopup.isShowing();
+    }
+
+    /**
+     * @return ListPopupWindow that displays all the menu options.
+     */
+    ListPopupWindow getPopup() {
+        return mPopup;
+    }
+
+    private void setMenuHeight(
+            int numMenuItems, Rect appDimensions, int screenHeight, Rect padding) {
+        assert mPopup.getAnchorView() != null;
+        View anchorView = mPopup.getAnchorView();
+        int[] anchorViewLocation = new int[2];
+        anchorView.getLocationOnScreen(anchorViewLocation);
+        anchorViewLocation[1] -= appDimensions.top;
+        int anchorViewImpactHeight = mIsByHardwareButton ? anchorView.getHeight() : 0;
+
+        // Set appDimensions.height() for abnormal anchorViewLocation.
+        if (anchorViewLocation[1] > screenHeight) {
+            anchorViewLocation[1] = appDimensions.height();
+        }
+        int availableScreenSpace = Math.max(anchorViewLocation[1],
+                appDimensions.height() - anchorViewLocation[1] - anchorViewImpactHeight);
+
+        availableScreenSpace -= padding.bottom;
+        if (mIsByHardwareButton) availableScreenSpace -= padding.top;
+
+        int numCanFit = availableScreenSpace / (mItemRowHeight + mItemDividerHeight);
+
+        // Fade out the last item if we cannot fit all items.
+        if (numCanFit < numMenuItems) {
+            int spaceForFullItems = numCanFit * (mItemRowHeight + mItemDividerHeight);
+            int spaceForPartialItem = (int) (LAST_ITEM_SHOW_FRACTION * mItemRowHeight);
+            // Determine which item needs hiding.
+            if (spaceForFullItems + spaceForPartialItem < availableScreenSpace) {
+                mPopup.setHeight(spaceForFullItems + spaceForPartialItem +
+                        padding.top + padding.bottom);
+            } else {
+                mPopup.setHeight(spaceForFullItems - mItemRowHeight + spaceForPartialItem +
+                        padding.top + padding.bottom);
+            }
+        } else {
+            mPopup.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+    }
+
+    private void runMenuItemEnterAnimations() {
+        AnimatorSet animation = new AnimatorSet();
+        AnimatorSet.Builder builder = null;
+
+        ViewGroup list = mPopup.getListView();
+        for (int i = 0; i < list.getChildCount(); i++) {
+            View view = list.getChildAt(i);
+            Object animatorObject = view.getTag(R.id.menu_item_enter_anim_id);
+            if (animatorObject != null) {
+                if (builder == null) {
+                    builder = animation.play((Animator) animatorObject);
+                } else {
+                    builder.with((Animator) animatorObject);
+                }
+            }
+        }
+
+        animation.start();
+    }
+}
diff --git a/src/com/android/browser/appmenu/AppMenuAdapter.java b/src/com/android/browser/appmenu/AppMenuAdapter.java
new file mode 100644
index 0000000..2b60292
--- /dev/null
+++ b/src/com/android/browser/appmenu/AppMenuAdapter.java
@@ -0,0 +1,396 @@
+// Copyright 2014 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.chrome.R;
+import org.chromium.ui.base.LocalizationUtils;
+import org.chromium.ui.interpolators.BakedBezierInterpolator;
+
+import java.util.List;
+
+/**
+ * ListAdapter to customize the view of items in the list.
+ */
+class AppMenuAdapter extends BaseAdapter {
+    private static final int VIEW_TYPE_COUNT = 5;
+
+    /**
+     * Regular Android menu item that contains a title and an icon if icon is specified.
+     */
+    private static final int STANDARD_MENU_ITEM = 0;
+    /**
+     * Menu item that has two buttons, the first one is a title and the second one is an icon.
+     * It is different from the regular menu item because it contains two separate buttons.
+     */
+    private static final int TITLE_BUTTON_MENU_ITEM = 1;
+    /**
+     * Menu item that has three buttons. Every one of these buttons is displayed as an icon.
+     */
+    private static final int THREE_BUTTON_MENU_ITEM = 2;
+    /**
+     * Menu item that has four buttons. Every one of these buttons is displayed as an icon.
+     */
+    private static final int FOUR_BUTTON_MENU_ITEM = 3;
+    /**
+     * Menu item that has two buttons, the first one is a title and the second is a menu icon.
+     * This is similar to {@link #TITLE_BUTTON_MENU_ITEM} but has some slight layout differences.
+     */
+    private static final int MENU_BUTTON_MENU_ITEM = 4;
+
+    /** MenuItem Animation Constants */
+    private static final int ENTER_ITEM_DURATION_MS = 350;
+    private static final int ENTER_ITEM_BASE_DELAY_MS = 80;
+    private static final int ENTER_ITEM_ADDL_DELAY_MS = 30;
+    private static final float ENTER_STANDARD_ITEM_OFFSET_Y_DP = -10.f;
+    private static final float ENTER_STANDARD_ITEM_OFFSET_X_DP = 10.f;
+
+    /** Menu Button Layout Constants */
+    private static final float MENU_BUTTON_WIDTH_DP = 59.f;
+    private static final float MENU_BUTTON_START_PADDING_DP = 21.f;
+
+    private final AppMenu mAppMenu;
+    private final LayoutInflater mInflater;
+    private final List<MenuItem> mMenuItems;
+    private final int mNumMenuItems;
+    private final boolean mShowMenuButton;
+    private final float mDpToPx;
+
+    public AppMenuAdapter(AppMenu appMenu, List<MenuItem> menuItems, LayoutInflater inflater,
+            boolean showMenuButton) {
+        mAppMenu = appMenu;
+        mMenuItems = menuItems;
+        mInflater = inflater;
+        mNumMenuItems = menuItems.size();
+        mShowMenuButton = showMenuButton;
+        mDpToPx = inflater.getContext().getResources().getDisplayMetrics().density;
+    }
+
+    @Override
+    public int getCount() {
+        return mNumMenuItems;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return VIEW_TYPE_COUNT;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        MenuItem item = getItem(position);
+        boolean hasMenuButton = mShowMenuButton && position == 0;
+        int viewCount = item.hasSubMenu() ? item.getSubMenu().size() : 1;
+        if (hasMenuButton) viewCount++;
+
+        if (viewCount == 4) {
+            return FOUR_BUTTON_MENU_ITEM;
+        } else if (viewCount == 3) {
+            return THREE_BUTTON_MENU_ITEM;
+        } else if (viewCount == 2) {
+            return hasMenuButton ? MENU_BUTTON_MENU_ITEM : TITLE_BUTTON_MENU_ITEM;
+        }
+        return STANDARD_MENU_ITEM;
+    }
+
+    @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) {
+        final boolean hasMenuButton = mShowMenuButton && position == 0;
+        final MenuItem item = getItem(position);
+        switch (getItemViewType(position)) {
+            case STANDARD_MENU_ITEM: {
+                StandardMenuItemViewHolder holder = null;
+                if (convertView == null) {
+                    holder = new StandardMenuItemViewHolder();
+                    convertView = mInflater.inflate(R.layout.menu_item, parent, false);
+                    holder.text = (TextView) convertView.findViewById(R.id.menu_item_text);
+                    holder.image = (AppMenuItemIcon) convertView.findViewById(R.id.menu_item_icon);
+                    convertView.setTag(holder);
+                    convertView.setTag(R.id.menu_item_enter_anim_id,
+                            buildStandardItemEnterAnimator(convertView, position));
+                } else {
+                    holder = (StandardMenuItemViewHolder) convertView.getTag();
+                }
+
+                convertView.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        mAppMenu.onItemClick(item);
+                    }
+                });
+                // 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.
+                convertView.setEnabled(isEnabled);
+                break;
+            }
+            case THREE_BUTTON_MENU_ITEM: {
+                ThreeButtonMenuItemViewHolder holder = null;
+                if (convertView == null) {
+                    holder = new ThreeButtonMenuItemViewHolder();
+                    convertView = mInflater.inflate(R.layout.three_button_menu_item, parent, false);
+                    holder.buttons[0] = (ImageButton) convertView.findViewById(R.id.button_one);
+                    holder.buttons[1] = (ImageButton) convertView.findViewById(R.id.button_two);
+                    holder.buttons[2] = (ImageButton) convertView.findViewById(R.id.button_three);
+                    convertView.setTag(holder);
+                    convertView.setTag(R.id.menu_item_enter_anim_id,
+                            buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
+                } else {
+                    holder = (ThreeButtonMenuItemViewHolder) convertView.getTag();
+                }
+                setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
+                setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
+                if (hasMenuButton) {
+                    setupMenuButton(holder.buttons[3]);
+                } else {
+                    setupImageButton(holder.buttons[2], item.getSubMenu().getItem(2));
+                }
+
+                convertView.setFocusable(false);
+                convertView.setEnabled(false);
+                break;
+            }
+            case FOUR_BUTTON_MENU_ITEM: {
+                FourButtonMenuItemViewHolder holder = null;
+                if (convertView == null) {
+                    holder = new FourButtonMenuItemViewHolder();
+                    convertView = mInflater.inflate(R.layout.four_button_menu_item, parent, false);
+                    holder.buttons[0] = (ImageButton) convertView.findViewById(R.id.button_one);
+                    holder.buttons[1] = (ImageButton) convertView.findViewById(R.id.button_two);
+                    holder.buttons[2] = (ImageButton) convertView.findViewById(R.id.button_three);
+                    holder.buttons[3] = (ImageButton) convertView.findViewById(R.id.button_four);
+                    convertView.setTag(holder);
+                    convertView.setTag(R.id.menu_item_enter_anim_id,
+                            buildIconItemEnterAnimator(holder.buttons, hasMenuButton));
+                } else {
+                    holder = (FourButtonMenuItemViewHolder) convertView.getTag();
+                }
+                setupImageButton(holder.buttons[0], item.getSubMenu().getItem(0));
+                setupImageButton(holder.buttons[1], item.getSubMenu().getItem(1));
+                setupImageButton(holder.buttons[2], item.getSubMenu().getItem(2));
+                if (hasMenuButton) {
+                    setupMenuButton(holder.buttons[3]);
+                } else {
+                    setupImageButton(holder.buttons[3], item.getSubMenu().getItem(3));
+                }
+                convertView.setFocusable(false);
+                convertView.setEnabled(false);
+                break;
+            }
+            case TITLE_BUTTON_MENU_ITEM:
+                // Fall through.
+            case MENU_BUTTON_MENU_ITEM: {
+                TitleButtonMenuItemViewHolder holder = null;
+                if (convertView == null) {
+                    holder = new TitleButtonMenuItemViewHolder();
+                    convertView = mInflater.inflate(R.layout.title_button_menu_item, parent, false);
+                    holder.title = (TextView) convertView.findViewById(R.id.title);
+                    holder.button = (ImageButton) convertView.findViewById(R.id.button);
+
+                    View animatedView = hasMenuButton ? holder.title : convertView;
+
+                    convertView.setTag(holder);
+                    convertView.setTag(R.id.menu_item_enter_anim_id,
+                            buildStandardItemEnterAnimator(animatedView, position));
+                } else {
+                    holder = (TitleButtonMenuItemViewHolder) convertView.getTag();
+                }
+                final MenuItem titleItem = item.hasSubMenu() ? item.getSubMenu().getItem(0) : item;
+                holder.title.setText(titleItem.getTitle());
+                holder.title.setEnabled(titleItem.isEnabled());
+                holder.title.setFocusable(titleItem.isEnabled());
+                holder.title.setOnClickListener(new OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        mAppMenu.onItemClick(titleItem);
+                    }
+                });
+
+                if (hasMenuButton) {
+                    holder.button.setVisibility(View.VISIBLE);
+                    setupMenuButton(holder.button);
+                } else if (item.getSubMenu().getItem(1).getIcon() != null) {
+                    holder.button.setVisibility(View.VISIBLE);
+                    setupImageButton(holder.button, item.getSubMenu().getItem(1));
+                } else {
+                    holder.button.setVisibility(View.GONE);
+                }
+                convertView.setFocusable(false);
+                convertView.setEnabled(false);
+                break;
+            }
+            default:
+                assert false : "Unexpected MenuItem type";
+        }
+        return convertView;
+    }
+
+    private void setupImageButton(ImageButton button, final MenuItem item) {
+        // Store and recover the level of image as button.setimageDrawable
+        // resets drawable to default level.
+        int currentLevel = item.getIcon().getLevel();
+        button.setImageDrawable(item.getIcon());
+        item.getIcon().setLevel(currentLevel);
+        button.setContentDescription(item.getTitle());
+        button.setEnabled(item.isEnabled());
+        button.setFocusable(item.isEnabled());
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mAppMenu.onItemClick(item);
+            }
+        });
+    }
+
+    private void setupMenuButton(ImageButton button) {
+        button.setImageResource(R.drawable.btn_menu_pressed);
+        button.setContentDescription(button.getResources().getString(R.string.menu_dismiss_btn));
+        button.setEnabled(true);
+        button.setFocusable(true);
+        button.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mAppMenu.dismiss();
+            }
+        });
+
+        // Set the button layout to make it properly line up with any underlying menu button
+        ApiCompatibilityUtils.setPaddingRelative(
+                button, (int) (MENU_BUTTON_START_PADDING_DP * mDpToPx), 0, 0, 0);
+        button.getLayoutParams().width = (int) (MENU_BUTTON_WIDTH_DP * mDpToPx);
+        button.setScaleType(ScaleType.CENTER);
+    }
+
+    /**
+     * This builds an {@link Animator} for the enter animation of a standard menu item.  This means
+     * it will animate the alpha from 0 to 1 and translate the view from -10dp to 0dp on the y axis.
+     *
+     * @param view     The menu item {@link View} to be animated.
+     * @param position The position in the menu.  This impacts the start delay of the animation.
+     * @return         The {@link Animator}.
+     */
+    private Animator buildStandardItemEnterAnimator(final View view, int position) {
+        final float offsetYPx = ENTER_STANDARD_ITEM_OFFSET_Y_DP * mDpToPx;
+        final int startDelay = ENTER_ITEM_BASE_DELAY_MS + ENTER_ITEM_ADDL_DELAY_MS * position;
+
+        AnimatorSet animation = new AnimatorSet();
+        animation.playTogether(
+                ObjectAnimator.ofFloat(view, View.ALPHA, 0.f, 1.f),
+                ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, offsetYPx, 0.f));
+        animation.setDuration(ENTER_ITEM_DURATION_MS);
+        animation.setStartDelay(startDelay);
+        animation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
+
+        animation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                view.setAlpha(0.f);
+            }
+        });
+        return animation;
+    }
+
+    /**
+     * This builds an {@link Animator} for the enter animation of icon row menu items.  This means
+     * it will animate the alpha from 0 to 1 and translate the views from 10dp to 0dp on the x axis.
+     *
+     * @param views        The list if icons in the menu item that should be animated.
+     * @param skipLastItem Whether or not the last item should be animated or not.
+     * @return             The {@link Animator}.
+     */
+    private Animator buildIconItemEnterAnimator(final ImageView[] views, boolean skipLastItem) {
+        final boolean rtl = LocalizationUtils.isLayoutRtl();
+        final float offsetXPx = ENTER_STANDARD_ITEM_OFFSET_X_DP * mDpToPx * (rtl ? -1.f : 1.f);
+        final int maxViewsToAnimate = views.length - (skipLastItem ? 1 : 0);
+
+        AnimatorSet animation = new AnimatorSet();
+        AnimatorSet.Builder builder = null;
+        for (int i = 0; i < maxViewsToAnimate; i++) {
+            final int startDelay = ENTER_ITEM_ADDL_DELAY_MS * i;
+
+            Animator alpha = ObjectAnimator.ofFloat(views[i], View.ALPHA, 0.f, 1.f);
+            Animator translate = ObjectAnimator.ofFloat(views[i], View.TRANSLATION_X, offsetXPx, 0);
+            alpha.setStartDelay(startDelay);
+            translate.setStartDelay(startDelay);
+            alpha.setDuration(ENTER_ITEM_DURATION_MS);
+            translate.setDuration(ENTER_ITEM_DURATION_MS);
+
+            if (builder == null) {
+                builder = animation.play(alpha);
+            } else {
+                builder.with(alpha);
+            }
+            builder.with(translate);
+        }
+        animation.setStartDelay(ENTER_ITEM_BASE_DELAY_MS);
+        animation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
+
+        animation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                for (int i = 0; i < maxViewsToAnimate; i++) {
+                    views[i].setAlpha(0.f);
+                }
+            }
+        });
+        return animation;
+    }
+
+    static class StandardMenuItemViewHolder {
+        public TextView text;
+        public AppMenuItemIcon image;
+    }
+
+    static class ThreeButtonMenuItemViewHolder {
+        public ImageButton[] buttons = new ImageButton[3];
+    }
+
+    static class FourButtonMenuItemViewHolder {
+        public ImageButton[] buttons = new ImageButton[4];
+    }
+
+    static class TitleButtonMenuItemViewHolder {
+        public TextView title;
+        public ImageButton button;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/browser/appmenu/AppMenuButtonHelper.java b/src/com/android/browser/appmenu/AppMenuButtonHelper.java
new file mode 100644
index 0000000..a63e2a5
--- /dev/null
+++ b/src/com/android/browser/appmenu/AppMenuButtonHelper.java
@@ -0,0 +1,106 @@
+// 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.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnTouchListener;
+
+import org.chromium.chrome.browser.UmaBridge;
+
+/**
+ * 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 implements OnTouchListener {
+    private final View mMenuButton;
+    private final AppMenuHandler mMenuHandler;
+    private Runnable mOnAppMenuShownListener;
+
+    /**
+     * @param menuButton  Menu button instance that will trigger the app menu.
+     * @param menuHandler MenuHandler implementation that can show and get the app menu.
+     */
+    public AppMenuButtonHelper(View menuButton, AppMenuHandler menuHandler) {
+        mMenuButton = menuButton;
+        mMenuHandler = menuHandler;
+    }
+
+    /**
+     * @param onAppMenuShownListener This is called when the app menu is shown by this class.
+     */
+    public void setOnAppMenuShownListener(Runnable onAppMenuShownListener) {
+        mOnAppMenuShownListener = onAppMenuShownListener;
+    }
+
+    /**
+     * Shows the app menu if it is not already shown.
+     * @param startDragging Whether dragging is started.
+     * @return Whether or not if the app menu is successfully shown.
+     */
+    private boolean showAppMenu(boolean startDragging) {
+        if (!mMenuHandler.isAppMenuShowing() &&
+                mMenuHandler.showAppMenu(mMenuButton, false, startDragging)) {
+            // 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) UmaBridge.usingMenu(false, false);
+
+            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 mMenuButton.isPressed() || mMenuHandler.isAppMenuShowing();
+    }
+
+    /**
+     * Handle the key press event on a menu button.
+     * @return Whether the app menu was shown as a result of this action.
+     */
+    public boolean onEnterKeyPress() {
+        return showAppMenu(false);
+    }
+
+    @Override
+
+    public boolean onTouch(View view, MotionEvent event) {
+        boolean isTouchEventConsumed = false;
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                isTouchEventConsumed |= true;
+                mMenuButton.setPressed(true);
+                showAppMenu(true);
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                isTouchEventConsumed |= true;
+                mMenuButton.setPressed(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);
+        }
+        return isTouchEventConsumed;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/browser/appmenu/AppMenuDragHelper.java b/src/com/android/browser/appmenu/AppMenuDragHelper.java
new file mode 100644
index 0000000..44e642a
--- /dev/null
+++ b/src/com/android/browser/appmenu/AppMenuDragHelper.java
@@ -0,0 +1,273 @@
+// Copyright 2014 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.animation.TimeAnimator;
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ListPopupWindow;
+import android.widget.ListView;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.UmaBridge;
+
+import java.util.ArrayList;
+
+/**
+ * Handles the drag touch events on AppMenu that start from the menu button.
+ *
+ * Lint suppression for NewApi is added because we are using TimeAnimator class that was marked
+ * hidden in API 16.
+ */
+@SuppressLint("NewApi")
+class AppMenuDragHelper {
+    private final Activity mActivity;
+    private final AppMenu mAppMenu;
+
+    // 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 static final float AUTO_SCROLL_AREA_MAX_RATIO = 0.25f;
+
+    // Dragging related variables, i.e., menu showing initiated by touch down and drag to navigate.
+    private final float mAutoScrollFullVelocity;
+    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 final int mItemRowHeight;
+    private boolean mIsSingleTapUpHappened;
+    GestureDetector mGestureSingleTapDetector;
+
+    // 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];
+
+    AppMenuDragHelper(Activity activity, AppMenu appMenu, int itemRowHeight) {
+        mActivity = activity;
+        mAppMenu = appMenu;
+        mItemRowHeight = itemRowHeight;
+        Resources res = mActivity.getResources();
+        mAutoScrollFullVelocity = res.getDimensionPixelSize(R.dimen.auto_scroll_full_velocity);
+        // 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) {
+                ListPopupWindow popup = mAppMenu.getPopup();
+                if (popup == null || popup.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;
+                popup.getListView().smoothScrollBy(diff, 0);
+
+                // Force touch move event to highlight items correctly for the scrolled position.
+                if (!Float.isNaN(mLastTouchX) && !Float.isNaN(mLastTouchY)) {
+                    menuItemAction(Math.round(mLastTouchX), Math.round(mLastTouchY),
+                            ITEM_ACTION_HIGHLIGHT);
+                }
+            }
+        });
+        mGestureSingleTapDetector = new GestureDetector(activity, new SimpleOnGestureListener() {
+            @Override
+            public boolean onSingleTapUp(MotionEvent e) {
+                mIsSingleTapUpHappened = true;
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Sets up all the internal state to prepare for menu dragging.
+     * @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.
+     */
+    void onShow(boolean startDragging) {
+        mLastTouchX = Float.NaN;
+        mLastTouchY = Float.NaN;
+        mDragScrollOffset = 0.0f;
+        mDragScrollOffsetRounded = 0;
+        mDragScrollingVelocity = 0.0f;
+        mIsSingleTapUpHappened = false;
+
+        if (startDragging) mDragScrolling.start();
+    }
+
+    /**
+     * Dragging mode will be stopped by calling this function. Note that it will fall back to normal
+     * non-dragging mode.
+     */
+    void finishDragging() {
+        menuItemAction(0, 0, ITEM_ACTION_CLEAR_HIGHLIGHT_ALL);
+        mDragScrolling.cancel();
+    }
+
+    /**
+     * 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 (!mAppMenu.isShowing() || !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 = mAppMenu.getPopup().getListView();
+
+        mLastTouchX = rawX;
+        mLastTouchY = rawY;
+
+        if (eventActionMasked == MotionEvent.ACTION_CANCEL) {
+            mAppMenu.dismiss();
+            return true;
+        }
+
+        if (!mIsSingleTapUpHappened) {
+            mGestureSingleTapDetector.onTouchEvent(event);
+            if (mIsSingleTapUpHappened) {
+                UmaBridge.usingMenu(false, false);
+                finishDragging();
+            }
+        }
+
+        // After this line, drag scrolling is happening.
+        if (!mDragScrolling.isRunning()) return false;
+
+        boolean didPerformClick = false;
+        int itemAction = ITEM_ACTION_CLEAR_HIGHLIGHT_ALL;
+        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;
+        }
+        didPerformClick = menuItemAction(roundedRawX, roundedRawY, itemAction);
+
+        if (eventActionMasked == MotionEvent.ACTION_UP && !didPerformClick) {
+            UmaBridge.usingMenu(false, true);
+            mAppMenu.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 (normalizedY < autoScrollAreaRatio) {
+                    // Top
+                    mDragScrollingVelocity = (normalizedY / autoScrollAreaRatio - 1.0f)
+                            * mAutoScrollFullVelocity;
+                } else if (normalizedY > 1.0f - autoScrollAreaRatio) {
+                    // Bottom
+                    mDragScrollingVelocity = ((normalizedY - 1.0f) / autoScrollAreaRatio + 1.0f)
+                            * mAutoScrollFullVelocity;
+                } else {
+                    // Middle or not scrollable.
+                    mDragScrollingVelocity = 0.0f;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * 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).
+     */
+    private boolean menuItemAction(int screenX, int screenY, int action) {
+        ListView listView = mAppMenu.getPopup().getListView();
+
+        ArrayList<View> itemViews = new ArrayList<View>();
+        for (int i = 0; i < listView.getChildCount(); ++i) {
+            boolean hasImageButtons = false;
+            if (listView.getChildAt(i) instanceof LinearLayout) {
+                LinearLayout layout = (LinearLayout) listView.getChildAt(i);
+                for (int j = 0; j < layout.getChildCount(); ++j) {
+                    itemViews.add(layout.getChildAt(j));
+                    if (layout.getChildAt(j) instanceof ImageButton) hasImageButtons = true;
+                }
+            }
+            if (!hasImageButtons) itemViews.add(listView.getChildAt(i));
+        }
+
+        boolean didPerformClick = false;
+        for (int i = 0; i < itemViews.size(); ++i) {
+            View itemView = itemViews.get(i);
+
+            boolean shouldPerform = itemView.isEnabled() && itemView.isShown() &&
+                    getScreenVisibleRect(itemView).contains(screenX, screenY);
+
+            switch (action) {
+                case ITEM_ACTION_HIGHLIGHT:
+                    itemView.setPressed(shouldPerform);
+                    break;
+                case ITEM_ACTION_PERFORM:
+                    if (shouldPerform) {
+                        UmaBridge.usingMenu(false, true);
+                        itemView.performClick();
+                        didPerformClick = true;
+                    }
+                    break;
+                case ITEM_ACTION_CLEAR_HIGHLIGHT_ALL:
+                    itemView.setPressed(false);
+                    break;
+                default:
+                    assert false;
+                    break;
+            }
+        }
+        return didPerformClick;
+    }
+
+    /**
+     * @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;
+    }
+}
diff --git a/src/com/android/browser/appmenu/AppMenuHandler.java b/src/com/android/browser/appmenu/AppMenuHandler.java
new file mode 100644
index 0000000..4e2c465
--- /dev/null
+++ b/src/com/android/browser/appmenu/AppMenuHandler.java
@@ -0,0 +1,174 @@
+// 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.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 com.google.common.annotations.VisibleForTesting;
+
+import org.chromium.chrome.browser.UmaBridge;
+
+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 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;
+    }
+
+    /**
+     * Show the app menu.
+     * @param anchorView         Anchor view (usually a menu button) to be used for the popup.
+     * @param isByHardwareButton True if 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 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.
+     */
+    public boolean showAppMenu(View anchorView, boolean isByHardwareButton, boolean startDragging) {
+        assert !(isByHardwareButton && startDragging);
+        if (!mDelegate.shouldShowAppMenu() || isAppMenuShowing()) return false;
+
+        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,
+                mDelegate.getMenuThemeResourceId());
+
+        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();
+        }
+        int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
+        Point pt = new Point();
+        mActivity.getWindowManager().getDefaultDisplay().getSize(pt);
+        mAppMenu.show(wrapper, anchorView, isByHardwareButton, rotation, appRect, pt.y);
+        mAppMenuDragHelper.onShow(startDragging);
+        UmaBridge.menuShow();
+        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.
+     */
+    @VisibleForTesting
+    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);
+        }
+    }
+}
diff --git a/src/com/android/browser/appmenu/AppMenuItemIcon.java b/src/com/android/browser/appmenu/AppMenuItemIcon.java
new file mode 100644
index 0000000..eddcc0d
--- /dev/null
+++ b/src/com/android/browser/appmenu/AppMenuItemIcon.java
@@ -0,0 +1,46 @@
+// Copyright 2014 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.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * A menu icon that supports the checkable state.
+ */
+class AppMenuItemIcon extends ImageView {
+    private static final int[] CHECKED_STATE_SET = new int[] {android.R.attr.state_checked};
+    private boolean mCheckedState;
+
+    public AppMenuItemIcon(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;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/browser/appmenu/AppMenuObserver.java b/src/com/android/browser/appmenu/AppMenuObserver.java
new file mode 100644
index 0000000..c133bc3
--- /dev/null
+++ b/src/com/android/browser/appmenu/AppMenuObserver.java
@@ -0,0 +1,16 @@
+// 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;
+
+/**
+ * Allows monitoring of application menu actions.
+ */
+public interface AppMenuObserver {
+    /**
+     * Informs when the App Menu visibility changes.
+     * @param isVisible Whether the menu is now visible.
+     */
+    public void onMenuVisibilityChanged(boolean isVisible);
+}
diff --git a/src/com/android/browser/appmenu/AppMenuPropertiesDelegate.java b/src/com/android/browser/appmenu/AppMenuPropertiesDelegate.java
new file mode 100644
index 0000000..00dcdfc
--- /dev/null
+++ b/src/com/android/browser/appmenu/AppMenuPropertiesDelegate.java
@@ -0,0 +1,29 @@
+// 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.view.Menu;
+
+/**
+ * Interface for the App Handler to query the desired state of the App Menu.
+ */
+public interface AppMenuPropertiesDelegate {
+
+    /**
+     * @return Whether the App Menu should be shown.
+     */
+    boolean shouldShowAppMenu();
+
+    /**
+     * Allows the delegate to show and hide items before the App Menu is shown.
+     * @param mMenu Menu that will be used as the source for the App Menu pop up.
+     */
+    void prepareMenu(Menu mMenu);
+
+    /**
+     * @return The theme resource to use for displaying the App Menu.
+     */
+    int getMenuThemeResourceId();
+}
diff --git a/src/com/android/browser/appmenu/OWNERS b/src/com/android/browser/appmenu/OWNERS
new file mode 100644
index 0000000..99f087e
--- /dev/null
+++ b/src/com/android/browser/appmenu/OWNERS
@@ -0,0 +1,2 @@
+aurimas@chromium.org
+kkimlabs@chromium.org