blob: 7cbd1ac6fcf0f483c8da87c64eaa23054054a0bf [file]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/input/web_input_event_builders_android.h"
#include <android/input.h>
#include "base/check.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversion_utils.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "ui/events/android/key_event_utils.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/blink_event_util.h"
#include "ui/events/event_constants.h"
#include "ui/events/features.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/keycodes/keyboard_code_conversion_android.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/types/scroll_types.h"
using blink::WebInputEvent;
using blink::WebKeyboardEvent;
using blink::WebGestureEvent;
using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
using blink::WebPointerProperties;
using blink::WebTouchEvent;
using blink::WebTouchPoint;
namespace input {
namespace {
const char* InputSourceToString(int source) {
switch (source) {
case AINPUT_SOURCE_UNKNOWN:
return "UNKNOWN";
case AINPUT_SOURCE_KEYBOARD:
return "KEYBOARD";
case AINPUT_SOURCE_DPAD:
return "DPAD";
case AINPUT_SOURCE_GAMEPAD:
return "GAMEPAD";
case AINPUT_SOURCE_TOUCHSCREEN:
return "TOUCHSCREEN";
case AINPUT_SOURCE_MOUSE:
return "MOUSE";
case AINPUT_SOURCE_STYLUS:
return "STYLUS";
case AINPUT_SOURCE_TRACKBALL:
return "TRACKBALL";
case AINPUT_SOURCE_MOUSE_RELATIVE:
return "MOUSE_RELATIVE";
case AINPUT_SOURCE_TOUCHPAD:
return "TOUCHPAD";
case AINPUT_SOURCE_TOUCH_NAVIGATION:
return "TOUCH_NAVIGATION";
case AINPUT_SOURCE_ROTARY_ENCODER:
return "ROTARY_ENCODER";
case AINPUT_SOURCE_JOYSTICK:
return "JOYSTICK";
default:
return "OTHER";
}
}
int WebInputEventToAndroidModifier(int web_modifier) {
int android_modifier = 0;
// Currently only Shift, CapsLock are used, add other modifiers if required.
if (web_modifier & WebInputEvent::kShiftKey)
android_modifier |= AMETA_SHIFT_ON;
if (web_modifier & WebInputEvent::kCapsLockOn)
android_modifier |= AMETA_CAPS_LOCK_ON;
return android_modifier;
}
ui::DomKey GetDomKeyFromEvent(
JNIEnv* env,
const base::android::JavaRef<jobject>& android_key_event,
int keycode,
int modifiers,
int unicode_character) {
// Synthetic key event, not enough information to get DomKey.
if (android_key_event.is_null() && !unicode_character)
return ui::DomKey::UNIDENTIFIED;
if (!unicode_character && env) {
// According to spec |kAllowedModifiers| should be Shift and AltGr, however
// Android doesn't have AltGr key and ImeAdapter::getModifiers won't pass it
// either.
// According to discussion we want to honor CapsLock and possibly NumLock as
// well. https://github.com/w3c/uievents/issues/70
const int kAllowedModifiers =
WebInputEvent::kShiftKey | WebInputEvent::kCapsLockOn;
int fallback_modifiers =
WebInputEventToAndroidModifier(modifiers & kAllowedModifiers);
unicode_character = ui::events::android::GetKeyEventUnicodeChar(
env, android_key_event, fallback_modifiers);
}
ui::DomKey key = ui::GetDomKeyFromAndroidEvent(keycode, unicode_character);
if (key != ui::DomKey::NONE)
return key;
return ui::DomKey::UNIDENTIFIED;
}
bool IsConfirmedPhysicalKeyboardEvent(
JNIEnv* env,
const base::android::JavaRef<jobject>& android_key_event) {
if (android_key_event.is_null()) {
// Synthetic key event, not enough information to detect source.
return false;
}
return !ui::events::android::IsVirtualKeyboardEvent(env, android_key_event);
}
} // namespace
WebKeyboardEvent WebKeyboardEventBuilder::Build(
JNIEnv* env,
const base::android::JavaRef<jobject>& android_key_event,
WebInputEvent::Type type,
int modifiers,
base::TimeTicks time,
int keycode,
int scancode,
int unicode_character,
bool is_system_key) {
DCHECK(WebInputEvent::IsKeyboardEventType(type));
ui::DomCode dom_code = ui::DomCode::NONE;
if (scancode)
dom_code = ui::KeycodeConverter::NativeKeycodeToDomCode(scancode);
WebKeyboardEvent result(
type, modifiers | ui::DomCodeToWebInputEventModifiers(dom_code), time);
result.windows_key_code = ui::LocatedToNonLocatedKeyboardCode(
ui::KeyboardCodeFromAndroidKeyCode(keycode));
result.native_key_code = keycode;
result.dom_code = static_cast<int>(dom_code);
result.dom_key = GetDomKeyFromEvent(env, android_key_event, keycode,
modifiers, unicode_character);
std::u16string unmodified_text_str;
if (unicode_character) {
base::WriteUnicodeCharacter(unicode_character, &unmodified_text_str);
}
if (result.windows_key_code == ui::VKEY_RETURN) {
// This is the same behavior as GTK:
// We need to treat the enter key as a key press of character \r. This
// is apparently just how webkit handles it and what it expects.
unmodified_text_str = u"\r";
}
base::u16cstrlcpy(result.unmodified_text.data(), unmodified_text_str.c_str(),
result.unmodified_text.size());
result.text = result.unmodified_text;
result.is_system_key = is_system_key;
result.is_confirmed_physical_keyboard_input =
IsConfirmedPhysicalKeyboardEvent(env, android_key_event);
return result;
}
WebMouseEvent WebMouseEventBuilder::Build(
const ui::MotionEventAndroid& motion_event,
WebInputEvent::Type type,
int click_count,
int action_button) {
DCHECK(WebInputEvent::IsMouseEventType(type));
int modifiers = motion_event.GetFlags();
WebMouseEvent result(type, ui::EventFlagsToWebEventModifiers(modifiers),
motion_event.GetEventTime());
result.SetPositionInWidget(motion_event.GetX(0), motion_event.GetY(0));
result.SetPositionInScreen(motion_event.GetRawX(0), motion_event.GetRawY(0));
result.click_count = click_count;
int button = action_button;
// For events other than MouseDown/Up, action_button is not defined. So we are
// determining |button| value from |modifiers| as is done in other platforms.
if ((type != WebInputEvent::Type::kMouseDown &&
type != WebInputEvent::Type::kMouseUp) ||
// TODO(crbug.com/409639106): Also on MouseDown/Up events with undefined button since
// MotionEvent.obtain used to simlulate inputs events in tests does not set the button
// correctly.
button == 0) {
if (modifiers & ui::EF_LEFT_MOUSE_BUTTON)
button = ui::MotionEvent::BUTTON_PRIMARY;
else if (modifiers & ui::EF_MIDDLE_MOUSE_BUTTON)
button = ui::MotionEvent::BUTTON_TERTIARY;
else if (modifiers & ui::EF_RIGHT_MOUSE_BUTTON)
button = ui::MotionEvent::BUTTON_SECONDARY;
else
button = 0;
}
ui::SetWebPointerPropertiesFromMotionEventData(
result, motion_event.GetPointerId(0), motion_event.GetPressure(0),
motion_event.GetOrientation(0), motion_event.GetTiltX(0),
motion_event.GetTiltY(0), motion_event.GetTwist(0),
motion_event.GetTangentialPressure(0), button,
motion_event.GetToolType(0));
return result;
}
WebMouseWheelEvent WebMouseWheelEventBuilder::Build(
const ui::MotionEventAndroid& motion_event) {
TRACE_EVENT("input", "WebMouseWheelEventBuilder::Build", "source",
InputSourceToString(motion_event.GetSource()), "tool_type",
motion_event.GetToolType(0));
WebMouseWheelEvent result(WebInputEvent::Type::kMouseWheel,
WebInputEvent::kNoModifiers,
motion_event.GetEventTime());
result.SetPositionInWidget(motion_event.GetX(0), motion_event.GetY(0));
result.SetPositionInScreen(motion_event.GetRawX(0), motion_event.GetRawY(0));
result.button = WebMouseEvent::Button::kNoButton;
bool is_touchpad = motion_event.GetSource() == AINPUT_SOURCE_TOUCHPAD;
// Touchpads can be represented in several ways on Android. It is important to
// mark those as precise, otherwise with smooth scrolling, we get
// acceleration, which translates to very high latency for the user.
//
// Note that in practice, the tool has been seen to be set to UNKNOWN locally,
// so this may overtrigger, but there isn't an easy clean way to make sure
// that touchpads are properly handled. However, it is properly set to MOUSE
// locally when using an external USB mouse, so we still get smooth scrolling.
if (base::FeatureList::IsEnabled(ui::kAndroidTouchpadDetection)) {
is_touchpad =
(motion_event.GetSource() & AINPUT_SOURCE_TOUCHPAD) ==
AINPUT_SOURCE_TOUCHPAD ||
((motion_event.GetSource() & AINPUT_SOURCE_MOUSE) ==
AINPUT_SOURCE_MOUSE &&
motion_event.GetToolType(0) != ui::MotionEvent::ToolType::MOUSE);
}
result.delta_units = is_touchpad
? ui::ScrollGranularity::kScrollByPrecisePixel
: ui::ScrollGranularity::kScrollByPixel;
// For vertical scrolling (delta_y, wheel_ticks_y), Android's MotionEvent
// provides values that align with "natural" scrolling (content moves in the
// direction of the scroll).
// For horizontal scrolling (delta_x, wheel_ticks_x), the MotionEvent
// historically provided values that matched traditional desktop behavior
// (content moves opposite to scroll direction). To align with Android's
// "natural" scrolling expectation for horizontal input, we negate the x-axis
// values.
// However, this negation should ONLY apply to Physical Mouse inputs.
// Other sources like Touchpads (Source Mouse + Tool Finger or Source
// Touchpad), and Joysticks already provide "natural" direction values (or
// standard
// Cartesian direction) and should not be negated.
// Note: AINPUT_SOURCE_TRACKBALL and AINPUT_SOURCE_MOUSE_RELATIVE are
// AINPUT_SOURCE_CLASS_NAVIGATION (similar to Joysticks), so they are also
// excluded from this negation.
bool is_physical_mouse_scroll =
(motion_event.GetSource() & AINPUT_SOURCE_MOUSE) == AINPUT_SOURCE_MOUSE &&
motion_event.GetToolType(0) == ui::MotionEvent::ToolType::MOUSE;
if (is_physical_mouse_scroll) {
result.delta_x = -motion_event.ticks_x() * motion_event.GetTickMultiplier();
result.wheel_ticks_x = -motion_event.ticks_x();
} else {
result.delta_x = motion_event.ticks_x() * motion_event.GetTickMultiplier();
result.wheel_ticks_x = motion_event.ticks_x();
}
result.delta_y = motion_event.ticks_y() * motion_event.GetTickMultiplier();
result.wheel_ticks_y = motion_event.ticks_y();
result.SetModifiers(
ui::EventFlagsToWebEventModifiers(motion_event.GetFlags()));
switch (motion_event.GetAction()) {
case ui::MotionEvent::Action::DOWN:
result.phase = blink::WebMouseWheelEvent::kPhaseBegan;
break;
case ui::MotionEvent::Action::MOVE:
result.phase = result.delta_x || result.delta_y
? blink::WebMouseWheelEvent::kPhaseChanged
: blink::WebMouseWheelEvent::kPhaseStationary;
break;
case ui::MotionEvent::Action::UP:
case ui::MotionEvent::Action::CANCEL:
result.phase = blink::WebMouseWheelEvent::kPhaseEnded;
break;
default:
result.phase = blink::WebMouseWheelEvent::kPhaseNone;
break;
}
return result;
}
WebGestureEvent WebGestureEventBuilder::Build(WebInputEvent::Type type,
base::TimeTicks time,
float x,
float y) {
DCHECK(WebInputEvent::IsGestureEventType(type));
WebGestureEvent result(type, WebInputEvent::kNoModifiers, time,
blink::WebGestureDevice::kTouchscreen);
result.SetPositionInWidget(gfx::PointF(x, y));
return result;
}
} // namespace input