blob: 16c92c1e1f9dbf8bd501efa4d7095a64bc7918ef [file] [log] [blame]
// Copyright 2017 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.
#include "chrome/browser/android/vr/android_ui_gesture_target.h"
#include <cmath>
#include "chrome/browser/vr/input_event.h"
#include "jni/AndroidUiGestureTarget_jni.h"
using base::android::JavaParamRef;
using base::android::JavaRef;
using content::MotionEventAction;
static constexpr int kFrameDurationMs = 16;
static constexpr int kScrollEventsPerFrame = 2;
namespace vr {
AndroidUiGestureTarget::AndroidUiGestureTarget(JNIEnv* env,
const JavaParamRef<jobject>& obj,
float scale_factor,
float scroll_ratio,
int touch_slop)
: scale_factor_(scale_factor),
scroll_ratio_(scroll_ratio),
touch_slop_(touch_slop),
java_ref_(env, obj) {}
AndroidUiGestureTarget::~AndroidUiGestureTarget() = default;
void AndroidUiGestureTarget::DispatchInputEvent(
std::unique_ptr<InputEvent> event) {
int64_t event_time_ms = event->time_stamp().since_origin().InMilliseconds();
switch (event->type()) {
case InputEvent::kScrollBegin: {
SetPointer(event->position_in_widget());
Inject(content::MOTION_EVENT_ACTION_START, event_time_ms);
float xdiff = event->scroll_data.delta_x;
float ydiff = event->scroll_data.delta_y;
if (xdiff == 0 && ydiff == 0)
ydiff = touch_slop_;
double dist = std::sqrt((xdiff * xdiff) + (ydiff * ydiff));
if (dist < touch_slop_) {
xdiff *= touch_slop_ / dist;
ydiff *= touch_slop_ / dist;
}
float xtarget = xdiff * scroll_ratio_ + event->position_in_widget().x();
float ytarget = ydiff * scroll_ratio_ + event->position_in_widget().y();
scroll_x_ = xtarget > 0 ? std::ceil(xtarget) : std::floor(xtarget);
scroll_y_ = ytarget > 0 ? std::ceil(ytarget) : std::floor(ytarget);
SetPointer(scroll_x_, scroll_y_);
// Send a move immediately so that we can't accidentally trigger a click.
Inject(content::MOTION_EVENT_ACTION_MOVE, event_time_ms);
break;
}
case InputEvent::kScrollEnd:
SetPointer(scroll_x_, scroll_y_);
Inject(content::MOTION_EVENT_ACTION_END, event_time_ms);
break;
case InputEvent::kScrollUpdate: {
float scale = scroll_ratio_ / kScrollEventsPerFrame;
scroll_x_ += event->scroll_data.delta_x * scale;
scroll_y_ += event->scroll_data.delta_y * scale;
SetPointer(scroll_x_, scroll_y_);
Inject(content::MOTION_EVENT_ACTION_MOVE, event_time_ms);
scroll_x_ += event->scroll_data.delta_x * scale;
scroll_y_ += event->scroll_data.delta_y * scale;
SetDelayedEvent(scroll_x_, scroll_y_, content::MOTION_EVENT_ACTION_MOVE,
event_time_ms, kFrameDurationMs / kScrollEventsPerFrame);
break;
}
case InputEvent::kFlingCancel:
Inject(content::MOTION_EVENT_ACTION_START, event_time_ms);
Inject(content::MOTION_EVENT_ACTION_CANCEL, event_time_ms);
break;
case InputEvent::kHoverEnter:
SetPointer(event->position_in_widget());
Inject(content::MOTION_EVENT_ACTION_HOVER_ENTER, event_time_ms);
break;
case InputEvent::kHoverLeave:
case InputEvent::kHoverMove:
// The platform ignores HOVER_EXIT, so we instead send a fixed
// out-of-bounds point (http://crbug.com/715114).
SetPointer(event->position_in_widget());
Inject(content::MOTION_EVENT_ACTION_HOVER_MOVE, event_time_ms);
break;
case InputEvent::kButtonDown:
SetPointer(event->position_in_widget());
Inject(content::MOTION_EVENT_ACTION_START, event_time_ms);
break;
case InputEvent::kButtonUp:
SetPointer(event->position_in_widget());
Inject(content::MOTION_EVENT_ACTION_END, event_time_ms);
break;
case InputEvent::kMove:
SetPointer(event->position_in_widget());
Inject(content::MOTION_EVENT_ACTION_MOVE, event_time_ms);
break;
default:
NOTREACHED() << "Unsupported event type sent to Android UI.";
break;
}
}
void AndroidUiGestureTarget::Inject(MotionEventAction action, int64_t time_ms) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
if (obj.is_null())
return;
Java_AndroidUiGestureTarget_inject(env, obj, action, time_ms);
}
void AndroidUiGestureTarget::SetPointer(const gfx::PointF& position) {
SetPointer(position.x(), position.y());
}
void AndroidUiGestureTarget::SetPointer(float x, float y) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
if (obj.is_null())
return;
Java_AndroidUiGestureTarget_setPointer(env, obj,
static_cast<int>(x * scale_factor_),
static_cast<int>(y * scale_factor_));
}
void AndroidUiGestureTarget::SetDelayedEvent(float x,
float y,
MotionEventAction action,
int64_t time_ms,
int delay_ms) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jobject> obj = java_ref_.get(env);
if (obj.is_null())
return;
Java_AndroidUiGestureTarget_setDelayedEvent(
env, obj, static_cast<int>(x * scale_factor_),
static_cast<int>(y * scale_factor_), action, time_ms, delay_ms);
}
// static
AndroidUiGestureTarget* AndroidUiGestureTarget::FromJavaObject(
const JavaRef<jobject>& obj) {
if (obj.is_null())
return nullptr;
JNIEnv* env = base::android::AttachCurrentThread();
return reinterpret_cast<AndroidUiGestureTarget*>(
Java_AndroidUiGestureTarget_getNativeObject(env, obj));
}
static jlong JNI_AndroidUiGestureTarget_Init(JNIEnv* env,
const JavaParamRef<jobject>& obj,
jfloat scale_factor,
jfloat scroll_ratio,
jint touch_slop) {
return reinterpret_cast<intptr_t>(new AndroidUiGestureTarget(
env, obj, scale_factor, scroll_ratio, touch_slop));
}
} // namespace vr