Android mouse events shouldn't appear as TouchEvents

Android low-level mouse events in Chrome used to follow the
"default" MotionEvent path which is designed for touches. In
particular in Blink, mouse events appeared as PlatformTouchEvents
which had some bad side-effects:

A. TouchEvents were fired for mouse.

B. Mouse events were suppressed through prevent-defaulting touch
  events.

C. Mouse event suppression thru canceled touches apply only to
  non-hovering mouse.

This CL fixes the mouse issues by routing Android mouse events
through a dedicated Web/PlatformMouseEvent path. The stylus
properties are also plumbed, to make Android stylus support
easier (follow-up CL).

Design doc: https://docs.google.com/document/d/1mpBR7J7kgTXvp0QACVjhxtwNJ7bgGoTMmxfxN2dupGg/edit?usp=sharing

BUG=468806,587550

Review-Url: https://codereview.chromium.org/2054193002
Cr-Commit-Position: refs/heads/master@{#431571}
diff --git a/blimp/client/app/android/blimp_view.cc b/blimp/client/app/android/blimp_view.cc
index 3c59192..5e447f2 100644
--- a/blimp/client/app/android/blimp_view.cc
+++ b/blimp/client/app/android/blimp_view.cc
@@ -206,8 +206,8 @@
                                android_meta_state,
                                raw_pos_x - pos_x_0,
                                raw_pos_y - pos_y_0,
-                               pointer0,
-                               pointer1);
+                               &pointer0,
+                               &pointer1);
 
   return document_manager_->OnTouchEvent(event);
 }
diff --git a/blimp/client/core/contents/android/blimp_view.cc b/blimp/client/core/contents/android/blimp_view.cc
index d7e0fd0..16eeb557 100644
--- a/blimp/client/core/contents/android/blimp_view.cc
+++ b/blimp/client/core/contents/android/blimp_view.cc
@@ -91,7 +91,7 @@
       1.f / device_scale_factor_dp_to_px, env, motion_event, time_ms,
       android_action, pointer_count, history_size, action_index,
       android_button_state, android_meta_state, raw_pos_x - pos_x_0,
-      raw_pos_y - pos_y_0, pointer0, pointer1);
+      raw_pos_y - pos_y_0, &pointer0, &pointer1);
   return blimp_contents_view_->OnTouchEvent(event);
 }
 
diff --git a/content/browser/android/content_view_core_impl.cc b/content/browser/android/content_view_core_impl.cc
index e11ef50..e55405f 100644
--- a/content/browser/android/content_view_core_impl.cc
+++ b/content/browser/android/content_view_core_impl.cc
@@ -61,6 +61,7 @@
 #include "ui/events/blink/blink_event_util.h"
 #include "ui/events/blink/web_input_event_traits.h"
 #include "ui/events/event_utils.h"
+#include "ui/events/gesture_detection/motion_event.h"
 #include "ui/gfx/android/java_bitmap.h"
 #include "ui/gfx/geometry/point_conversions.h"
 #include "ui/gfx/geometry/size_conversions.h"
@@ -954,31 +955,59 @@
                            android_meta_state,
                            raw_pos_x - pos_x_0,
                            raw_pos_y - pos_y_0,
-                           pointer0,
-                           pointer1);
+                           &pointer0,
+                           &pointer1);
 
   return is_touch_handle_event ? rwhv->OnTouchHandleEvent(event)
                                : rwhv->OnTouchEvent(event);
 }
 
-jboolean ContentViewCoreImpl::SendMouseMoveEvent(
+jboolean ContentViewCoreImpl::SendMouseEvent(
     JNIEnv* env,
     const JavaParamRef<jobject>& obj,
     jlong time_ms,
+    jint android_action,
     jfloat x,
     jfloat y,
-    jint tool_type) {
+    jint pointer_id,
+    jfloat pressure,
+    jfloat orientation,
+    jfloat tilt,
+    jint android_changed_button,
+    jint android_button_state,
+    jint android_meta_state,
+    jint android_tool_type) {
+
   RenderWidgetHostViewAndroid* rwhv = GetRenderWidgetHostViewAndroid();
   if (!rwhv)
     return false;
 
-  blink::WebMouseEvent event = WebMouseEventBuilder::Build(
-      WebInputEvent::MouseMove,
-      blink::WebMouseEvent::Button::NoButton,
-      time_ms / 1000.0, x / dpi_scale(), y / dpi_scale(), 0, 1,
-      ui::ToWebPointerType(static_cast<ui::MotionEvent::ToolType>(tool_type)));
+  // Construct a motion_event object minimally, only to convert the raw
+  // parameters to ui::MotionEvent values. Since we used only the cached values
+  // at index=0, it is okay to even pass a null event to the constructor.
+  ui::MotionEventAndroid::Pointer pointer0(
+      pointer_id, x, y, 0.0f /* touch_major */, 0.0f /* touch_minor */,
+      orientation, tilt, android_tool_type);
 
-  rwhv->SendMouseEvent(event);
+  ui::MotionEventAndroid motion_event(1.f / dpi_scale(),
+      env,
+      nullptr /* event */,
+      time_ms,
+      android_action,
+      1 /* pointer_count */,
+      0 /* history_size */,
+      0 /* action_index */,
+      android_button_state,
+      android_meta_state,
+      0 /* raw_offset_x_pixels */,
+      0 /* raw_offset_y_pixels */,
+      &pointer0,
+      nullptr);
+
+  // Note: This relies on identical button enum values in MotionEvent and
+  // MotionEventAndroid.
+  rwhv->SendMouseEvent(motion_event, android_changed_button);
+
   return true;
 }
 
diff --git a/content/browser/android/content_view_core_impl.h b/content/browser/android/content_view_core_impl.h
index 63d7786..454a698 100644
--- a/content/browser/android/content_view_core_impl.h
+++ b/content/browser/android/content_view_core_impl.h
@@ -122,12 +122,20 @@
       jint android_button_state,
       jint android_meta_state,
       jboolean is_touch_handle_event);
-  jboolean SendMouseMoveEvent(JNIEnv* env,
-                              const base::android::JavaParamRef<jobject>& obj,
-                              jlong time_ms,
-                              jfloat x,
-                              jfloat y,
-                              jint tool_type);
+  jboolean SendMouseEvent(JNIEnv* env,
+                          const base::android::JavaParamRef<jobject>& obj,
+                          jlong time_ms,
+                          jint android_action,
+                          jfloat x,
+                          jfloat y,
+                          jint pointer_id,
+                          jfloat pressure,
+                          jfloat orientation,
+                          jfloat tilt,
+                          jint android_changed_button,
+                          jint android_button_state,
+                          jint android_meta_state,
+                          jint tool_type);
   jboolean SendMouseWheelEvent(JNIEnv* env,
                                const base::android::JavaParamRef<jobject>& obj,
                                jlong time_ms,
diff --git a/content/browser/renderer_host/input/stylus_text_selector.cc b/content/browser/renderer_host/input/stylus_text_selector.cc
index 5af89606..a9bff878 100644
--- a/content/browser/renderer_host/input/stylus_text_selector.cc
+++ b/content/browser/renderer_host/input/stylus_text_selector.cc
@@ -89,6 +89,11 @@
     case MotionEvent::ACTION_POINTER_DOWN:
       break;
     case MotionEvent::ACTION_NONE:
+    case MotionEvent::ACTION_HOVER_ENTER:
+    case MotionEvent::ACTION_HOVER_EXIT:
+    case MotionEvent::ACTION_HOVER_MOVE:
+    case MotionEvent::ACTION_BUTTON_PRESS:
+    case MotionEvent::ACTION_BUTTON_RELEASE:
       NOTREACHED();
       break;
   }
diff --git a/content/browser/renderer_host/input/web_input_event_builders_android.cc b/content/browser/renderer_host/input/web_input_event_builders_android.cc
index 0166653..9e89311 100644
--- a/content/browser/renderer_host/input/web_input_event_builders_android.cc
+++ b/content/browser/renderer_host/input/web_input_event_builders_android.cc
@@ -112,32 +112,39 @@
 }
 
 WebMouseEvent WebMouseEventBuilder::Build(
-    WebInputEvent::Type type,
-    WebMouseEvent::Button button,
-    double time_sec,
-    int window_x,
-    int window_y,
-    int modifiers,
-    int click_count,
-    WebPointerProperties::PointerType pointer_type) {
+      WebInputEvent::Type type,
+      double time_sec,
+      int window_x,
+      int window_y,
+      int modifiers,
+      int click_count,
+      int pointer_id,
+      float pressure,
+      float orientation_rad,
+      float tilt_rad,
+      int changed_button,
+      int tool_type) {
 
   DCHECK(WebInputEvent::isMouseEventType(type));
   WebMouseEvent result;
 
   result.type = type;
-  result.pointerType = pointer_type;
   result.x = window_x;
   result.y = window_y;
   result.windowX = window_x;
   result.windowY = window_y;
   result.timeStampSeconds = time_sec;
   result.clickCount = click_count;
-  result.modifiers = modifiers;
+  result.modifiers = ui::EventFlagsToWebEventModifiers(modifiers);
 
-  if (type == WebInputEvent::MouseDown || type == WebInputEvent::MouseUp)
-    result.button = button;
-  else
-    result.button = WebMouseEvent::Button::NoButton;
+  ui::SetWebPointerPropertiesFromMotionEventData(
+      result,
+      pointer_id,
+      pressure,
+      orientation_rad,
+      tilt_rad,
+      changed_button,
+      tool_type);
 
   return result;
 }
diff --git a/content/browser/renderer_host/input/web_input_event_builders_android.h b/content/browser/renderer_host/input/web_input_event_builders_android.h
index d4394b0c..1d8e0bc 100644
--- a/content/browser/renderer_host/input/web_input_event_builders_android.h
+++ b/content/browser/renderer_host/input/web_input_event_builders_android.h
@@ -17,13 +17,17 @@
  public:
   static blink::WebMouseEvent Build(
       blink::WebInputEvent::Type type,
-      blink::WebMouseEvent::Button button,
       double time_sec,
       int window_x,
       int window_y,
       int modifiers,
       int click_count,
-      blink::WebPointerProperties::PointerType pointer_type);
+      int pointer_id,
+      float pressure,
+      float orientation_rad,
+      float tilt_rad,
+      int changed_button,
+      int tool_type);
 };
 
 class WebMouseWheelEventBuilder {
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 59d11e0..08ef4ae 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -82,6 +82,7 @@
 #include "ui/base/layout.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
+#include "ui/events/base_event_utils.h"
 #include "ui/events/blink/blink_event_util.h"
 #include "ui/events/blink/did_overscroll_params.h"
 #include "ui/events/blink/web_input_event_traits.h"
@@ -1654,9 +1655,27 @@
 }
 
 void RenderWidgetHostViewAndroid::SendMouseEvent(
-    const blink::WebMouseEvent& event) {
+    const ui::MotionEventAndroid& motion_event,
+    int changed_button) {
+  blink::WebInputEvent::Type webMouseEventType =
+      ui::ToWebMouseEventType(motion_event.GetAction());
+
+  blink::WebMouseEvent mouse_event = WebMouseEventBuilder::Build(
+      webMouseEventType,
+      ui::EventTimeStampToSeconds(motion_event.GetEventTime()),
+      motion_event.GetX(0),
+      motion_event.GetY(0),
+      motion_event.GetFlags(),
+      motion_event.GetButtonState() ? 1 : 0 /* click count */,
+      motion_event.GetPointerId(0),
+      motion_event.GetPressure(0),
+      motion_event.GetOrientation(0),
+      motion_event.GetTilt(0),
+      changed_button,
+      motion_event.GetToolType(0));
+
   if (host_)
-    host_->ForwardMouseEvent(event);
+    host_->ForwardMouseEvent(mouse_event);
 }
 
 void RenderWidgetHostViewAndroid::SendMouseWheelEvent(
diff --git a/content/browser/renderer_host/render_widget_host_view_android.h b/content/browser/renderer_host/render_widget_host_view_android.h
index e8754e0..0ac7715 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.h
+++ b/content/browser/renderer_host/render_widget_host_view_android.h
@@ -32,6 +32,7 @@
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/android/view_android.h"
 #include "ui/android/window_android_observer.h"
+#include "ui/events/android/motion_event_android.h"
 #include "ui/events/gesture_detection/filtered_gesture_provider.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/geometry/vector2d_f.h"
@@ -205,7 +206,7 @@
   void SetContentViewCore(ContentViewCoreImpl* content_view_core);
   SkColor GetCachedBackgroundColor() const;
   void SendKeyEvent(const NativeWebKeyboardEvent& event);
-  void SendMouseEvent(const blink::WebMouseEvent& event);
+  void SendMouseEvent(const ui::MotionEventAndroid&, int changed_button);
   void SendMouseWheelEvent(const blink::WebMouseWheelEvent& event);
   void SendGestureEvent(const blink::WebGestureEvent& event);
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
index 1d616eb..de98a5b 100644
--- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
+++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
@@ -1007,8 +1007,48 @@
      * @see View#onTouchEvent(MotionEvent)
      */
     public boolean onTouchEvent(MotionEvent event) {
+        // TODO(mustaq): Should we include MotionEvent.TOOL_TYPE_STYLUS here?
+        // crbug.com/592082
+        if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+            // Mouse button info is incomplete on L and below
+            int apiVersion = Build.VERSION.SDK_INT;
+            if (apiVersion >= android.os.Build.VERSION_CODES.M) {
+                return sendMouseEvent(event);
+            }
+        }
+
         final boolean isTouchHandleEvent = false;
-        return onTouchEventImpl(event, isTouchHandleEvent);
+        return sendTouchEvent(event, isTouchHandleEvent);
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private boolean sendMouseEvent(MotionEvent event) {
+        TraceEvent.begin("sendMouseEvent");
+
+        MotionEvent offsetEvent = createOffsetMotionEvent(event);
+        try {
+            mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
+            if (mNativeContentViewCore == 0) return false;
+
+            int eventAction = event.getActionMasked();
+
+            // For mousedown and mouseup events, we use ACTION_BUTTON_PRESS
+            // and ACTION_BUTTON_RELEASE respectively because they provide
+            // info about the changed-button.
+            if (eventAction == MotionEvent.ACTION_DOWN || eventAction == MotionEvent.ACTION_UP) {
+                return false;
+            }
+
+            nativeSendMouseEvent(mNativeContentViewCore, event.getEventTime(), eventAction,
+                    offsetEvent.getX(), offsetEvent.getY(), event.getPointerId(0),
+                    event.getPressure(0), event.getOrientation(0),
+                    event.getAxisValue(MotionEvent.AXIS_TILT, 0), event.getActionButton(),
+                    event.getButtonState(), event.getMetaState(), event.getToolType(0));
+            return true;
+        } finally {
+            offsetEvent.recycle();
+            TraceEvent.end("sendMouseEvent");
+        }
     }
 
     /**
@@ -1017,11 +1057,11 @@
      */
     public boolean onTouchHandleEvent(MotionEvent event) {
         final boolean isTouchHandleEvent = true;
-        return onTouchEventImpl(event, isTouchHandleEvent);
+        return sendTouchEvent(event, isTouchHandleEvent);
     }
 
-    private boolean onTouchEventImpl(MotionEvent event, boolean isTouchHandleEvent) {
-        TraceEvent.begin("onTouchEvent");
+    private boolean sendTouchEvent(MotionEvent event, boolean isTouchHandleEvent) {
+        TraceEvent.begin("sendTouchEvent");
         try {
             int eventAction = event.getActionMasked();
 
@@ -1080,7 +1120,7 @@
             if (offset != null) offset.recycle();
             return consumed;
         } finally {
-            TraceEvent.end("onTouchEvent");
+            TraceEvent.end("sendTouchEvent");
         }
     }
 
@@ -1555,18 +1595,22 @@
     public boolean onHoverEvent(MotionEvent event) {
         TraceEvent.begin("onHoverEvent");
 
+        int eventAction = event.getActionMasked();
+
+        // Ignore ACTION_HOVER_ENTER & ACTION_HOVER_EXIT: every mouse-down on
+        // Android follows a hover-exit and is followed by a hover-enter. The
+        // MotionEvent spec seems to support this behavior indirectly.
+        if (eventAction == MotionEvent.ACTION_HOVER_ENTER
+                || eventAction == MotionEvent.ACTION_HOVER_EXIT) {
+            return false;
+        }
+
         MotionEvent offset = createOffsetMotionEvent(event);
         try {
             if (mBrowserAccessibilityManager != null && !mIsObscuredByAnotherView) {
                 return mBrowserAccessibilityManager.onHoverEvent(offset);
             }
 
-            // Work around Android bug where the x, y coordinates of a hover exit
-            // event are incorrect when touch exploration is on.
-            if (mTouchExplorationEnabled && offset.getAction() == MotionEvent.ACTION_HOVER_EXIT) {
-                return true;
-            }
-
             // TODO(lanwei): Remove this switch once experimentation is complete -
             // crbug.com/418188
             if (event.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER) {
@@ -1579,8 +1623,11 @@
 
             mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
             if (mNativeContentViewCore != 0) {
-                nativeSendMouseMoveEvent(mNativeContentViewCore, offset.getEventTime(),
-                        offset.getX(), offset.getY(), event.getToolType(0));
+                nativeSendMouseEvent(mNativeContentViewCore, event.getEventTime(), eventAction,
+                        offset.getX(), offset.getY(), event.getPointerId(0), event.getPressure(0),
+                        event.getOrientation(0), event.getAxisValue(MotionEvent.AXIS_TILT, 0),
+                        0 /* changedButton */, event.getButtonState(), event.getMetaState(),
+                        event.getToolType(0));
             }
             return true;
         } finally {
@@ -1597,7 +1644,7 @@
         if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
             mLastFocalEventX = event.getX();
             mLastFocalEventY = event.getY();
-            switch (event.getAction()) {
+            switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_SCROLL:
                     if (mNativeContentViewCore == 0) return false;
 
@@ -1607,6 +1654,7 @@
                             event.getAxisValue(MotionEvent.AXIS_VSCROLL),
                             mRenderCoordinates.getWheelScrollFactor());
 
+                    // TODO(mustaq): Delete mFakeMouseMoveRunnable, see crbug.com/492738
                     mContainerView.removeCallbacks(mFakeMouseMoveRunnable);
                     // Send a delayed onMouseMove event so that we end
                     // up hovering over the right position after the scroll.
@@ -1620,6 +1668,13 @@
                     };
                     mContainerView.postDelayed(mFakeMouseMoveRunnable, 250);
                     return true;
+                case MotionEvent.ACTION_BUTTON_PRESS:
+                case MotionEvent.ACTION_BUTTON_RELEASE:
+                    // TODO(mustaq): Should we include MotionEvent.TOOL_TYPE_STYLUS here?
+                    // crbug.com/592082
+                    if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+                        return sendMouseEvent(event);
+                    }
             }
         } else if ((event.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
             if (mJoystickScrollProvider.onMotion(event)) return true;
@@ -2909,8 +2964,9 @@
             int androidButtonState, int androidMetaState,
             boolean isTouchHandleEvent);
 
-    private native int nativeSendMouseMoveEvent(
-            long nativeContentViewCoreImpl, long timeMs, float x, float y, int toolType);
+    private native int nativeSendMouseEvent(long nativeContentViewCoreImpl, long timeMs, int action,
+            float x, float y, int pointerId, float pressure, float orientaton, float tilt,
+            int changedButton, int buttonState, int metaState, int toolType);
 
     private native int nativeSendMouseWheelEvent(long nativeContentViewCoreImpl, long timeMs,
             float x, float y, float ticksX, float ticksY, float pixelsPerTick);
diff --git a/ui/events/android/motion_event_android.cc b/ui/events/android/motion_event_android.cc
index f7507bf9..ee645bf 100644
--- a/ui/events/android/motion_event_android.cc
+++ b/ui/events/android/motion_event_android.cc
@@ -34,9 +34,18 @@
       return MotionEventAndroid::ACTION_POINTER_DOWN;
     case ACTION_POINTER_UP:
       return MotionEventAndroid::ACTION_POINTER_UP;
+    case ACTION_HOVER_ENTER:
+      return MotionEventAndroid::ACTION_HOVER_ENTER;
+    case ACTION_HOVER_EXIT:
+      return MotionEventAndroid::ACTION_HOVER_EXIT;
+    case ACTION_HOVER_MOVE:
+      return MotionEventAndroid::ACTION_HOVER_MOVE;
+    case ACTION_BUTTON_PRESS:
+      return MotionEventAndroid::ACTION_BUTTON_PRESS;
+    case ACTION_BUTTON_RELEASE:
+      return MotionEventAndroid::ACTION_BUTTON_RELEASE;
     default:
-      NOTREACHED() << "Invalid Android MotionEvent type for gesture detection: "
-                   << android_action;
+      NOTREACHED() << "Invalid Android MotionEvent action: " << android_action;
   };
   return MotionEventAndroid::ACTION_CANCEL;
 }
@@ -79,8 +88,9 @@
   return result;
 }
 
-int FromAndroidMetaState(int meta_state) {
+int ToEventFlags(int meta_state, int button_state) {
   int flags = ui::EF_NONE;
+
   if ((meta_state & AMETA_SHIFT_ON) != 0)
     flags |= ui::EF_SHIFT_DOWN;
   if ((meta_state & AMETA_CTRL_ON) != 0)
@@ -91,6 +101,22 @@
     flags |= ui::EF_COMMAND_DOWN;
   if ((meta_state & AMETA_CAPS_LOCK_ON) != 0)
     flags |= ui::EF_CAPS_LOCK_ON;
+
+  if ((button_state & BUTTON_BACK) != 0)
+    flags |= ui::EF_BACK_MOUSE_BUTTON;
+  if ((button_state & BUTTON_FORWARD) != 0)
+    flags |= ui::EF_FORWARD_MOUSE_BUTTON;
+  if ((button_state & BUTTON_PRIMARY) != 0)
+    flags |= ui::EF_LEFT_MOUSE_BUTTON;
+  if ((button_state & BUTTON_SECONDARY) != 0)
+    flags |= ui::EF_RIGHT_MOUSE_BUTTON;
+  if ((button_state & BUTTON_TERTIARY) != 0)
+    flags |= ui::EF_MIDDLE_MOUSE_BUTTON;
+  if ((button_state & BUTTON_STYLUS_PRIMARY) != 0)
+    flags |= ui::EF_LEFT_MOUSE_BUTTON;
+  if ((button_state & BUTTON_STYLUS_SECONDARY) != 0)
+    flags |= ui::EF_RIGHT_MOUSE_BUTTON;
+
   return flags;
 }
 
@@ -163,11 +189,11 @@
                                        jint history_size,
                                        jint action_index,
                                        jint android_button_state,
-                                       jint meta_state,
+                                       jint android_meta_state,
                                        jfloat raw_offset_x_pixels,
                                        jfloat raw_offset_y_pixels,
-                                       const Pointer& pointer0,
-                                       const Pointer& pointer1)
+                                       const Pointer* const pointer0,
+                                       const Pointer* const pointer1)
     : pix_to_dip_(pix_to_dip),
       cached_time_(FromAndroidTime(time_ms)),
       cached_action_(FromAndroidAction(android_action)),
@@ -175,18 +201,20 @@
       cached_history_size_(ToValidHistorySize(history_size, cached_action_)),
       cached_action_index_(action_index),
       cached_button_state_(FromAndroidButtonState(android_button_state)),
-      cached_flags_(FromAndroidMetaState(meta_state)),
+      cached_flags_(ToEventFlags(android_meta_state, android_button_state)),
       cached_raw_position_offset_(ToDips(raw_offset_x_pixels),
                                   ToDips(raw_offset_y_pixels)),
       unique_event_id_(ui::GetNextTouchEventId()) {
-  DCHECK_GT(pointer_count, 0);
+  DCHECK_GT(cached_pointer_count_, 0U);
+  DCHECK(cached_pointer_count_ == 1 || pointer1);
 
   event_.Reset(env, event);
   if (cached_pointer_count_ > MAX_POINTERS_TO_CACHE || cached_history_size_ > 0)
     DCHECK(event_.obj());
 
-  cached_pointers_[0] = FromAndroidPointer(pointer0);
-  cached_pointers_[1] = FromAndroidPointer(pointer1);
+  cached_pointers_[0] = FromAndroidPointer(*pointer0);
+  if (cached_pointer_count_ > 1)
+    cached_pointers_[1] = FromAndroidPointer(*pointer1);
 }
 
 MotionEventAndroid::~MotionEventAndroid() {
diff --git a/ui/events/android/motion_event_android.h b/ui/events/android/motion_event_android.h
index f4d915e..00fc4216 100644
--- a/ui/events/android/motion_event_android.h
+++ b/ui/events/android/motion_event_android.h
@@ -1,4 +1,3 @@
-
 // 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.
@@ -59,8 +58,8 @@
                      jint meta_state,
                      jfloat raw_offset_x_pixels,
                      jfloat raw_offset_y_pixels,
-                     const Pointer& pointer0,
-                     const Pointer& pointer1);
+                     const Pointer* const pointer0,
+                     const Pointer* const pointer1);
   ~MotionEventAndroid() override;
 
   // ui::MotionEvent methods.
diff --git a/ui/events/android/motion_event_android_unittest.cc b/ui/events/android/motion_event_android_unittest.cc
index 82ec1c5..7e619b5 100644
--- a/ui/events/android/motion_event_android_unittest.cc
+++ b/ui/events/android/motion_event_android_unittest.cc
@@ -59,8 +59,8 @@
                            kAndroidAltKeyDown,
                            raw_offset,
                            -raw_offset,
-                           p0,
-                           p1);
+                           &p0,
+                           &p1);
 
   EXPECT_EQ(MotionEvent::ACTION_DOWN, event.GetAction());
   EXPECT_EQ(event_time, event.GetEventTime());
@@ -85,7 +85,7 @@
   EXPECT_EQ(MotionEvent::TOOL_TYPE_FINGER, event.GetToolType(0));
   EXPECT_EQ(MotionEvent::TOOL_TYPE_FINGER, event.GetToolType(1));
   EXPECT_EQ(MotionEvent::BUTTON_PRIMARY, event.GetButtonState());
-  EXPECT_EQ(ui::EF_ALT_DOWN, event.GetFlags());
+  EXPECT_EQ(ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON, event.GetFlags());
   EXPECT_EQ(static_cast<size_t>(pointer_count), event.GetPointerCount());
   EXPECT_EQ(static_cast<size_t>(history_size), event.GetHistorySize());
 }
@@ -94,7 +94,6 @@
   const int pointer_count = 1;
   MotionEventAndroid::Pointer p0(
       1, 13.7f, -7.13f, 5.3f, 1.2f, 0.1f, 0.2f, kAndroidToolTypeFinger);
-  MotionEventAndroid::Pointer p1(0, 0, 0, 0, 0, 0, 0, 0);
   MotionEventAndroid event(kPixToDip,
                            base::android::AttachCurrentThread(),
                            nullptr,
@@ -107,8 +106,8 @@
                            0,
                            0,
                            0,
-                           p0,
-                           p1);
+                           &p0,
+                           nullptr);
 
   std::unique_ptr<MotionEvent> clone = event.Clone();
   EXPECT_EQ(ui::test::ToString(event), ui::test::ToString(*clone));
@@ -119,7 +118,6 @@
   const int pointer_count = 1;
   MotionEventAndroid::Pointer p0(
       1, 13.7f, -7.13f, 5.3f, 1.2f, 0.1f, 0.2f, kAndroidToolTypeFinger);
-  MotionEventAndroid::Pointer p1(0, 0, 0, 0, 0, 0, 0, 0);
   MotionEventAndroid event(kPixToDip,
                            base::android::AttachCurrentThread(),
                            nullptr,
@@ -132,8 +130,8 @@
                            0,
                            0,
                            0,
-                           p0,
-                           p1);
+                           &p0,
+                           nullptr);
 
   std::unique_ptr<MotionEvent> cancel_event = event.Cancel();
   EXPECT_EQ(MotionEvent::ACTION_CANCEL, cancel_event->GetAction());
@@ -165,8 +163,8 @@
                            0,
                            0,
                            0,
-                           p0,
-                           p1);
+                           &p0,
+                           &p1);
 
   EXPECT_EQ(0.f, event.GetOrientation(0));
   EXPECT_EQ(0.f, event.GetOrientation(1));
@@ -176,7 +174,6 @@
   int pointer_count = 1;
   size_t history_size = 5;
   MotionEventAndroid::Pointer p0(0, 0, 0, 0, 0, 0, 0, 0);
-  MotionEventAndroid::Pointer p1(0, 0, 0, 0, 0, 0, 0, 0);
   MotionEventAndroid event(kPixToDip,
                            base::android::AttachCurrentThread(),
                            nullptr,
@@ -189,8 +186,8 @@
                            0,
                            0,
                            0,
-                           p0,
-                           p1);
+                           &p0,
+                           nullptr);
 
   EXPECT_EQ(0U, event.GetHistorySize());
 }
@@ -215,8 +212,8 @@
                            0,
                            0,
                            0,
-                           p0,
-                           p1);
+                           &p0,
+                           &p1);
 
   EXPECT_EQ(MotionEvent::ACTION_POINTER_DOWN, event.GetAction());
   EXPECT_EQ(action_index, event.GetActionIndex());
diff --git a/ui/events/blink/blink_event_util.cc b/ui/events/blink/blink_event_util.cc
index c3ac973..f0bb124 100644
--- a/ui/events/blink/blink_event_util.cc
+++ b/ui/events/blink/blink_event_util.cc
@@ -33,7 +33,7 @@
 namespace ui {
 namespace {
 
-WebInputEvent::Type ToWebInputEventType(MotionEvent::Action action) {
+WebInputEvent::Type ToWebTouchEventType(MotionEvent::Action action) {
   switch (action) {
     case MotionEvent::ACTION_DOWN:
       return WebInputEvent::TouchStart;
@@ -48,10 +48,14 @@
     case MotionEvent::ACTION_POINTER_UP:
       return WebInputEvent::TouchEnd;
     case MotionEvent::ACTION_NONE:
-      NOTREACHED();
-      return WebInputEvent::Undefined;
+    case MotionEvent::ACTION_HOVER_ENTER:
+    case MotionEvent::ACTION_HOVER_EXIT:
+    case MotionEvent::ACTION_HOVER_MOVE:
+    case MotionEvent::ACTION_BUTTON_PRESS:
+    case MotionEvent::ACTION_BUTTON_RELEASE:
+      break;
   }
-  NOTREACHED() << "Invalid MotionEvent::Action.";
+  NOTREACHED() << "Invalid MotionEvent::Action = " << action;
   return WebInputEvent::Undefined;
 }
 
@@ -78,18 +82,63 @@
                  ? WebTouchPoint::StateReleased
                  : WebTouchPoint::StateStationary;
     case MotionEvent::ACTION_NONE:
-      NOTREACHED();
-      return WebTouchPoint::StateUndefined;
+    case MotionEvent::ACTION_HOVER_ENTER:
+    case MotionEvent::ACTION_HOVER_EXIT:
+    case MotionEvent::ACTION_HOVER_MOVE:
+    case MotionEvent::ACTION_BUTTON_PRESS:
+    case MotionEvent::ACTION_BUTTON_RELEASE:
+      break;
   }
   NOTREACHED() << "Invalid MotionEvent::Action.";
   return WebTouchPoint::StateUndefined;
 }
 
+WebPointerProperties::PointerType ToWebPointerType(int tool_type) {
+  switch (static_cast<MotionEvent::ToolType>(tool_type)) {
+    case MotionEvent::TOOL_TYPE_UNKNOWN:
+      return WebPointerProperties::PointerType::Unknown;
+    case MotionEvent::TOOL_TYPE_FINGER:
+      return WebPointerProperties::PointerType::Touch;
+    case MotionEvent::TOOL_TYPE_STYLUS:
+      return WebPointerProperties::PointerType::Pen;
+    case MotionEvent::TOOL_TYPE_MOUSE:
+      return WebPointerProperties::PointerType::Mouse;
+    case MotionEvent::TOOL_TYPE_ERASER:
+      return WebPointerProperties::PointerType::Eraser;
+  }
+  NOTREACHED() << "Invalid MotionEvent::ToolType = " << tool_type;
+  return WebPointerProperties::PointerType::Unknown;
+}
+
+WebPointerProperties::Button ToWebPointerButton(int android_button_state) {
+    if (android_button_state & MotionEvent::BUTTON_PRIMARY)
+        return WebPointerProperties::Button::Left;
+    else if (android_button_state & MotionEvent::BUTTON_SECONDARY)
+        return WebPointerProperties::Button::Right;
+    else if (android_button_state & MotionEvent::BUTTON_TERTIARY)
+        return WebPointerProperties::Button::Middle;
+    else if (android_button_state & MotionEvent::BUTTON_BACK)
+        return WebPointerProperties::Button::X1;
+    else if (android_button_state & MotionEvent::BUTTON_FORWARD)
+        return WebPointerProperties::Button::X2;
+    else if (android_button_state & MotionEvent::BUTTON_STYLUS_PRIMARY)
+        return WebPointerProperties::Button::Left;
+    else if (android_button_state & MotionEvent::BUTTON_STYLUS_SECONDARY)
+        return WebPointerProperties::Button::Right;
+    else
+        return WebPointerProperties::Button::NoButton;
+}
+
 WebTouchPoint CreateWebTouchPoint(const MotionEvent& event,
                                   size_t pointer_index) {
   WebTouchPoint touch;
-  touch.id = event.GetPointerId(pointer_index);
-  touch.pointerType = ToWebPointerType(event.GetToolType(pointer_index));
+
+  SetWebPointerPropertiesFromMotionEventData(
+      touch, event.GetPointerId(pointer_index),
+      event.GetPressure(pointer_index), event.GetOrientation(pointer_index),
+      event.GetTilt(pointer_index), 0 /* no button changed */,
+      event.GetToolType(pointer_index));
+
   touch.state = ToWebTouchPointState(event, pointer_index);
   touch.position.x = event.GetX(pointer_index);
   touch.position.y = event.GetY(pointer_index);
@@ -110,9 +159,8 @@
 
   float major_radius = event.GetTouchMajor(pointer_index) / 2.f;
   float minor_radius = event.GetTouchMinor(pointer_index) / 2.f;
+  float orientation_deg = event.GetOrientation(pointer_index) * 180.f / M_PI;
 
-  float orientation_rad = event.GetOrientation(pointer_index);
-  float orientation_deg = orientation_rad * 180.f / M_PI;
   DCHECK_GE(major_radius, 0);
   DCHECK_GE(minor_radius, 0);
   DCHECK_GE(major_radius, minor_radius);
@@ -143,20 +191,6 @@
     touch.rotationAngle = orientation_deg + 90;
   }
 
-  touch.force = event.GetPressure(pointer_index);
-
-  if (event.GetToolType(pointer_index) == MotionEvent::TOOL_TYPE_STYLUS) {
-    // A stylus points to a direction specified by orientation and tilts to
-    // the opposite direction. Coordinate system is left-handed.
-    float tilt_rad = event.GetTilt(pointer_index);
-    float r = sin(tilt_rad);
-    float z = cos(tilt_rad);
-    touch.tiltX = lround(atan2(sin(-orientation_rad) * r, z) * 180.f / M_PI);
-    touch.tiltY = lround(atan2(cos(-orientation_rad) * r, z) * 180.f / M_PI);
-  } else {
-    touch.tiltX = touch.tiltY = 0;
-  }
-
   return touch;
 }
 
@@ -171,13 +205,17 @@
 
   blink::WebTouchEvent result;
 
-  result.type = ToWebInputEventType(event.GetAction());
+  result.type = ToWebTouchEventType(event.GetAction());
   result.dispatchType = result.type == WebInputEvent::TouchCancel
                             ? WebInputEvent::EventNonBlocking
                             : WebInputEvent::Blocking;
   result.timeStampSeconds = ui::EventTimeStampToSeconds(event.GetEventTime());
   result.movedBeyondSlopRegion = moved_beyond_slop_region;
+
   result.modifiers = EventFlagsToWebEventModifiers(event.GetFlags());
+  // TODO(mustaq): MotionEvent flags seems unrelated, should use
+  // metaState instead?
+
   DCHECK_NE(event.GetUniqueEventId(), 0U);
   result.uniqueTouchEventId = event.GetUniqueEventId();
   result.touchesLength =
@@ -480,22 +518,58 @@
   return scaled_event;
 }
 
-WebPointerProperties::PointerType ToWebPointerType(
-    MotionEvent::ToolType tool_type) {
-  switch (tool_type) {
-    case MotionEvent::TOOL_TYPE_UNKNOWN:
-      return WebPointerProperties::PointerType::Unknown;
-    case MotionEvent::TOOL_TYPE_FINGER:
-      return WebPointerProperties::PointerType::Touch;
-    case MotionEvent::TOOL_TYPE_STYLUS:
-      return WebPointerProperties::PointerType::Pen;
-    case MotionEvent::TOOL_TYPE_MOUSE:
-      return WebPointerProperties::PointerType::Mouse;
-    case MotionEvent::TOOL_TYPE_ERASER:
-      return WebPointerProperties::PointerType::Eraser;
+WebInputEvent::Type ToWebMouseEventType(MotionEvent::Action action) {
+  switch (action) {
+    case MotionEvent::ACTION_DOWN:
+    case MotionEvent::ACTION_BUTTON_PRESS:
+      return WebInputEvent::MouseDown;
+    case MotionEvent::ACTION_MOVE:
+    case MotionEvent::ACTION_HOVER_MOVE:
+      return WebInputEvent::MouseMove;
+    case MotionEvent::ACTION_HOVER_ENTER:
+      return WebInputEvent::MouseEnter;
+    case MotionEvent::ACTION_HOVER_EXIT:
+      return WebInputEvent::MouseLeave;
+    case MotionEvent::ACTION_UP:
+    case MotionEvent::ACTION_BUTTON_RELEASE:
+      return WebInputEvent::MouseUp;
+    case MotionEvent::ACTION_NONE:
+    case MotionEvent::ACTION_CANCEL:
+    case MotionEvent::ACTION_POINTER_DOWN:
+    case MotionEvent::ACTION_POINTER_UP:
+      break;
   }
-  NOTREACHED() << "Invalid MotionEvent::ToolType = " << tool_type;
-  return WebPointerProperties::PointerType::Unknown;
+  NOTREACHED() << "Invalid MotionEvent::Action = " << action;
+  return WebInputEvent::Undefined;
+}
+
+void SetWebPointerPropertiesFromMotionEventData(
+    WebPointerProperties& webPointerProperties,
+    int pointer_id,
+    float pressure,
+    float orientation_rad,
+    float tilt_rad,
+    int android_buttons_changed,
+    int tool_type) {
+
+  webPointerProperties.id = pointer_id;
+  webPointerProperties.force = pressure;
+
+  if (tool_type == MotionEvent::TOOL_TYPE_STYLUS) {
+    // A stylus points to a direction specified by orientation and tilts to
+    // the opposite direction. Coordinate system is left-handed.
+    float r = sin(tilt_rad);
+    float z = cos(tilt_rad);
+    webPointerProperties.tiltX =
+        lround(atan2(sin(-orientation_rad) * r, z) * 180.f / M_PI);
+    webPointerProperties.tiltY =
+        lround(atan2(cos(-orientation_rad) * r, z) * 180.f / M_PI);
+  } else {
+    webPointerProperties.tiltX = webPointerProperties.tiltY = 0;
+  }
+
+  webPointerProperties.button = ToWebPointerButton(android_buttons_changed);
+  webPointerProperties.pointerType = ToWebPointerType(tool_type);
 }
 
 int WebEventModifiersToEventFlags(int modifiers) {
diff --git a/ui/events/blink/blink_event_util.h b/ui/events/blink/blink_event_util.h
index 247ce4f..c325290 100644
--- a/ui/events/blink/blink_event_util.h
+++ b/ui/events/blink/blink_event_util.h
@@ -62,8 +62,16 @@
     const gfx::Vector2d& delta,
     float scale);
 
-blink::WebPointerProperties::PointerType ToWebPointerType(
-    MotionEvent::ToolType tool_type);
+blink::WebInputEvent::Type ToWebMouseEventType(MotionEvent::Action action);
+
+void SetWebPointerPropertiesFromMotionEventData(
+    blink::WebPointerProperties& webPointerProperties,
+    int pointer_id,
+    float pressure,
+    float orientation_rad,
+    float tilt_rad,
+    int android_buttons_changed,
+    int tool_type);
 
 int WebEventModifiersToEventFlags(int modifiers);
 
diff --git a/ui/events/gesture_detection/gesture_detector.cc b/ui/events/gesture_detection/gesture_detector.cc
index d1e1c38..1d26d730 100644
--- a/ui/events/gesture_detection/gesture_detector.cc
+++ b/ui/events/gesture_detection/gesture_detector.cc
@@ -178,6 +178,11 @@
 
   switch (action) {
     case MotionEvent::ACTION_NONE:
+    case MotionEvent::ACTION_HOVER_ENTER:
+    case MotionEvent::ACTION_HOVER_EXIT:
+    case MotionEvent::ACTION_HOVER_MOVE:
+    case MotionEvent::ACTION_BUTTON_PRESS:
+    case MotionEvent::ACTION_BUTTON_RELEASE:
       NOTREACHED();
       return handled;
 
diff --git a/ui/events/gesture_detection/gesture_event_data_packet.cc b/ui/events/gesture_detection/gesture_event_data_packet.cc
index a24e4ed..9b3af05 100644
--- a/ui/events/gesture_detection/gesture_event_data_packet.cc
+++ b/ui/events/gesture_detection/gesture_event_data_packet.cc
@@ -26,6 +26,11 @@
     case ui::MotionEvent::ACTION_POINTER_UP:
       return GestureEventDataPacket::TOUCH_END;
     case ui::MotionEvent::ACTION_NONE:
+    case ui::MotionEvent::ACTION_HOVER_ENTER:
+    case ui::MotionEvent::ACTION_HOVER_EXIT:
+    case ui::MotionEvent::ACTION_HOVER_MOVE:
+    case ui::MotionEvent::ACTION_BUTTON_PRESS:
+    case ui::MotionEvent::ACTION_BUTTON_RELEASE:
       NOTREACHED();
       return GestureEventDataPacket::INVALID;
   };
diff --git a/ui/events/gesture_detection/gesture_provider.cc b/ui/events/gesture_detection/gesture_provider.cc
index b7cdb24..f5f2698 100644
--- a/ui/events/gesture_detection/gesture_provider.cc
+++ b/ui/events/gesture_detection/gesture_provider.cc
@@ -42,6 +42,16 @@
       return "ACTION_CANCEL";
     case MotionEvent::ACTION_MOVE:
       return "ACTION_MOVE";
+    case MotionEvent::ACTION_HOVER_ENTER:
+      return "ACTION_HOVER_ENTER";
+    case MotionEvent::ACTION_HOVER_EXIT:
+      return "ACTION_HOVER_EXIT";
+    case MotionEvent::ACTION_HOVER_MOVE:
+      return "ACTION_HOVER_MOVE";
+    case MotionEvent::ACTION_BUTTON_PRESS:
+      return "ACTION_BUTTON_PRESS";
+    case MotionEvent::ACTION_BUTTON_RELEASE:
+      return "ACTION_BUTTON_RELEASE";
   }
   return "";
 }
@@ -853,6 +863,11 @@
     case MotionEvent::ACTION_MOVE:
       break;
     case MotionEvent::ACTION_NONE:
+    case MotionEvent::ACTION_HOVER_ENTER:
+    case MotionEvent::ACTION_HOVER_EXIT:
+    case MotionEvent::ACTION_HOVER_MOVE:
+    case MotionEvent::ACTION_BUTTON_PRESS:
+    case MotionEvent::ACTION_BUTTON_RELEASE:
       NOTREACHED();
       break;
   }
@@ -881,6 +896,11 @@
     case MotionEvent::ACTION_MOVE:
       break;
     case MotionEvent::ACTION_NONE:
+    case MotionEvent::ACTION_HOVER_ENTER:
+    case MotionEvent::ACTION_HOVER_EXIT:
+    case MotionEvent::ACTION_HOVER_MOVE:
+    case MotionEvent::ACTION_BUTTON_PRESS:
+    case MotionEvent::ACTION_BUTTON_RELEASE:
       NOTREACHED();
       break;
   }
diff --git a/ui/events/gesture_detection/motion_event.h b/ui/events/gesture_detection/motion_event.h
index 9047d1a..11f647602 100644
--- a/ui/events/gesture_detection/motion_event.h
+++ b/ui/events/gesture_detection/motion_event.h
@@ -27,6 +27,11 @@
     ACTION_CANCEL,
     ACTION_POINTER_DOWN,
     ACTION_POINTER_UP,
+    ACTION_HOVER_ENTER,
+    ACTION_HOVER_EXIT,
+    ACTION_HOVER_MOVE,
+    ACTION_BUTTON_PRESS,
+    ACTION_BUTTON_RELEASE
   };
 
   enum ToolType {