blob: 0b3452421eec61ef11673ce394fd3ed76f2c3182 [file] [log] [blame]
// Copyright 2017 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/android/shared/input_events_generator.h"
#include <android/keycodes.h>
#include <jni.h>
#include <math.h>
#include <utility>
#include "starboard/android/shared/application_android.h"
#include "starboard/android/shared/jni_env_ext.h"
#include "starboard/android/shared/jni_utils.h"
#include "starboard/key.h"
namespace starboard {
namespace android {
namespace shared {
using ::starboard::shared::starboard::Application;
typedef ::starboard::android::shared::InputEventsGenerator::Event Event;
typedef ::starboard::android::shared::InputEventsGenerator::Events Events;
namespace {
SbKeyLocation GameActivityKeyToSbKeyLocation(GameActivityKeyEvent* event) {
int32_t keycode = event->keyCode;
switch (keycode) {
case AKEYCODE_ALT_LEFT:
case AKEYCODE_CTRL_LEFT:
case AKEYCODE_META_LEFT:
case AKEYCODE_SHIFT_LEFT:
return kSbKeyLocationLeft;
case AKEYCODE_ALT_RIGHT:
case AKEYCODE_CTRL_RIGHT:
case AKEYCODE_META_RIGHT:
case AKEYCODE_SHIFT_RIGHT:
return kSbKeyLocationRight;
}
return kSbKeyLocationUnspecified;
}
unsigned int GameActivityInputEventMetaStateToSbModifiers(unsigned int meta) {
unsigned int modifiers = kSbKeyModifiersNone;
if (meta & AMETA_ALT_ON) {
modifiers |= kSbKeyModifiersAlt;
}
if (meta & AMETA_CTRL_ON) {
modifiers |= kSbKeyModifiersCtrl;
}
if (meta & AMETA_META_ON) {
modifiers |= kSbKeyModifiersMeta;
}
if (meta & AMETA_SHIFT_ON) {
modifiers |= kSbKeyModifiersShift;
}
return modifiers;
}
std::unique_ptr<Event> CreateMoveEventWithKey(
int32_t device_id,
SbWindow window,
SbKey key,
SbKeyLocation location,
const SbInputVector& input_vector) {
std::unique_ptr<SbInputData> data(new SbInputData());
memset(data.get(), 0, sizeof(*data));
// window
data->window = window;
data->type = kSbInputEventTypeMove;
data->device_type = kSbInputDeviceTypeGamepad;
data->device_id = device_id;
// key
data->key = key;
data->key_location = location;
data->key_modifiers = kSbKeyModifiersNone;
data->position = input_vector;
return std::unique_ptr<Event>(
new Application::Event(kSbEventTypeInput, data.release(),
&Application::DeleteDestructor<SbInputData>));
}
float GetFlat(jobject input_device, int axis) {
if (input_device == NULL) {
return 0.0f;
}
JniEnvExt* env = JniEnvExt::Get();
ScopedLocalJavaRef<jobject> motion_range(env->CallObjectMethodOrAbort(
input_device, "getMotionRange",
"(I)Landroid/view/InputDevice$MotionRange;", axis));
if (motion_range.Get() == NULL) {
return 0.0f;
}
float flat =
env->CallFloatMethodOrAbort(motion_range.Get(), "getFlat", "()F");
SB_DCHECK(flat < 1.0f);
return flat;
}
bool IsDPadKey(SbKey key) {
return key == kSbKeyGamepadDPadUp || key == kSbKeyGamepadDPadDown ||
key == kSbKeyGamepadDPadLeft || key == kSbKeyGamepadDPadRight;
}
SbKey AInputEventToSbKey(GameActivityKeyEvent* event) {
auto keycode = event->keyCode;
switch (keycode) {
// Modifiers
case AKEYCODE_ALT_LEFT:
case AKEYCODE_ALT_RIGHT:
case AKEYCODE_MENU:
return kSbKeyMenu;
case AKEYCODE_CTRL_LEFT:
case AKEYCODE_CTRL_RIGHT:
return kSbKeyControl;
case AKEYCODE_META_LEFT:
return kSbKeyLwin;
case AKEYCODE_META_RIGHT:
return kSbKeyRwin;
case AKEYCODE_SHIFT_LEFT:
case AKEYCODE_SHIFT_RIGHT:
return kSbKeyShift;
case AKEYCODE_CAPS_LOCK:
return kSbKeyCapital;
case AKEYCODE_NUM_LOCK:
return kSbKeyNumlock;
case AKEYCODE_SCROLL_LOCK:
return kSbKeyScroll;
// System functions
case AKEYCODE_SLEEP:
return kSbKeySleep;
case AKEYCODE_HELP:
return kSbKeyHelp;
// Navigation
case AKEYCODE_BACK: // Android back button, not backspace
case AKEYCODE_ESCAPE:
return kSbKeyEscape;
// Enter/Select
case AKEYCODE_ENTER:
case AKEYCODE_NUMPAD_ENTER:
return kSbKeyReturn;
// Focus movement
case AKEYCODE_PAGE_UP:
return kSbKeyPrior;
case AKEYCODE_PAGE_DOWN:
return kSbKeyNext;
case AKEYCODE_MOVE_HOME:
return kSbKeyHome;
case AKEYCODE_MOVE_END:
return kSbKeyEnd;
case AKEYCODE_SEARCH:
return kSbKeyBrowserSearch;
// D-pad
case AKEYCODE_DPAD_UP:
return kSbKeyGamepadDPadUp;
case AKEYCODE_DPAD_DOWN:
return kSbKeyGamepadDPadDown;
case AKEYCODE_DPAD_LEFT:
return kSbKeyGamepadDPadLeft;
case AKEYCODE_DPAD_RIGHT:
return kSbKeyGamepadDPadRight;
case AKEYCODE_DPAD_CENTER:
return kSbKeyGamepad1;
// Game controller
case AKEYCODE_BUTTON_A:
return kSbKeyGamepad1;
case AKEYCODE_BUTTON_B:
return kSbKeyGamepad2;
case AKEYCODE_BUTTON_C:
return kSbKeyUnknown;
case AKEYCODE_BUTTON_X:
return kSbKeyGamepad3;
case AKEYCODE_BUTTON_Y:
return kSbKeyGamepad4;
case AKEYCODE_BUTTON_L1:
return kSbKeyGamepadLeftBumper;
case AKEYCODE_BUTTON_R1:
return kSbKeyGamepadRightBumper;
case AKEYCODE_BUTTON_L2:
return kSbKeyGamepadLeftTrigger;
case AKEYCODE_BUTTON_R2:
return kSbKeyGamepadRightTrigger;
case AKEYCODE_BUTTON_THUMBL:
return kSbKeyGamepadLeftStick;
case AKEYCODE_BUTTON_THUMBR:
return kSbKeyGamepadRightStick;
case AKEYCODE_BUTTON_START:
return kSbKeyGamepad6;
case AKEYCODE_BUTTON_SELECT:
return kSbKeyGamepad5;
case AKEYCODE_BUTTON_MODE:
return kSbKeyModechange;
// Media transport
case AKEYCODE_MEDIA_PLAY_PAUSE:
return kSbKeyMediaPlayPause;
case AKEYCODE_MEDIA_PLAY:
return kSbKeyPlay;
case AKEYCODE_MEDIA_PAUSE:
return kSbKeyPause;
case AKEYCODE_MEDIA_STOP:
return kSbKeyMediaStop;
case AKEYCODE_MEDIA_NEXT:
return kSbKeyMediaNextTrack;
case AKEYCODE_MEDIA_PREVIOUS:
return kSbKeyMediaPrevTrack;
case AKEYCODE_MEDIA_REWIND:
return kSbKeyMediaRewind;
case AKEYCODE_MEDIA_FAST_FORWARD:
return kSbKeyMediaFastForward;
#if SB_API_VERSION >= 15
case AKEYCODE_MEDIA_RECORD:
return kSbKeyRecord;
#endif
// TV Remote specific
case AKEYCODE_CHANNEL_UP:
return kSbKeyChannelUp;
case AKEYCODE_CHANNEL_DOWN:
return kSbKeyChannelDown;
case AKEYCODE_CAPTIONS:
return kSbKeyClosedCaption;
case AKEYCODE_INFO:
return kSbKeyInfo;
case AKEYCODE_GUIDE:
return kSbKeyGuide;
case AKEYCODE_LAST_CHANNEL:
return kSbKeyLast;
case AKEYCODE_MEDIA_AUDIO_TRACK:
return kSbKeyMediaAudioTrack;
case AKEYCODE_PROG_RED:
return kSbKeyRed;
case AKEYCODE_PROG_GREEN:
return kSbKeyGreen;
case AKEYCODE_PROG_YELLOW:
return kSbKeyYellow;
case AKEYCODE_PROG_BLUE:
return kSbKeyBlue;
// Whitespace
case AKEYCODE_TAB:
return kSbKeyTab;
case AKEYCODE_SPACE:
return kSbKeySpace;
// Deletion
case AKEYCODE_DEL: // Backspace
return kSbKeyBack;
case AKEYCODE_FORWARD_DEL:
return kSbKeyDelete;
case AKEYCODE_CLEAR:
return kSbKeyClear;
// Insert
case AKEYCODE_INSERT:
return kSbKeyInsert;
// Symbols
case AKEYCODE_NUMPAD_ADD:
return kSbKeyAdd;
case AKEYCODE_PLUS:
case AKEYCODE_EQUALS:
case AKEYCODE_NUMPAD_EQUALS:
return kSbKeyOemPlus;
case AKEYCODE_NUMPAD_SUBTRACT:
return kSbKeySubtract;
case AKEYCODE_MINUS:
return kSbKeyOemMinus;
case AKEYCODE_NUMPAD_MULTIPLY:
return kSbKeyMultiply;
case AKEYCODE_NUMPAD_DIVIDE:
return kSbKeyDivide;
case AKEYCODE_COMMA:
case AKEYCODE_NUMPAD_COMMA:
return kSbKeyOemComma;
case AKEYCODE_NUMPAD_DOT:
return kSbKeyDecimal;
case AKEYCODE_PERIOD:
return kSbKeyOemPeriod;
case AKEYCODE_SEMICOLON:
return kSbKeyOem1;
case AKEYCODE_SLASH:
return kSbKeyOem2;
case AKEYCODE_GRAVE:
return kSbKeyOem3;
case AKEYCODE_LEFT_BRACKET:
return kSbKeyOem4;
case AKEYCODE_BACKSLASH:
return kSbKeyOem5;
case AKEYCODE_RIGHT_BRACKET:
return kSbKeyOem6;
case AKEYCODE_APOSTROPHE:
return kSbKeyOem7;
// Function keys
case AKEYCODE_F1:
case AKEYCODE_F2:
case AKEYCODE_F3:
case AKEYCODE_F4:
case AKEYCODE_F5:
case AKEYCODE_F6:
case AKEYCODE_F7:
case AKEYCODE_F8:
case AKEYCODE_F9:
case AKEYCODE_F10:
case AKEYCODE_F11:
case AKEYCODE_F12:
return static_cast<SbKey>(kSbKeyF1 + (keycode - AKEYCODE_F1));
// Digits
case AKEYCODE_0:
case AKEYCODE_1:
case AKEYCODE_2:
case AKEYCODE_3:
case AKEYCODE_4:
case AKEYCODE_5:
case AKEYCODE_6:
case AKEYCODE_7:
case AKEYCODE_8:
case AKEYCODE_9:
return static_cast<SbKey>(kSbKey0 + (keycode - AKEYCODE_0));
// Numpad digits
case AKEYCODE_NUMPAD_0:
case AKEYCODE_NUMPAD_1:
case AKEYCODE_NUMPAD_2:
case AKEYCODE_NUMPAD_3:
case AKEYCODE_NUMPAD_4:
case AKEYCODE_NUMPAD_5:
case AKEYCODE_NUMPAD_6:
case AKEYCODE_NUMPAD_7:
case AKEYCODE_NUMPAD_8:
case AKEYCODE_NUMPAD_9:
return static_cast<SbKey>(kSbKeyNumpad0 + (keycode - AKEYCODE_NUMPAD_0));
// Alphabetic
case AKEYCODE_A:
case AKEYCODE_B:
case AKEYCODE_C:
case AKEYCODE_D:
case AKEYCODE_E:
case AKEYCODE_F:
case AKEYCODE_G:
case AKEYCODE_H:
case AKEYCODE_I:
case AKEYCODE_J:
case AKEYCODE_K:
case AKEYCODE_L:
case AKEYCODE_M:
case AKEYCODE_N:
case AKEYCODE_O:
case AKEYCODE_P:
case AKEYCODE_Q:
case AKEYCODE_R:
case AKEYCODE_S:
case AKEYCODE_T:
case AKEYCODE_U:
case AKEYCODE_V:
case AKEYCODE_W:
case AKEYCODE_X:
case AKEYCODE_Y:
case AKEYCODE_Z:
return static_cast<SbKey>(kSbKeyA + (keycode - AKEYCODE_A));
// Required, but will not be used by the YouTube application.
case AKEYCODE_VOLUME_UP:
return kSbKeyVolumeUp;
case AKEYCODE_VOLUME_DOWN:
return kSbKeyVolumeDown;
case AKEYCODE_MUTE:
return kSbKeyVolumeMute;
// Don't handle these keys so the OS can in a uniform manner.
case AKEYCODE_BRIGHTNESS_UP:
case AKEYCODE_BRIGHTNESS_DOWN:
default:
return kSbKeyUnknown;
}
}
} // namespace
InputEventsGenerator::InputEventsGenerator(SbWindow window)
: window_(window),
hat_value_(),
left_thumbstick_key_pressed_{kSbKeyUnknown, kSbKeyUnknown} {
SB_DCHECK(SbWindowIsValid(window_));
}
InputEventsGenerator::~InputEventsGenerator() {}
// For a left joystick, AMOTION_EVENT_AXIS_X reports the absolute X position of
// the joystick. The value is normalized to a range from -1.0 (left) to 1.0
// (right).
//
// For a left joystick, AMOTION_EVENT_AXIS_Y reports the absolute Y position of
// the joystick. The value is normalized to a range from -1.0 (up or far) to 1.0
// (down or near).
//
// On game pads with two analog joysticks, AMOTION_EVENT_AXIS_Z is often
// reinterpreted to report the absolute X position of the second joystick.
//
// On game pads with two analog joysticks, AMOTION_EVENT_AXIS_RZ is often
// reinterpreted to report the absolute Y position of the second joystick.
void InputEventsGenerator::ProcessJoyStickEvent(
FlatAxis axis,
int32_t motion_axis,
GameActivityMotionEvent* android_motion_event,
Events* events) {
SB_DCHECK(android_motion_event->pointerCount > 0);
int32_t device_id = android_motion_event->deviceId;
SB_DCHECK(device_flat_.find(device_id) != device_flat_.end());
float flat = device_flat_[device_id][axis];
float offset = GameActivityPointerAxes_getAxisValue(
&android_motion_event->pointers[0], motion_axis);
int sign = offset < 0.0f ? -1 : 1;
if (fabs(offset) < flat) {
offset = sign * flat;
}
// Rescaled the range:
// [-1.0f, -flat] to [-1.0f, 0.0f] and [flat, 1.0f] to [0.0f, 1.0f]
offset = (offset - sign * flat) / (1 - flat);
// Report up and left as negative values.
SbInputVector input_vector;
SbKey key = kSbKeyUnknown;
SbKeyLocation location = kSbKeyLocationUnspecified;
switch (axis) {
case kLeftX: {
input_vector.x = offset;
input_vector.y = 0.0f;
key = kSbKeyGamepadLeftStickLeft;
location = kSbKeyLocationLeft;
break;
}
case kLeftY: {
input_vector.x = 0.0f;
input_vector.y = offset;
key = kSbKeyGamepadLeftStickUp;
location = kSbKeyLocationLeft;
break;
}
case kRightX: {
input_vector.x = offset;
input_vector.y = 0.0f;
key = kSbKeyGamepadRightStickLeft;
location = kSbKeyLocationRight;
break;
}
case kRightY: {
input_vector.x = 0.0f;
input_vector.y = offset;
key = kSbKeyGamepadRightStickUp;
location = kSbKeyLocationRight;
break;
}
default:
SB_NOTREACHED();
}
events->push_back(
CreateMoveEventWithKey(device_id, window_, key, location, input_vector));
}
namespace {
// Generate a Starboard event from an Android motion event, with the SbKey and
// SbInputEventType pre-specified (so that it can be used by event
// synthesization as well.)
void PushKeyEvent(SbKey key,
SbInputEventType type,
SbWindow window,
GameActivityMotionEvent* android_event,
Events* events) {
std::unique_ptr<SbInputData> data(new SbInputData());
memset(data.get(), 0, sizeof(*data));
// window
data->window = window;
data->type = type;
// device
// TODO: differentiate gamepad, remote, etc.
data->device_type = kSbInputDeviceTypeKeyboard;
data->device_id = android_event->deviceId;
data->key = key;
data->key_location = kSbKeyLocationUnspecified;
data->key_modifiers =
GameActivityInputEventMetaStateToSbModifiers(android_event->metaState);
std::unique_ptr<Event> event(
new Event(kSbEventTypeInput, data.release(),
&Application::DeleteDestructor<SbInputData>));
events->push_back(std::move(event));
}
// Generate a Starboard event from an Android key event, with the SbKey and
// SbInputEventType pre-specified (so that it can be used by event
// synthesization as well.)
void PushKeyEvent(SbKey key,
SbInputEventType type,
SbWindow window,
GameActivityKeyEvent* android_event,
Events* events) {
std::unique_ptr<SbInputData> data(new SbInputData());
memset(data.get(), 0, sizeof(*data));
// window
data->window = window;
data->type = type;
// device
// TODO: differentiate gamepad, remote, etc.
data->device_type = kSbInputDeviceTypeKeyboard;
data->device_id = android_event->deviceId;
// key
data->key = key;
data->key_location = GameActivityKeyToSbKeyLocation(android_event);
data->key_modifiers =
GameActivityInputEventMetaStateToSbModifiers(android_event->metaState);
std::unique_ptr<Event> event(
new Event(kSbEventTypeInput, data.release(),
&Application::DeleteDestructor<SbInputData>));
events->push_back(std::move(event));
}
// Some helper enumerations to index into the InputEventsGenerator::hat_value_
// array.
enum HatAxis {
kHatX,
kHatY,
};
struct HatValue {
HatAxis axis;
float value;
};
// Converts Starboard DPad direction keys to Starboard left thumbstick
// direction keys.
SbKey ConvertDPadKeyToThumbstickKey(SbKey key) {
switch (key) {
case kSbKeyGamepadDPadUp:
return kSbKeyGamepadLeftStickUp;
case kSbKeyGamepadDPadDown:
return kSbKeyGamepadLeftStickDown;
case kSbKeyGamepadDPadLeft:
return kSbKeyGamepadLeftStickLeft;
case kSbKeyGamepadDPadRight:
return kSbKeyGamepadLeftStickRight;
default: {
SB_NOTREACHED();
return kSbKeyUnknown;
}
}
}
// Convert a Starboard DPad direction key to a (axis, direction) pair.
HatValue HatValueForDPadKey(SbKey key) {
SB_DCHECK(IsDPadKey(key));
switch (key) {
case kSbKeyGamepadDPadUp:
return HatValue({kHatY, -1.0f});
case kSbKeyGamepadDPadDown:
return HatValue({kHatY, 1.0f});
case kSbKeyGamepadDPadLeft:
return HatValue({kHatX, -1.0f});
case kSbKeyGamepadDPadRight:
return HatValue({kHatX, 1.0f});
default: {
SB_NOTREACHED();
return HatValue({kHatX, 0.0f});
}
}
}
// The inverse of HatValueForDPadKey().
SbKey KeyForHatValue(const HatValue& hat_value) {
SB_DCHECK(hat_value.value > 0.5f || hat_value.value < -0.5f);
if (hat_value.axis == kHatX) {
if (hat_value.value > 0.5f) {
return kSbKeyGamepadDPadRight;
} else {
return kSbKeyGamepadDPadLeft;
}
} else if (hat_value.axis == kHatY) {
if (hat_value.value > 0.5f) {
return kSbKeyGamepadDPadDown;
} else {
return kSbKeyGamepadDPadUp;
}
} else {
SB_NOTREACHED();
return kSbKeyUnknown;
}
}
// Analyzes old axis values and new axis values and fire off any synthesized
// key press/unpress events as necessary.
void PossiblySynthesizeHatKeyEvents(HatAxis axis,
float old_value,
float new_value,
SbWindow window,
GameActivityMotionEvent* android_event,
Events* events) {
if (old_value == new_value) {
// No events to generate if the hat motion value did not change.
return;
}
if (old_value > 0.5f || old_value < -0.5f) {
PushKeyEvent(KeyForHatValue(HatValue({axis, old_value})),
kSbInputEventTypeUnpress, window, android_event, events);
}
if (new_value > 0.5f || new_value < -0.5f) {
PushKeyEvent(KeyForHatValue(HatValue({axis, new_value})),
kSbInputEventTypePress, window, android_event, events);
}
}
} // namespace
bool InputEventsGenerator::CreateInputEventsFromGameActivityEvent(
GameActivityKeyEvent* android_event,
Events* events) {
SbInputEventType type;
switch (android_event->action) {
case AKEY_EVENT_ACTION_DOWN:
type = kSbInputEventTypePress;
break;
case AKEY_EVENT_ACTION_UP:
type = kSbInputEventTypeUnpress;
break;
default:
// TODO: send multiple events for AKEY_EVENT_ACTION_MULTIPLE
return false;
}
SbKey key = AInputEventToSbKey(android_event);
if (android_event->flags & AKEY_EVENT_FLAG_FALLBACK && IsDPadKey(key)) {
// For fallback DPad keys, we flow into special processing to manage the
// differentiation between the actual DPad and the left thumbstick, since
// Android conflates the key down/up events for these inputs.
ProcessFallbackDPadEvent(type, key, android_event, events);
} else {
PushKeyEvent(key, type, window_, android_event, events);
}
return true;
}
namespace {
SbKey ButtonStateToSbKey(int32_t button_state) {
if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) {
return kSbKeyMouse1;
} else if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) {
return kSbKeyMouse2;
} else if (button_state & AMOTION_EVENT_BUTTON_TERTIARY) {
return kSbKeyMouse3;
} else if (button_state & AMOTION_EVENT_BUTTON_BACK) {
return kSbKeyBrowserBack;
} else if (button_state & AMOTION_EVENT_BUTTON_FORWARD) {
return kSbKeyBrowserForward;
}
return kSbKeyUnknown;
}
// Get an SbKeyModifiers from a button state
unsigned int ButtonStateToSbModifiers(unsigned int button_state) {
unsigned int key_modifiers = kSbKeyModifiersNone;
if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) {
key_modifiers |= kSbKeyModifiersPointerButtonLeft;
}
if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) {
key_modifiers |= kSbKeyModifiersPointerButtonMiddle;
}
if (button_state & AMOTION_EVENT_BUTTON_TERTIARY) {
key_modifiers |= kSbKeyModifiersPointerButtonRight;
}
if (button_state & AMOTION_EVENT_BUTTON_BACK) {
key_modifiers |= kSbKeyModifiersPointerButtonBack;
}
if (button_state & AMOTION_EVENT_BUTTON_FORWARD) {
key_modifiers |= kSbKeyModifiersPointerButtonForward;
}
return key_modifiers;
}
} // namespace
bool InputEventsGenerator::ProcessPointerEvent(
GameActivityMotionEvent* android_event,
Events* events) {
float offset_x = GameActivityPointerAxes_getAxisValue(
&android_event->pointers[0], AMOTION_EVENT_AXIS_X);
float offset_y = GameActivityPointerAxes_getAxisValue(
&android_event->pointers[0], AMOTION_EVENT_AXIS_Y);
std::unique_ptr<SbInputData> data(new SbInputData());
memset(data.get(), 0, sizeof(*data));
data->window = window_;
SB_DCHECK(SbWindowIsValid(data->window));
data->pressure = NAN;
data->size = {NAN, NAN};
data->tilt = {NAN, NAN};
unsigned int button_state = android_event->buttonState;
unsigned int button_modifiers = ButtonStateToSbModifiers(button_state);
// Default to reporting pointer events as mouse events.
data->device_type = kSbInputDeviceTypeMouse;
// Report both stylus and touchscreen events as touchscreen device events.
int32_t event_source = android_event->source;
if (((event_source & AINPUT_SOURCE_TOUCHSCREEN) != 0) ||
((event_source & AINPUT_SOURCE_STYLUS) != 0)) {
data->device_type = kSbInputDeviceTypeTouchScreen;
}
data->device_id = android_event->deviceId;
data->key_modifiers =
button_modifiers |
GameActivityInputEventMetaStateToSbModifiers(android_event->metaState);
data->position.x = offset_x;
data->position.y = offset_y;
data->key = ButtonStateToSbKey(button_state);
switch (android_event->action & AMOTION_EVENT_ACTION_MASK) {
case AMOTION_EVENT_ACTION_UP:
data->type = kSbInputEventTypeUnpress;
break;
case AMOTION_EVENT_ACTION_DOWN:
data->type = kSbInputEventTypePress;
break;
case AMOTION_EVENT_ACTION_MOVE:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
data->type = kSbInputEventTypeMove;
break;
case AMOTION_EVENT_ACTION_SCROLL: {
float hscroll = GameActivityPointerAxes_getAxisValue(
&android_event->pointers[0],
AMOTION_EVENT_AXIS_HSCROLL); // left is -1
float vscroll = GameActivityPointerAxes_getAxisValue(
&android_event->pointers[0],
AMOTION_EVENT_AXIS_VSCROLL); // down is -1
float wheel = GameActivityPointerAxes_getAxisValue(
&android_event->pointers[0],
AMOTION_EVENT_AXIS_WHEEL); // this is not used
data->type = kSbInputEventTypeWheel;
data->key = kSbKeyUnknown;
data->delta.y = -vscroll;
data->delta.x = hscroll;
break;
}
default:
return false;
}
events->push_back(std::unique_ptr<Event>(
new Application::Event(kSbEventTypeInput, data.release(),
&Application::DeleteDestructor<SbInputData>)));
return true;
}
jobject GetDevice(GameActivityMotionEvent* android_motion_event) {
int32_t device_id = android_motion_event->deviceId;
JniEnvExt* env = JniEnvExt::Get();
return env->CallStaticObjectMethodOrAbort(
"android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;",
device_id);
}
bool InputEventsGenerator::CreateInputEventsFromGameActivityEvent(
GameActivityMotionEvent* android_event,
Events* events) {
if ((android_event->source & AINPUT_SOURCE_CLASS_POINTER) != 0) {
return ProcessPointerEvent(android_event, events);
}
if ((android_event->source & AINPUT_SOURCE_JOYSTICK) == 0) {
// Only handles joystick events in the code below.
return false;
}
ScopedLocalJavaRef<jobject> device(GetDevice(android_event));
if (device.Get() != NULL) {
UpdateDeviceFlatMapIfNecessary(android_event, device.Get());
ProcessJoyStickEvent(kLeftX, AMOTION_EVENT_AXIS_X, android_event, events);
ProcessJoyStickEvent(kLeftY, AMOTION_EVENT_AXIS_Y, android_event, events);
ProcessJoyStickEvent(kRightX, AMOTION_EVENT_AXIS_Z, android_event, events);
ProcessJoyStickEvent(kRightY, AMOTION_EVENT_AXIS_RZ, android_event, events);
// Remember the "hat" input values (dpad on the game controller) to help
// differentiate hat vs. stick fallback events.
UpdateHatValuesAndPossiblySynthesizeKeyEvents(android_event, events);
}
// Lie to Android and tell it that we did not process the motion event,
// causing Android to synthesize dpad key events for us. When we handle
// those synthesized key events we'll enqueue kSbKeyGamepadLeft rather
// than kSbKeyGamepadDPad events if they're from the joystick.
return false;
}
// Special processing to disambiguate between DPad events and left-thumbstick
// direction key events.
void InputEventsGenerator::ProcessFallbackDPadEvent(
SbInputEventType type,
SbKey key,
GameActivityKeyEvent* android_event,
Events* events) {
SB_DCHECK(android_event->flags & AKEY_EVENT_FLAG_FALLBACK);
SB_DCHECK(IsDPadKey(key));
HatAxis hat_axis = HatValueForDPadKey(key).axis;
if (hat_value_[hat_axis] != 0.0f && type == kSbInputEventTypePress) {
// Direction pad events are all assumed to be coming from the hat controls
// if motion events for that hat DPAD is active, but we do still handle
// repeat keys here.
if ((android_event->repeatCount) > 0) {
SB_LOG(INFO) << android_event->repeatCount;
PushKeyEvent(key, kSbInputEventTypePress, window_, android_event, events);
}
return;
}
// If we get this far, then we are exclusively dealing with thumbstick events,
// as actual DPad events are processed in motion events by checking the
// hat axis representing the DPad.
SbKey thumbstick_key = ConvertDPadKeyToThumbstickKey(key);
if (left_thumbstick_key_pressed_[hat_axis] != kSbKeyUnknown &&
(type == kSbInputEventTypeUnpress ||
left_thumbstick_key_pressed_[hat_axis] != thumbstick_key)) {
// Fire an unpressed event if our current key differs from the last seen
// key.
PushKeyEvent(left_thumbstick_key_pressed_[hat_axis],
kSbInputEventTypeUnpress, window_, android_event, events);
}
if (type == kSbInputEventTypePress) {
PushKeyEvent(thumbstick_key, kSbInputEventTypePress, window_, android_event,
events);
left_thumbstick_key_pressed_[hat_axis] = thumbstick_key;
} else if (type == kSbInputEventTypeUnpress) {
left_thumbstick_key_pressed_[hat_axis] = kSbKeyUnknown;
} else {
SB_NOTREACHED();
}
}
// Update |InputEventsGenerator::hat_value_| according to the incoming motion
// event's data. Possibly generate DPad events based on any changes in value
// here.
void InputEventsGenerator::UpdateHatValuesAndPossiblySynthesizeKeyEvents(
GameActivityMotionEvent* android_motion_event,
Events* events) {
float new_hat_x = GameActivityPointerAxes_getAxisValue(
&android_motion_event->pointers[0], AMOTION_EVENT_AXIS_HAT_X);
PossiblySynthesizeHatKeyEvents(kHatX, hat_value_[kHatX], new_hat_x, window_,
android_motion_event, events);
hat_value_[kHatX] = new_hat_x;
float new_hat_y = GameActivityPointerAxes_getAxisValue(
&android_motion_event->pointers[0], AMOTION_EVENT_AXIS_HAT_Y);
PossiblySynthesizeHatKeyEvents(kHatY, hat_value_[kHatY], new_hat_y, window_,
android_motion_event, events);
hat_value_[kHatY] = new_hat_y;
}
void InputEventsGenerator::UpdateDeviceFlatMapIfNecessary(
GameActivityMotionEvent* android_motion_event,
jobject input_device) {
int32_t device_id = android_motion_event->deviceId;
if (device_flat_.find(device_id) != device_flat_.end()) {
// |device_flat_| is already contains the device flat information.
return;
}
float flats[kNumAxes] = {GetFlat(input_device, AMOTION_EVENT_AXIS_X),
GetFlat(input_device, AMOTION_EVENT_AXIS_Y),
GetFlat(input_device, AMOTION_EVENT_AXIS_Z),
GetFlat(input_device, AMOTION_EVENT_AXIS_RZ)};
device_flat_[device_id] = std::vector<float>(flats, flats + kNumAxes);
}
void InputEventsGenerator::CreateInputEventsFromSbKey(SbKey key,
Events* events) {
events->clear();
// Press event
std::unique_ptr<SbInputData> data(new SbInputData());
memset(data.get(), 0, sizeof(*data));
data->window = window_;
data->type = kSbInputEventTypePress;
data->device_type = kSbInputDeviceTypeKeyboard;
data->device_id = 0;
data->key = key;
data->key_location = kSbKeyLocationUnspecified;
data->key_modifiers = kSbKeyModifiersNone;
events->push_back(std::unique_ptr<Event>(
new Application::Event(kSbEventTypeInput, data.release(),
&Application::DeleteDestructor<SbInputData>)));
// Unpress event
data.reset(new SbInputData());
memset(data.get(), 0, sizeof(*data));
data->window = window_;
data->type = kSbInputEventTypeUnpress;
data->device_type = kSbInputDeviceTypeKeyboard;
data->device_id = 0;
data->key = key;
data->key_location = kSbKeyLocationUnspecified;
data->key_modifiers = kSbKeyModifiersNone;
events->push_back(std::unique_ptr<Event>(
new Application::Event(kSbEventTypeInput, data.release(),
&Application::DeleteDestructor<SbInputData>)));
}
} // namespace shared
} // namespace android
} // namespace starboard