summaryrefslogtreecommitdiff
path: root/Source/Core/InputCommon/ControllerInterface/Android
diff options
context:
space:
mode:
Diffstat (limited to 'Source/Core/InputCommon/ControllerInterface/Android')
-rw-r--r--Source/Core/InputCommon/ControllerInterface/Android/Android.cpp1138
-rw-r--r--Source/Core/InputCommon/ControllerInterface/Android/Android.h6
2 files changed, 1128 insertions, 16 deletions
diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp
index 7a7c548cc6..a4bd7a3d46 100644
--- a/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp
+++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.cpp
@@ -1,32 +1,1142 @@
-// Copyright 2013 Dolphin Emulator Project
+// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "InputCommon/ControllerInterface/Android/Android.h"
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+#include <fmt/format.h>
+
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <jni.h>
+
+#include "Common/Assert.h"
+#include "Common/Logging/Log.h"
+#include "Common/StringUtil.h"
+
#include "InputCommon/ControllerInterface/ControllerInterface.h"
-#include "InputCommon/ControllerInterface/Touch/Touchscreen.h"
+
+#include "jni/AndroidCommon/AndroidCommon.h"
+#include "jni/AndroidCommon/IDCache.h"
+#include "jni/Input/CoreDevice.h"
+
+namespace
+{
+jclass s_list_class;
+jmethodID s_list_get;
+jmethodID s_list_size;
+
+jclass s_input_device_class;
+jmethodID s_input_device_get_device_ids;
+jmethodID s_input_device_get_device;
+jmethodID s_input_device_get_controller_number;
+jmethodID s_input_device_get_id;
+jmethodID s_input_device_get_motion_ranges;
+jmethodID s_input_device_get_name;
+jmethodID s_input_device_get_sources;
+jmethodID s_input_device_has_keys;
+
+jclass s_motion_range_class;
+jmethodID s_motion_range_get_axis;
+jmethodID s_motion_range_get_max;
+jmethodID s_motion_range_get_min;
+jmethodID s_motion_range_get_source;
+
+jclass s_input_event_class;
+jmethodID s_input_event_get_device;
+
+jclass s_key_event_class;
+jmethodID s_key_event_get_action;
+jmethodID s_key_event_get_keycode;
+
+jclass s_motion_event_class;
+jmethodID s_motion_event_get_axis_value;
+jmethodID s_motion_event_get_source;
+
+jclass s_controller_interface_class;
+jmethodID s_controller_interface_register_input_device_listener;
+jmethodID s_controller_interface_unregister_input_device_listener;
+jmethodID s_controller_interface_get_vibrator_manager;
+jmethodID s_controller_interface_get_system_vibrator_manager;
+jmethodID s_controller_interface_vibrate;
+
+jclass s_sensor_event_listener_class;
+jmethodID s_sensor_event_listener_constructor;
+jmethodID s_sensor_event_listener_constructor_input_device;
+jmethodID s_sensor_event_listener_set_device_qualifier;
+jmethodID s_sensor_event_listener_request_unsuspend_sensor;
+jmethodID s_sensor_event_listener_get_axis_names;
+jmethodID s_sensor_event_listener_get_negative_axes;
+
+jclass s_dolphin_vibrator_manager_class;
+jmethodID s_dolphin_vibrator_manager_get_vibrator;
+jmethodID s_dolphin_vibrator_manager_get_vibrator_ids;
+
+jintArray s_keycodes_array;
+
+using Clock = std::chrono::steady_clock;
+constexpr Clock::duration ACTIVE_INPUT_TIMEOUT = std::chrono::milliseconds(1000);
+
+std::unordered_map<jint, ciface::Core::DeviceQualifier> s_device_id_to_device_qualifier;
+
+constexpr int MAX_KEYCODE = AKEYCODE_PROFILE_SWITCH; // Up to date as of SDK 31
+
+const std::array<std::string_view, MAX_KEYCODE + 1> KEYCODE_NAMES = {
+ "Unknown",
+ "Soft Left",
+ "Soft Right",
+ "Home",
+ "Back",
+ "Call",
+ "End Call",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "Star",
+ "Pound",
+ "Up",
+ "Down",
+ "Left",
+ "Right",
+ "Center",
+ "Volume Up",
+ "Volume Down",
+ "Power",
+ "Camera",
+ "Clear",
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ "I",
+ "J",
+ "K",
+ "L",
+ "M",
+ "N",
+ "O",
+ "P",
+ "Q",
+ "R",
+ "S",
+ "T",
+ "U",
+ "V",
+ "W",
+ "X",
+ "Y",
+ "Z",
+ "Comma",
+ "Period",
+ "Left Alt",
+ "Right Alt",
+ "Left Shift",
+ "Right Shift",
+ "Tab",
+ "Space",
+ "Sym",
+ "Explorer",
+ "Envelope",
+ "Enter",
+ "Backspace",
+ "Grave",
+ "Minus",
+ "Equals",
+ "Left Bracket",
+ "Right Bracket",
+ "Backslash",
+ "Semicolon",
+ "Apostrophe",
+ "Slash",
+ "At",
+ "Num",
+ "Headset Hook",
+ "Focus",
+ "Plus",
+ "Menu",
+ "Notification",
+ "Search",
+ "Play Pause",
+ "Stop",
+ "Next",
+ "Previous",
+ "Rewind",
+ "Fast Forward",
+ "Mute",
+ "Page Up",
+ "Page Down",
+ "Emoji",
+ "Switch Charset",
+ "Button A",
+ "Button B",
+ "Button C",
+ "Button X",
+ "Button Y",
+ "Button Z",
+ "Button L1",
+ "Button R1",
+ "Button L2",
+ "Button R2",
+ "Button L3",
+ "Button R3",
+ "Start",
+ "Select",
+ "Mode",
+ "Escape",
+ "Delete",
+ "Left Ctrl",
+ "Right Ctrl",
+ "Caps Lock",
+ "Scroll Lock",
+ "Left Meta",
+ "Right Meta",
+ "Fn",
+ "PrtSc SysRq",
+ "Pause Break",
+ "Move Home",
+ "Move End",
+ "Insert",
+ "Forward",
+ "Play",
+ "Pause",
+ "Close",
+ "Eject",
+ "Record",
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "F10",
+ "F11",
+ "F12",
+ "Num Lock",
+ "Numpad 0",
+ "Numpad 1",
+ "Numpad 2",
+ "Numpad 3",
+ "Numpad 4",
+ "Numpad 5",
+ "Numpad 6",
+ "Numpad 7",
+ "Numpad 8",
+ "Numpad 9",
+ "Numpad Divide",
+ "Numpad Multiply",
+ "Numpad Subtract",
+ "Numpad Add",
+ "Numpad Dot",
+ "Numpad Comma",
+ "Numpad Enter",
+ "Numpad Equals",
+ "Numpad Left Paren",
+ "Numpad Right Paren",
+ "Volume Mute",
+ "Info",
+ "Channel Up",
+ "Channel Down",
+ "Zoom In",
+ "Zoom Out",
+ "TV",
+ "Window",
+ "Guide",
+ "DVR",
+ "Bookmark",
+ "Captions",
+ "Settings",
+ "TV Power",
+ "TV Input",
+ "STB Power",
+ "STB Input",
+ "AVR Power",
+ "AVR Input",
+ "Prog Red",
+ "Prog Green",
+ "Prog Yellow",
+ "Prog Blue",
+ "App Switch",
+ "Button 1",
+ "Button 2",
+ "Button 3",
+ "Button 4",
+ "Button 5",
+ "Button 6",
+ "Button 7",
+ "Button 8",
+ "Button 9",
+ "Button 10",
+ "Button 11",
+ "Button 12",
+ "Button 13",
+ "Button 14",
+ "Button 15",
+ "Button 16",
+ "Language Switch",
+ "Manner Mode",
+ "3D Mode",
+ "Contacts",
+ "Calendar",
+ "Music",
+ "Calculator",
+ "Zenkaku Hankaku",
+ "Eisu",
+ "Henkan",
+ "Muhenkan",
+ "Katakana Hiragana",
+ "Yen",
+ "Ro",
+ "Kana",
+ "Assist",
+ "Brightness Down",
+ "Brightness Up",
+ "Audio Track",
+ "Sleep",
+ "Wakeup",
+ "Pairing",
+ "Top Menu",
+ "11",
+ "12",
+ "Last Channel",
+ "Data Service",
+ "Voice Assist",
+ "Radio Service",
+ "Teletext",
+ "Number Entry",
+ "Terrestrial Analog",
+ "Terrestrial Digital",
+ "Satellite",
+ "Satellite BS",
+ "Satellite CS",
+ "Satellite Service",
+ "Network",
+ "Antenna Cable",
+ "Input HDMI 1",
+ "Input HDMI 2",
+ "Input HDMI 3",
+ "Input HDMI 4",
+ "Input Composite 1",
+ "Input Composite 2",
+ "Input Component 1",
+ "Input Component 2",
+ "Input VGA 1",
+ "Audio Description",
+ "Audio Description Mix Up",
+ "Audio Description Mix Down",
+ "Zoom Mode",
+ "Contents Menu",
+ "Media Context Menu",
+ "Timer Programming",
+ "Help",
+ "Navigate Previous",
+ "Navigate Next",
+ "Navigate In",
+ "Navigate Out",
+ "Stem Primary",
+ "Stem 1",
+ "Stem 2",
+ "Stem 3",
+ "Up Left",
+ "Down Left",
+ "Up Right",
+ "Down Right",
+ "Skip Forward",
+ "Skip Backward",
+ "Step Forward",
+ "Step Backward",
+ "Soft Sleep",
+ "Cut",
+ "Copy",
+ "Paste",
+ "System Navigation Up",
+ "System Navigation Down",
+ "System Navigation Left",
+ "System Navigation Right",
+ "All Apps",
+ "Refresh",
+ "Thumbs Up",
+ "Thumbs Down",
+ "Profile Switch",
+};
+
+std::string ConstructKeyName(int keycode)
+{
+ return std::string(KEYCODE_NAMES[keycode]);
+}
+
+std::string ConstructAxisNamePrefix(int source)
+{
+ // A device is allowed to have two axes with the same axis ID but different source IDs,
+ // so we have to make sure to include the source in the axis name.
+
+ static const std::unordered_map<int, std::string> source_names{
+ {AINPUT_SOURCE_KEYBOARD, "Keyboard"},
+ {AINPUT_SOURCE_DPAD, "Dpad"},
+ {AINPUT_SOURCE_GAMEPAD, "Gamepad"},
+ {AINPUT_SOURCE_TOUCHSCREEN, "Touch"},
+ {AINPUT_SOURCE_MOUSE, "Cursor"},
+ {AINPUT_SOURCE_STYLUS, "Stylus"},
+ {AINPUT_SOURCE_BLUETOOTH_STYLUS, "BTStylus"},
+ {AINPUT_SOURCE_TRACKBALL, "Trackball"},
+ {AINPUT_SOURCE_MOUSE_RELATIVE, "Mouse"},
+ {AINPUT_SOURCE_TOUCHPAD, "Touchpad"},
+ {AINPUT_SOURCE_TOUCH_NAVIGATION, "Touchnav"},
+ {AINPUT_SOURCE_JOYSTICK, "Axis"}, // The typical source for all axes on a gamepad
+ {AINPUT_SOURCE_HDMI, "HDMI"},
+ {AINPUT_SOURCE_SENSOR, "Sensor"},
+ {AINPUT_SOURCE_ROTARY_ENCODER, "Rotary"},
+ };
+
+ const auto it = source_names.find(source);
+ if (it != source_names.end())
+ return fmt::format("{} ", it->second);
+ else
+ return fmt::format("Axis {:08x}/", source);
+}
+
+std::string ConstructAxisName(int source, int axis, bool negative)
+{
+ const char sign = negative ? '-' : '+';
+ return fmt::format("{}{}{}", ConstructAxisNamePrefix(source), axis, sign);
+}
+
+std::shared_ptr<ciface::Core::Device> FindDevice(jint device_id)
+{
+ const auto it = s_device_id_to_device_qualifier.find(device_id);
+ if (it == s_device_id_to_device_qualifier.end())
+ {
+ ERROR_LOG_FMT(CONTROLLERINTERFACE, "Could not find device ID {}", device_id);
+ return nullptr;
+ }
+
+ const ciface::Core::DeviceQualifier& qualifier = it->second;
+ std::shared_ptr<ciface::Core::Device> device = g_controller_interface.FindDevice(qualifier);
+ if (!device)
+ {
+ ERROR_LOG_FMT(CONTROLLERINTERFACE, "Could not find device {}", qualifier.ToString());
+ return nullptr;
+ }
+
+ return device;
+}
+
+} // namespace
namespace ciface::Android
{
-static bool s_accelerometer_enabled = false;
-static bool s_gyroscope_enabled = false;
+class AndroidInput : public Core::Device::Input
+{
+public:
+ explicit AndroidInput(std::string name) : m_name(std::move(name))
+ {
+ DEBUG_LOG_FMT(CONTROLLERINTERFACE, "Created {}", m_name);
+ }
+
+ std::string GetName() const override { return m_name; }
+
+ ControlState GetState() const override
+ {
+ m_last_polled.store(Clock::now(), std::memory_order_relaxed);
+ return m_state.load(std::memory_order_relaxed);
+ }
+
+ void SetState(ControlState state) { m_state.store(state, std::memory_order_relaxed); }
+
+ Clock::time_point GetLastPolled() const { return m_last_polled.load(std::memory_order_relaxed); }
+
+private:
+ std::string m_name;
+ std::atomic<ControlState> m_state = 0;
+ mutable std::atomic<Clock::time_point> m_last_polled{};
+};
+
+class AndroidKey final : public AndroidInput
+{
+public:
+ explicit AndroidKey(int keycode) : AndroidInput(ConstructKeyName(keycode)) {}
+};
+
+class AndroidAxis : public AndroidInput
+{
+public:
+ AndroidAxis(int source, int axis, bool negative)
+ : AndroidInput(ConstructAxisName(source, axis, negative)), m_negative(negative)
+ {
+ }
+
+ AndroidAxis(std::string name, bool negative) : AndroidInput(std::move(name)), m_negative(negative)
+ {
+ }
+
+ ControlState GetState() const override
+ {
+ return m_negative ? -AndroidInput::GetState() : AndroidInput::GetState();
+ }
+
+private:
+ bool m_negative;
+};
+
+class AndroidSensorAxis final : public AndroidAxis
+{
+public:
+ // This class does not create its own global reference to the passed-in sensor_event_listener.
+ // That is, it's up to the device that contains this axis to keep sensor_event_listener valid.
+ // It does however create its own global reference to the passed-in name.
+ AndroidSensorAxis(JNIEnv* env, jobject sensor_event_listener, jstring j_name, bool negative)
+ : AndroidAxis(GetJString(env, j_name), negative),
+ m_sensor_event_listener(sensor_event_listener),
+ m_j_name(reinterpret_cast<jstring>(env->NewGlobalRef(j_name)))
+ {
+ }
+
+ ~AndroidSensorAxis() { IDCache::GetEnvForThread()->DeleteGlobalRef(m_j_name); }
+
+ bool IsDetectable() const override { return false; }
+
+ ControlState GetState() const override
+ {
+ if (m_is_suspended.load(std::memory_order_relaxed))
+ {
+ IDCache::GetEnvForThread()->CallVoidMethod(
+ m_sensor_event_listener, s_sensor_event_listener_request_unsuspend_sensor, m_j_name);
+
+ // m_is_suspended is intentionally not updated here. To prevent the C++ suspended status from
+ // ending up desynced with the Java suspended status, we only update m_is_suspended when Java
+ // calls notifySensorSuspendedState (which calls NotifyIsSuspended). This way, Java is the
+ // authoritative source for the suspended status, and C++ mirrors it (possibly with a delay).
+ }
+
+ return AndroidAxis::GetState();
+ }
+
+ void NotifyIsSuspended(bool is_suspended)
+ {
+ m_is_suspended.store(is_suspended, std::memory_order_relaxed);
+ }
+
+private:
+ const jobject m_sensor_event_listener;
+ const jstring m_j_name;
+ std::atomic<bool> m_is_suspended = true;
+};
+
+class AndroidMotor : public Core::Device::Output
+{
+public:
+ AndroidMotor(JNIEnv* env, jobject vibrator, jint id)
+ : m_vibrator(env->NewGlobalRef(vibrator)), m_id(id)
+ {
+ }
+
+ ~AndroidMotor() { IDCache::GetEnvForThread()->DeleteGlobalRef(m_vibrator); }
+
+ std::string GetName() const override { return "Motor " + std::to_string(m_id); }
+
+ void SetState(ControlState state) override
+ {
+ ControlState old_state = m_state.exchange(state, std::memory_order_relaxed);
+
+ if (old_state < 0.5 && state >= 0.5)
+ {
+ IDCache::GetEnvForThread()->CallStaticVoidMethod(s_controller_interface_class,
+ s_controller_interface_vibrate, m_vibrator);
+ }
+ }
+
+private:
+ const jobject m_vibrator;
+ const jint m_id;
+ std::atomic<ControlState> m_state = 0;
+};
+
+class AndroidDevice final : public Core::Device
+{
+public:
+ AndroidDevice(JNIEnv* env, jobject input_device)
+ : m_sensor_event_listener(AddSensors(env, input_device)),
+ m_source(env->CallIntMethod(input_device, s_input_device_get_sources)),
+ m_controller_number(env->CallIntMethod(input_device, s_input_device_get_controller_number))
+ {
+ jstring j_name =
+ reinterpret_cast<jstring>(env->CallObjectMethod(input_device, s_input_device_get_name));
+ m_name = GetJString(env, j_name);
+ env->DeleteLocalRef(j_name);
+
+ DEBUG_LOG_FMT(CONTROLLERINTERFACE, "Sources for {}: {:08x}", GetQualifiedName(), m_source);
+
+ AddKeys(env, input_device);
+ AddAxes(env, input_device);
+ AddMotors(env, input_device);
+ }
+
+ // Constructor for the device added by Dolphin to contain sensor inputs
+ AndroidDevice(JNIEnv* env, std::string name)
+ : m_sensor_event_listener(AddSensors(env, nullptr)), m_source(AINPUT_SOURCE_SENSOR),
+ m_controller_number(0), m_name(std::move(name))
+ {
+ AddSystemMotors(env);
+ }
+
+ ~AndroidDevice()
+ {
+ if (m_sensor_event_listener)
+ IDCache::GetEnvForThread()->DeleteGlobalRef(m_sensor_event_listener);
+ }
+
+ std::string GetName() const override { return m_name; }
+
+ std::string GetSource() const override { return "Android"; }
+
+ std::optional<int> GetPreferredId() const override
+ {
+ return m_controller_number != 0 ? std::make_optional(m_controller_number) : std::nullopt;
+ }
+
+ int GetSortPriority() const override
+ {
+ // If m_controller_number is non-zero, Android considers this to be a gamepad
+ if (m_controller_number != 0)
+ return 0;
+
+ if ((m_source & AINPUT_SOURCE_KEYBOARD) != 0)
+ return -1;
+
+ if ((m_source & (AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_MOUSE_RELATIVE)) != 0)
+ return -2;
+
+ return -3;
+ }
+
+ jobject GetSensorEventListener() { return m_sensor_event_listener; }
+
+private:
+ void AddKeys(JNIEnv* env, jobject input_device)
+ {
+ jbooleanArray keys_array = reinterpret_cast<jbooleanArray>(
+ env->CallObjectMethod(input_device, s_input_device_has_keys, s_keycodes_array));
+ jboolean* keys = env->GetBooleanArrayElements(keys_array, nullptr);
+ jsize keys_count = env->GetArrayLength(keys_array);
+ for (jsize i = 0; i < keys_count; ++i)
+ {
+ // These specific keys never get delivered to applications,
+ // so there's no point in letting users try to map them
+ if (i == AKEYCODE_HOME || i == AKEYCODE_ASSIST || i == AKEYCODE_VOICE_ASSIST)
+ continue;
+
+ if (keys[i])
+ AddInput(new AndroidKey(i));
+ }
+ env->ReleaseBooleanArrayElements(keys_array, keys, JNI_ABORT);
+ env->DeleteLocalRef(keys_array);
+ }
+
+ void AddAxes(JNIEnv* env, jobject input_device)
+ {
+ jobject motion_ranges_list =
+ env->CallObjectMethod(input_device, s_input_device_get_motion_ranges);
+ jint motion_ranges_count = env->CallIntMethod(motion_ranges_list, s_list_size);
+ for (jint i = 0; i < motion_ranges_count; ++i)
+ {
+ jobject motion_range = env->CallObjectMethod(motion_ranges_list, s_list_get, i);
+
+ jint source = env->CallIntMethod(motion_range, s_motion_range_get_source);
+ jint axis = env->CallIntMethod(motion_range, s_motion_range_get_axis);
+ jfloat min = env->CallFloatMethod(motion_range, s_motion_range_get_min);
+ jfloat max = env->CallFloatMethod(motion_range, s_motion_range_get_max);
+
+ env->DeleteLocalRef(motion_range);
+
+ AndroidAxis* positive = nullptr;
+ AndroidAxis* negative = nullptr;
+ if (max > 0)
+ positive = new AndroidAxis(source, axis, false);
+ if (min < 0)
+ negative = new AndroidAxis(source, axis, true);
+
+ if (positive && negative)
+ AddAnalogInputs(positive, negative);
+ else if (positive || negative)
+ AddInput(positive ? positive : negative);
+ }
+ env->DeleteLocalRef(motion_ranges_list);
+ }
+
+ jobject AddSensors(JNIEnv* env, jobject input_device)
+ {
+ jobject local_sensor_event_listener;
+ if (input_device)
+ {
+ local_sensor_event_listener =
+ env->NewObject(s_sensor_event_listener_class,
+ s_sensor_event_listener_constructor_input_device, input_device);
+ }
+ else
+ {
+ local_sensor_event_listener =
+ env->NewObject(s_sensor_event_listener_class, s_sensor_event_listener_constructor);
+ }
+
+ jobject sensor_event_listener = env->NewGlobalRef(local_sensor_event_listener);
+
+ env->DeleteLocalRef(local_sensor_event_listener);
+
+ jobjectArray j_axis_names = reinterpret_cast<jobjectArray>(
+ env->CallObjectMethod(sensor_event_listener, s_sensor_event_listener_get_axis_names));
+
+ jbooleanArray j_negative_axes = reinterpret_cast<jbooleanArray>(
+ env->CallObjectMethod(sensor_event_listener, s_sensor_event_listener_get_negative_axes));
+ jboolean* negative_axes = env->GetBooleanArrayElements(j_negative_axes, nullptr);
+
+ const jsize axis_count = env->GetArrayLength(j_axis_names);
+ ASSERT(axis_count == env->GetArrayLength(j_negative_axes));
+ for (jsize i = 0; i < axis_count; ++i)
+ {
+ const jstring axis_name =
+ reinterpret_cast<jstring>(env->GetObjectArrayElement(j_axis_names, i));
+ AddInput(new AndroidSensorAxis(env, sensor_event_listener, axis_name, negative_axes[i]));
+ env->DeleteLocalRef(axis_name);
+ }
+
+ env->DeleteLocalRef(j_axis_names);
+ env->ReleaseBooleanArrayElements(j_negative_axes, negative_axes, 0);
+ env->DeleteLocalRef(j_negative_axes);
+
+ return sensor_event_listener;
+ }
+
+ void AddMotors(JNIEnv* env, jobject input_device)
+ {
+ jobject vibrator_manager = env->CallStaticObjectMethod(
+ s_controller_interface_class, s_controller_interface_get_vibrator_manager, input_device);
+ AddMotorsFromManager(env, vibrator_manager);
+ env->DeleteLocalRef(vibrator_manager);
+ }
+
+ void AddSystemMotors(JNIEnv* env)
+ {
+ jobject vibrator_manager = env->CallStaticObjectMethod(
+ s_controller_interface_class, s_controller_interface_get_system_vibrator_manager);
+ AddMotorsFromManager(env, vibrator_manager);
+ env->DeleteLocalRef(vibrator_manager);
+ }
+
+ void AddMotorsFromManager(JNIEnv* env, jobject vibrator_manager)
+ {
+ jintArray j_vibrator_ids = reinterpret_cast<jintArray>(
+ env->CallObjectMethod(vibrator_manager, s_dolphin_vibrator_manager_get_vibrator_ids));
+ jint* vibrator_ids = env->GetIntArrayElements(j_vibrator_ids, nullptr);
+
+ jint size = env->GetArrayLength(j_vibrator_ids);
+ for (jint i = 0; i < size; ++i)
+ {
+ jobject vibrator =
+ env->CallObjectMethod(vibrator_manager, s_dolphin_vibrator_manager_get_vibrator, i);
+ AddOutput(new AndroidMotor(env, vibrator, i));
+ env->DeleteLocalRef(vibrator);
+ }
+
+ env->ReleaseIntArrayElements(j_vibrator_ids, vibrator_ids, 0);
+ env->DeleteLocalRef(j_vibrator_ids);
+ }
+
+ const jobject m_sensor_event_listener;
+ const int m_source;
+ const int m_controller_number;
+ std::string m_name;
+};
+
+// Creates an array that contains every possible keycode
+static jintArray CreateKeyCodesArray(JNIEnv* env)
+{
+ jintArray keycodes_array = env->NewIntArray(MAX_KEYCODE + 1);
+
+ int* keycodes = env->GetIntArrayElements(keycodes_array, nullptr);
+ for (int i = 0; i <= MAX_KEYCODE; ++i)
+ keycodes[i] = i;
+ env->ReleaseIntArrayElements(keycodes_array, keycodes, 0);
+
+ return keycodes_array;
+}
-void SetMotionSensorsEnabled(bool accelerometer_enabled, bool gyroscope_enabled)
+void Init()
{
- const bool any_changes =
- s_accelerometer_enabled != accelerometer_enabled || s_gyroscope_enabled != gyroscope_enabled;
+ JNIEnv* env = IDCache::GetEnvForThread();
+
+ const jclass list_class = env->FindClass("java/util/List");
+ s_list_class = reinterpret_cast<jclass>(env->NewGlobalRef(list_class));
+ s_list_get = env->GetMethodID(s_list_class, "get", "(I)Ljava/lang/Object;");
+ s_list_size = env->GetMethodID(s_list_class, "size", "()I");
+ env->DeleteLocalRef(list_class);
+
+ const jclass input_device_class = env->FindClass("android/view/InputDevice");
+ s_input_device_class = reinterpret_cast<jclass>(env->NewGlobalRef(input_device_class));
+ s_input_device_get_device_ids =
+ env->GetStaticMethodID(s_input_device_class, "getDeviceIds", "()[I");
+ s_input_device_get_device =
+ env->GetStaticMethodID(s_input_device_class, "getDevice", "(I)Landroid/view/InputDevice;");
+ s_input_device_get_controller_number =
+ env->GetMethodID(s_input_device_class, "getControllerNumber", "()I");
+ s_input_device_get_id = env->GetMethodID(s_input_device_class, "getId", "()I");
+ s_input_device_get_motion_ranges =
+ env->GetMethodID(s_input_device_class, "getMotionRanges", "()Ljava/util/List;");
+ s_input_device_get_name =
+ env->GetMethodID(s_input_device_class, "getName", "()Ljava/lang/String;");
+ s_input_device_get_sources = env->GetMethodID(s_input_device_class, "getSources", "()I");
+ s_input_device_has_keys = env->GetMethodID(s_input_device_class, "hasKeys", "([I)[Z");
+ env->DeleteLocalRef(input_device_class);
+
+ const jclass motion_range_class = env->FindClass("android/view/InputDevice$MotionRange");
+ s_motion_range_class = reinterpret_cast<jclass>(env->NewGlobalRef(motion_range_class));
+ s_motion_range_get_axis = env->GetMethodID(s_motion_range_class, "getAxis", "()I");
+ s_motion_range_get_max = env->GetMethodID(s_motion_range_class, "getMax", "()F");
+ s_motion_range_get_min = env->GetMethodID(s_motion_range_class, "getMin", "()F");
+ s_motion_range_get_source = env->GetMethodID(s_motion_range_class, "getSource", "()I");
+ env->DeleteLocalRef(motion_range_class);
+
+ const jclass input_event_class = env->FindClass("android/view/InputEvent");
+ s_input_event_class = reinterpret_cast<jclass>(env->NewGlobalRef(input_event_class));
+ s_input_event_get_device =
+ env->GetMethodID(s_input_event_class, "getDevice", "()Landroid/view/InputDevice;");
+
+ const jclass key_event_class = env->FindClass("android/view/KeyEvent");
+ s_key_event_class = reinterpret_cast<jclass>(env->NewGlobalRef(key_event_class));
+ s_key_event_get_action = env->GetMethodID(s_key_event_class, "getAction", "()I");
+ s_key_event_get_keycode = env->GetMethodID(s_key_event_class, "getKeyCode", "()I");
- s_accelerometer_enabled = accelerometer_enabled;
- s_gyroscope_enabled = gyroscope_enabled;
+ const jclass motion_event_class = env->FindClass("android/view/MotionEvent");
+ s_motion_event_class = reinterpret_cast<jclass>(env->NewGlobalRef(motion_event_class));
+ s_motion_event_get_axis_value = env->GetMethodID(s_motion_event_class, "getAxisValue", "(I)F");
+ s_motion_event_get_source = env->GetMethodID(s_motion_event_class, "getSource", "()I");
- if (any_changes)
- g_controller_interface.RefreshDevices();
+ const jclass controller_interface_class =
+ env->FindClass("org/dolphinemu/dolphinemu/features/input/model/ControllerInterface");
+ s_controller_interface_class =
+ reinterpret_cast<jclass>(env->NewGlobalRef(controller_interface_class));
+ s_controller_interface_register_input_device_listener =
+ env->GetStaticMethodID(s_controller_interface_class, "registerInputDeviceListener", "()V");
+ s_controller_interface_unregister_input_device_listener =
+ env->GetStaticMethodID(s_controller_interface_class, "unregisterInputDeviceListener", "()V");
+ s_controller_interface_get_vibrator_manager =
+ env->GetStaticMethodID(s_controller_interface_class, "getVibratorManager",
+ "(Landroid/view/InputDevice;)Lorg/dolphinemu/dolphinemu/features/"
+ "input/model/DolphinVibratorManager;");
+ s_controller_interface_get_system_vibrator_manager = env->GetStaticMethodID(
+ s_controller_interface_class, "getSystemVibratorManager",
+ "()Lorg/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager;");
+ s_controller_interface_vibrate =
+ env->GetStaticMethodID(s_controller_interface_class, "vibrate", "(Landroid/os/Vibrator;)V");
+
+ const jclass sensor_event_listener_class =
+ env->FindClass("org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener");
+ s_sensor_event_listener_class =
+ reinterpret_cast<jclass>(env->NewGlobalRef(sensor_event_listener_class));
+ s_sensor_event_listener_constructor =
+ env->GetMethodID(s_sensor_event_listener_class, "<init>", "()V");
+ s_sensor_event_listener_constructor_input_device =
+ env->GetMethodID(s_sensor_event_listener_class, "<init>", "(Landroid/view/InputDevice;)V");
+ s_sensor_event_listener_set_device_qualifier = env->GetMethodID(
+ s_sensor_event_listener_class, "setDeviceQualifier", "(Ljava/lang/String;)V");
+ s_sensor_event_listener_request_unsuspend_sensor = env->GetMethodID(
+ s_sensor_event_listener_class, "requestUnsuspendSensor", "(Ljava/lang/String;)V");
+ s_sensor_event_listener_get_axis_names =
+ env->GetMethodID(s_sensor_event_listener_class, "getAxisNames", "()[Ljava/lang/String;");
+ s_sensor_event_listener_get_negative_axes =
+ env->GetMethodID(s_sensor_event_listener_class, "getNegativeAxes", "()[Z");
+
+ const jclass dolphin_vibrator_manager_class =
+ env->FindClass("org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager");
+ s_dolphin_vibrator_manager_class =
+ reinterpret_cast<jclass>(env->NewGlobalRef(dolphin_vibrator_manager_class));
+ s_dolphin_vibrator_manager_get_vibrator =
+ env->GetMethodID(s_dolphin_vibrator_manager_class, "getVibrator", "(I)Landroid/os/Vibrator;");
+ s_dolphin_vibrator_manager_get_vibrator_ids =
+ env->GetMethodID(s_dolphin_vibrator_manager_class, "getVibratorIds", "()[I");
+
+ jintArray keycodes_array = CreateKeyCodesArray(env);
+ s_keycodes_array = reinterpret_cast<jintArray>(env->NewGlobalRef(keycodes_array));
+ env->DeleteLocalRef(keycodes_array);
+
+ env->CallStaticVoidMethod(s_controller_interface_class,
+ s_controller_interface_register_input_device_listener);
+}
+
+void Shutdown()
+{
+ JNIEnv* env = IDCache::GetEnvForThread();
+
+ env->CallStaticVoidMethod(s_controller_interface_class,
+ s_controller_interface_unregister_input_device_listener);
+
+ env->DeleteGlobalRef(s_input_device_class);
+ env->DeleteGlobalRef(s_motion_range_class);
+ env->DeleteGlobalRef(s_input_event_class);
+ env->DeleteGlobalRef(s_key_event_class);
+ env->DeleteGlobalRef(s_motion_event_class);
+ env->DeleteGlobalRef(s_controller_interface_class);
+ env->DeleteGlobalRef(s_sensor_event_listener_class);
+ env->DeleteGlobalRef(s_dolphin_vibrator_manager_class);
+ env->DeleteGlobalRef(s_keycodes_array);
+}
+
+static void AddDevice(JNIEnv* env, int device_id)
+{
+ jobject input_device =
+ env->CallStaticObjectMethod(s_input_device_class, s_input_device_get_device, device_id);
+
+ auto device = std::make_shared<AndroidDevice>(env, input_device);
+
+ env->DeleteLocalRef(input_device);
+
+ if (device->Inputs().empty() && device->Outputs().empty())
+ return;
+
+ g_controller_interface.AddDevice(device);
+
+ Core::DeviceQualifier qualifier;
+ qualifier.FromDevice(device.get());
+
+ INFO_LOG_FMT(CONTROLLERINTERFACE, "Added device ID {} as {}", device_id,
+ device->GetQualifiedName());
+ s_device_id_to_device_qualifier.emplace(device_id, qualifier);
+
+ jstring j_qualifier = ToJString(env, qualifier.ToString());
+ env->CallVoidMethod(device->GetSensorEventListener(),
+ s_sensor_event_listener_set_device_qualifier, j_qualifier);
+ env->DeleteLocalRef(j_qualifier);
+}
+
+static void AddSensorDevice(JNIEnv* env)
+{
+ // Device sensors (accelerometer, etc.) aren't associated with any Android InputDevice.
+ // Create an otherwise empty Dolphin input device so that they have somewhere to live.
+
+ auto device = std::make_shared<AndroidDevice>(env, "Device Sensors");
+
+ if (device->Inputs().empty() && device->Outputs().empty())
+ return;
+
+ g_controller_interface.AddDevice(device);
+
+ Core::DeviceQualifier qualifier;
+ qualifier.FromDevice(device.get());
+
+ INFO_LOG_FMT(CONTROLLERINTERFACE, "Added sensor device as {}", device->GetQualifiedName());
+
+ jstring j_qualifier = ToJString(env, qualifier.ToString());
+ env->CallVoidMethod(device->GetSensorEventListener(),
+ s_sensor_event_listener_set_device_qualifier, j_qualifier);
+ env->DeleteLocalRef(j_qualifier);
}
void PopulateDevices()
{
- for (int i = 0; i < 8; ++i)
- g_controller_interface.AddDevice(std::make_shared<ciface::Touch::Touchscreen>(
- i, s_accelerometer_enabled, s_gyroscope_enabled));
+ INFO_LOG_FMT(CONTROLLERINTERFACE, "Android populating devices");
+
+ JNIEnv* env = IDCache::GetEnvForThread();
+
+ jintArray device_ids_array = reinterpret_cast<jintArray>(
+ env->CallStaticObjectMethod(s_input_device_class, s_input_device_get_device_ids));
+ int* device_ids = env->GetIntArrayElements(device_ids_array, nullptr);
+ jsize device_ids_count = env->GetArrayLength(device_ids_array);
+ for (jsize i = 0; i < device_ids_count; ++i)
+ AddDevice(env, device_ids[i]);
+ env->ReleaseIntArrayElements(device_ids_array, device_ids, JNI_ABORT);
+ env->DeleteLocalRef(device_ids_array);
+
+ AddSensorDevice(env);
}
+
} // namespace ciface::Android
+
+extern "C" {
+
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchKeyEvent(
+ JNIEnv* env, jclass, jobject key_event)
+{
+ const jint action = env->CallIntMethod(key_event, s_key_event_get_action);
+ ControlState state;
+ switch (action)
+ {
+ case AKEY_EVENT_ACTION_DOWN:
+ state = 1;
+ break;
+ case AKEY_EVENT_ACTION_UP:
+ state = 0;
+ break;
+ default:
+ return JNI_FALSE;
+ }
+
+ const jobject input_device = env->CallObjectMethod(key_event, s_input_event_get_device);
+ const jint device_id = env->CallIntMethod(input_device, s_input_device_get_id);
+ env->DeleteLocalRef(input_device);
+ const std::shared_ptr<ciface::Core::Device> device = FindDevice(device_id);
+ if (!device)
+ return JNI_FALSE;
+
+ const jint keycode = env->CallIntMethod(key_event, s_key_event_get_keycode);
+ const std::string input_name = ConstructKeyName(keycode);
+
+ ciface::Core::Device::Input* input = device->FindInput(input_name);
+ if (!input)
+ {
+ ERROR_LOG_FMT(CONTROLLERINTERFACE, "Could not find input {} in device {}", input_name,
+ device->GetQualifiedName());
+ return false;
+ }
+
+ auto casted_input = static_cast<ciface::Android::AndroidInput*>(input);
+ casted_input->SetState(state);
+ const Clock::time_point last_polled = casted_input->GetLastPolled();
+
+ DEBUG_LOG_FMT(CONTROLLERINTERFACE, "Set {} of {} to {}", input_name, device->GetQualifiedName(),
+ state);
+
+ return last_polled >= Clock::now() - ACTIVE_INPUT_TIMEOUT;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchGenericMotionEvent(
+ JNIEnv* env, jclass, jobject motion_event)
+{
+ const jobject input_device = env->CallObjectMethod(motion_event, s_input_event_get_device);
+ const jint device_id = env->CallIntMethod(input_device, s_input_device_get_id);
+ env->DeleteLocalRef(input_device);
+ const std::shared_ptr<ciface::Core::Device> device = FindDevice(device_id);
+ if (!device)
+ return JNI_FALSE;
+
+ const jint source = env->CallIntMethod(motion_event, s_motion_event_get_source);
+ const std::string axis_name_prefix = ConstructAxisNamePrefix(source);
+
+ Clock::time_point last_polled{};
+
+ for (ciface::Core::Device::Input* input : device->Inputs())
+ {
+ const std::string input_name = input->GetName();
+ if (input_name.starts_with(axis_name_prefix))
+ {
+ const std::string axis_id_str = input_name.substr(
+ axis_name_prefix.size(), input_name.size() - axis_name_prefix.size() - sizeof('+'));
+
+ int axis_id;
+ if (!TryParse(axis_id_str, &axis_id))
+ {
+ ERROR_LOG_FMT(CONTROLLERINTERFACE, "Failed to parse \"{}\" from \"{}\" as axis ID",
+ axis_id_str, input_name);
+ continue;
+ }
+
+ float value = env->CallFloatMethod(motion_event, s_motion_event_get_axis_value, axis_id);
+
+ auto casted_input = static_cast<ciface::Android::AndroidInput*>(input);
+ casted_input->SetState(value);
+ last_polled = std::max(last_polled, casted_input->GetLastPolled());
+
+ DEBUG_LOG_FMT(CONTROLLERINTERFACE, "Set {} of {} to {}", input_name,
+ device->GetQualifiedName(), value);
+ }
+ }
+
+ return last_polled >= Clock::now() - ACTIVE_INPUT_TIMEOUT;
+}
+
+JNIEXPORT jboolean JNICALL
+Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_dispatchSensorEvent(
+ JNIEnv* env, jclass, jstring j_device_qualifier, jstring j_axis_name, jfloat value)
+{
+ ciface::Core::DeviceQualifier device_qualifier;
+ device_qualifier.FromString(GetJString(env, j_device_qualifier));
+ const std::shared_ptr<ciface::Core::Device> device =
+ g_controller_interface.FindDevice(device_qualifier);
+ if (!device)
+ return JNI_FALSE;
+
+ const std::string axis_name = GetJString(env, j_axis_name);
+
+ Clock::time_point last_polled{};
+
+ for (ciface::Core::Device::Input* input : device->Inputs())
+ {
+ const std::string input_name = input->GetName();
+ if (input_name == axis_name)
+ {
+ auto casted_input = static_cast<ciface::Android::AndroidInput*>(input);
+ casted_input->SetState(value);
+ last_polled = std::max(last_polled, casted_input->GetLastPolled());
+ }
+ }
+
+ return last_polled >= Clock::now() - ACTIVE_INPUT_TIMEOUT;
+}
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_notifySensorSuspendedState(
+ JNIEnv* env, jclass, jstring j_device_qualifier, jobjectArray j_axis_names, jboolean suspended)
+{
+ ciface::Core::DeviceQualifier device_qualifier;
+ device_qualifier.FromString(GetJString(env, j_device_qualifier));
+ const std::shared_ptr<ciface::Core::Device> device =
+ g_controller_interface.FindDevice(device_qualifier);
+ if (!device)
+ return;
+
+ const std::vector<std::string> axis_names = JStringArrayToVector(env, j_axis_names);
+
+ for (ciface::Core::Device::Input* input : device->Inputs())
+ {
+ const std::string input_name = input->GetName();
+ if (std::find(axis_names.begin(), axis_names.end(), input_name) != axis_names.end())
+ {
+ auto casted_input = static_cast<ciface::Android::AndroidSensorAxis*>(input);
+ casted_input->NotifyIsSuspended(static_cast<bool>(suspended));
+ }
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_refreshDevices(JNIEnv* env,
+ jclass)
+{
+ g_controller_interface.RefreshDevices();
+}
+
+JNIEXPORT jobjectArray JNICALL
+Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_getAllDeviceStrings(
+ JNIEnv* env, jclass)
+{
+ return VectorToJStringArray(env, g_controller_interface.GetAllDeviceStrings());
+}
+
+JNIEXPORT jobject JNICALL
+Java_org_dolphinemu_dolphinemu_features_input_model_ControllerInterface_getDevice(
+ JNIEnv* env, jclass, jstring j_device_string)
+{
+ ciface::Core::DeviceQualifier qualifier;
+ qualifier.FromString(GetJString(env, j_device_string));
+ return CoreDeviceToJava(env, g_controller_interface.FindDevice(qualifier));
+}
+}
diff --git a/Source/Core/InputCommon/ControllerInterface/Android/Android.h b/Source/Core/InputCommon/ControllerInterface/Android/Android.h
index 2cc3c5a22b..8b8478d7ac 100644
--- a/Source/Core/InputCommon/ControllerInterface/Android/Android.h
+++ b/Source/Core/InputCommon/ControllerInterface/Android/Android.h
@@ -1,11 +1,13 @@
-// Copyright 2008 Dolphin Emulator Project
+// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
namespace ciface::Android
{
-void SetMotionSensorsEnabled(bool accelerometer_enabled, bool gyroscope_enabled);
+void Init();
+void Shutdown();
void PopulateDevices();
+
} // namespace ciface::Android