2 * Copyright (C) 2016-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "PeripheralBusAndroid.h"
11 #include "AndroidJoystickTranslator.h"
12 #include "input/joysticks/JoystickTypes.h"
13 #include "peripherals/addons/PeripheralAddonTranslator.h"
14 #include "peripherals/devices/PeripheralJoystick.h"
15 #include "utils/StringUtils.h"
16 #include "utils/log.h"
18 #include "platform/android/activity/XBMCApp.h"
19 #include "platform/android/peripherals/AndroidJoystickState.h"
25 #include <android/input.h>
26 #include <android/keycodes.h>
27 #include <androidjni/View.h>
30 using namespace PERIPHERALS
;
32 #define JOYSTICK_PROVIDER_ANDROID "android"
34 // Set this to the final key code in android/keycodes.h
35 const unsigned int KEY_CODE_FINAL
= AKEYCODE_DEMO_APP_4
;
37 static const std::string DeviceLocationPrefix
= "android/inputdevice/";
39 CPeripheralBusAndroid::CPeripheralBusAndroid(CPeripherals
& manager
)
40 : CPeripheralBus("PeripBusAndroid", manager
, PERIPHERAL_BUS_ANDROID
)
42 // we don't need polling as we get notified through the IInputDeviceCallbacks interface
43 m_bNeedsPolling
= false;
45 // register for input device callbacks
46 CXBMCApp::Get().RegisterInputDeviceCallbacks(this);
48 // register for input device events
49 CXBMCApp::Get().RegisterInputDeviceEventHandler(this);
51 // get all currently connected input devices
52 m_scanResults
= GetInputDevices();
55 CPeripheralBusAndroid::~CPeripheralBusAndroid()
57 // unregister from input device events
58 CXBMCApp::Get().UnregisterInputDeviceEventHandler();
60 // unregister from input device callbacks
61 CXBMCApp::Get().UnregisterInputDeviceCallbacks();
64 bool CPeripheralBusAndroid::InitializeProperties(CPeripheral
& peripheral
)
66 if (!CPeripheralBus::InitializeProperties(peripheral
))
69 if (peripheral
.Type() != PERIPHERAL_JOYSTICK
)
71 CLog::Log(LOGWARNING
, "CPeripheralBusAndroid: invalid peripheral type: {}",
72 PeripheralTypeTranslator::TypeToString(peripheral
.Type()));
77 if (!GetDeviceId(peripheral
.Location(), deviceId
))
80 "CPeripheralBusAndroid: failed to initialize properties for peripheral \"{}\"",
81 peripheral
.Location());
85 const CJNIViewInputDevice device
= CXBMCApp::GetInputDevice(deviceId
);
88 CLog::Log(LOGWARNING
, "CPeripheralBusAndroid: failed to get input device with ID {}", deviceId
);
92 CPeripheralJoystick
& joystick
= static_cast<CPeripheralJoystick
&>(peripheral
);
93 if (device
.getControllerNumber() > 0)
94 joystick
.SetRequestedPort(device
.getControllerNumber() - 1);
95 joystick
.SetProvider(JOYSTICK_PROVIDER_ANDROID
);
97 CLog::Log(LOGDEBUG
, "CPeripheralBusAndroid: Initializing device {} \"{}\"", deviceId
,
98 peripheral
.DeviceName());
100 // prepare the joystick state
101 CAndroidJoystickState state
;
102 if (!state
.Initialize(device
))
106 "CPeripheralBusAndroid: failed to initialize the state for input device \"{}\" with ID {}",
107 joystick
.DeviceName(), deviceId
);
111 // fill in the number of buttons, hats and axes
112 joystick
.SetButtonCount(state
.GetButtonCount());
113 joystick
.SetAxisCount(state
.GetAxisCount());
115 std::unique_lock
<CCriticalSection
> lock(m_critSectionStates
);
117 // remember the joystick state
118 m_joystickStates
.insert(std::make_pair(deviceId
, std::move(state
)));
120 CLog::Log(LOGDEBUG
, "CPeripheralBusAndroid: Device has {} buttons and {} axes",
121 joystick
.ButtonCount(), joystick
.AxisCount());
126 bool CPeripheralBusAndroid::InitializeButtonMap(const CPeripheral
& peripheral
,
127 JOYSTICK::IButtonMap
& buttonMap
) const
130 if (!GetDeviceId(peripheral
.Location(), deviceId
))
132 CLog::Log(LOGWARNING
,
133 "CPeripheralBusAndroid: failed to initialize buttonmap due to unknown device ID for "
135 peripheral
.Location());
139 std::unique_lock
<CCriticalSection
> lock(m_critSectionStates
);
141 // get the joystick state
142 auto it
= m_joystickStates
.find(deviceId
);
143 if (it
== m_joystickStates
.end())
145 CLog::Log(LOGWARNING
,
146 "CPeripheralBusAndroid: joystick with device ID {} not found for peripheral \"{}\"",
147 deviceId
, peripheral
.Location());
151 const CAndroidJoystickState
& joystick
= it
->second
;
152 if (joystick
.GetButtonCount() == 0 && joystick
.GetAxisCount() == 0)
155 "CPeripheralBusAndroid: joystick has no buttons or axes for peripheral \"{}\"",
156 peripheral
.Location());
160 if (!joystick
.InitializeButtonMap(buttonMap
))
164 "CPeripheralBusAndroid: failed to initialize joystick buttonmap for peripheral \"{}\"",
165 peripheral
.Location());
172 std::string
CPeripheralBusAndroid::GetAppearance(const CPeripheral
& peripheral
) const
175 if (!GetDeviceId(peripheral
.Location(), deviceId
))
178 std::unique_lock
<CCriticalSection
> lock(m_critSectionStates
);
180 auto it
= m_joystickStates
.find(deviceId
);
181 if (it
== m_joystickStates
.end())
184 const CAndroidJoystickState
& joystick
= it
->second
;
185 return joystick
.GetAppearance();
188 void CPeripheralBusAndroid::Initialise(void)
190 CPeripheralBus::Initialise();
194 void CPeripheralBusAndroid::ProcessEvents()
196 std::vector
<kodi::addon::PeripheralEvent
> events
;
198 std::unique_lock
<CCriticalSection
> lock(m_critSectionStates
);
199 for (auto& joystickState
: m_joystickStates
)
200 joystickState
.second
.GetEvents(events
);
203 for (const auto& event
: events
)
205 PeripheralPtr device
= GetPeripheral(GetDeviceLocation(event
.PeripheralIndex()));
206 if (!device
|| device
->Type() != PERIPHERAL_JOYSTICK
)
209 CPeripheralJoystick
* joystick
= static_cast<CPeripheralJoystick
*>(device
.get());
210 switch (event
.Type())
212 case PERIPHERAL_EVENT_TYPE_DRIVER_BUTTON
:
214 const bool bPressed
= (event
.ButtonState() == JOYSTICK_STATE_BUTTON_PRESSED
);
215 joystick
->OnButtonMotion(event
.DriverIndex(), bPressed
);
218 case PERIPHERAL_EVENT_TYPE_DRIVER_AXIS
:
220 joystick
->OnAxisMotion(event
.DriverIndex(), event
.AxisState());
229 std::unique_lock
<CCriticalSection
> lock(m_critSectionStates
);
230 for (const auto& joystickState
: m_joystickStates
)
232 PeripheralPtr device
= GetPeripheral(GetDeviceLocation(joystickState
.second
.GetDeviceId()));
233 if (!device
|| device
->Type() != PERIPHERAL_JOYSTICK
)
236 static_cast<CPeripheralJoystick
*>(device
.get())->OnInputFrame();
241 void CPeripheralBusAndroid::OnInputDeviceAdded(int deviceId
)
243 const std::string deviceLocation
= GetDeviceLocation(deviceId
);
245 std::unique_lock
<CCriticalSection
> lock(m_critSectionResults
);
246 // add the device to the cached result list
247 const auto& it
= std::find_if(m_scanResults
.m_results
.cbegin(), m_scanResults
.m_results
.cend(),
248 [&deviceLocation
](const PeripheralScanResult
& scanResult
)
249 { return scanResult
.m_strLocation
== deviceLocation
; });
251 if (it
!= m_scanResults
.m_results
.cend())
254 "CPeripheralBusAndroid: ignoring added input device with ID {} because we already "
260 const CJNIViewInputDevice device
= CXBMCApp::GetInputDevice(deviceId
);
263 CLog::Log(LOGWARNING
,
264 "CPeripheralBusAndroid: failed to add input device with ID {} because it couldn't "
270 CLog::Log(LOGDEBUG
, "CPeripheralBusAndroid: Device added:");
271 LogInputDevice(device
);
273 PeripheralScanResult result
;
274 if (!ConvertToPeripheralScanResult(device
, result
))
276 m_scanResults
.m_results
.emplace_back(std::move(result
));
279 CLog::Log(LOGDEBUG
, "CPeripheralBusAndroid: input device with ID {} added", deviceId
);
280 OnDeviceAdded(deviceLocation
);
283 void CPeripheralBusAndroid::OnInputDeviceChanged(int deviceId
)
285 bool changed
= false;
286 const std::string deviceLocation
= GetDeviceLocation(deviceId
);
288 std::unique_lock
<CCriticalSection
> lock(m_critSectionResults
);
289 // change the device in the cached result list
290 for (auto result
= m_scanResults
.m_results
.begin(); result
!= m_scanResults
.m_results
.end();
293 if (result
->m_strLocation
== deviceLocation
)
295 const CJNIViewInputDevice device
= CXBMCApp::GetInputDevice(deviceId
);
298 CLog::Log(LOGWARNING
,
299 "CPeripheralBusAndroid: failed to update input device \"{}\" with ID {} "
300 "because it couldn't be found",
301 result
->m_strDeviceName
, deviceId
);
305 if (!ConvertToPeripheralScanResult(device
, *result
))
308 CLog::Log(LOGINFO
, "CPeripheralBusAndroid: input device \"{}\" with ID {} updated",
309 result
->m_strDeviceName
, deviceId
);
317 OnDeviceChanged(deviceLocation
);
319 CLog::Log(LOGWARNING
,
320 "CPeripheralBusAndroid: failed to update input device with ID {} because it couldn't "
325 void CPeripheralBusAndroid::OnInputDeviceRemoved(int deviceId
)
327 bool removed
= false;
328 const std::string deviceLocation
= GetDeviceLocation(deviceId
);
330 std::unique_lock
<CCriticalSection
> lock(m_critSectionResults
);
331 // remove the device from the cached result list
332 for (auto result
= m_scanResults
.m_results
.begin(); result
!= m_scanResults
.m_results
.end();
335 if (result
->m_strLocation
== deviceLocation
)
337 CLog::Log(LOGINFO
, "CPeripheralBusAndroid: input device \"{}\" with ID {} removed",
338 result
->m_strDeviceName
, deviceId
);
339 m_scanResults
.m_results
.erase(result
);
349 std::unique_lock
<CCriticalSection
> lock(m_critSectionStates
);
350 m_joystickStates
.erase(deviceId
);
353 OnDeviceRemoved(deviceLocation
);
356 CLog::Log(LOGWARNING
,
357 "CPeripheralBusAndroid: failed to remove input device with ID {} because it couldn't "
362 bool CPeripheralBusAndroid::OnInputDeviceEvent(const AInputEvent
* event
)
364 if (event
== nullptr)
367 std::unique_lock
<CCriticalSection
> lock(m_critSectionStates
);
368 // get the id of the input device which generated the event
369 int32_t deviceId
= AInputEvent_getDeviceId(event
);
371 // find the matching joystick state
372 auto joystickState
= m_joystickStates
.find(deviceId
);
373 if (joystickState
== m_joystickStates
.end())
376 "CPeripheralBusAndroid: ignoring input event for non-joystick device with ID {}",
381 return joystickState
->second
.ProcessEvent(event
);
384 bool CPeripheralBusAndroid::PerformDeviceScan(PeripheralScanResults
& results
)
386 std::unique_lock
<CCriticalSection
> lock(m_critSectionResults
);
387 results
= m_scanResults
;
392 PeripheralScanResults
CPeripheralBusAndroid::GetInputDevices()
394 CLog::Log(LOGINFO
, "CPeripheralBusAndroid: scanning for input devices...");
396 PeripheralScanResults results
;
397 std::vector
<int> deviceIds
= CXBMCApp::GetInputDeviceIds();
399 for (const auto& deviceId
: deviceIds
)
401 const CJNIViewInputDevice device
= CXBMCApp::GetInputDevice(deviceId
);
404 CLog::Log(LOGWARNING
, "CPeripheralBusAndroid: no input device with ID {} found", deviceId
);
408 CLog::Log(LOGDEBUG
, "CPeripheralBusAndroid: Device discovered:");
409 LogInputDevice(device
);
411 PeripheralScanResult result
;
412 if (!ConvertToPeripheralScanResult(device
, result
))
415 CLog::Log(LOGINFO
, "CPeripheralBusAndroid: added input device");
416 results
.m_results
.emplace_back(std::move(result
));
422 std::string
CPeripheralBusAndroid::GetDeviceLocation(int deviceId
)
424 return StringUtils::Format("{}{}", DeviceLocationPrefix
, deviceId
);
427 bool CPeripheralBusAndroid::GetDeviceId(const std::string
& deviceLocation
, int& deviceId
)
429 if (deviceLocation
.empty() || !StringUtils::StartsWith(deviceLocation
, DeviceLocationPrefix
) ||
430 deviceLocation
.size() <= DeviceLocationPrefix
.size())
433 std::string strDeviceId
= deviceLocation
.substr(DeviceLocationPrefix
.size());
434 if (!StringUtils::IsNaturalNumber(strDeviceId
))
437 deviceId
= static_cast<int>(strtol(strDeviceId
.c_str(), nullptr, 10));
441 bool CPeripheralBusAndroid::ConvertToPeripheralScanResult(
442 const CJNIViewInputDevice
& inputDevice
, PeripheralScanResult
& peripheralScanResult
)
444 if (inputDevice
.isVirtual())
446 CLog::Log(LOGDEBUG
, "CPeripheralBusAndroid: ignoring virtual input device");
450 if (!inputDevice
.supportsSource(CJNIViewInputDevice::SOURCE_JOYSTICK
) &&
451 !inputDevice
.supportsSource(CJNIViewInputDevice::SOURCE_GAMEPAD
))
453 CLog::Log(LOGDEBUG
, "CPeripheralBusAndroid: ignoring non-joystick device");
457 peripheralScanResult
.m_type
= PERIPHERAL_JOYSTICK
;
458 peripheralScanResult
.m_strLocation
= GetDeviceLocation(inputDevice
.getId());
459 peripheralScanResult
.m_iVendorId
= inputDevice
.getVendorId();
460 peripheralScanResult
.m_iProductId
= inputDevice
.getProductId();
461 peripheralScanResult
.m_mappedType
= PERIPHERAL_JOYSTICK
;
462 peripheralScanResult
.m_strDeviceName
= inputDevice
.getName();
463 peripheralScanResult
.m_busType
= PERIPHERAL_BUS_ANDROID
;
464 peripheralScanResult
.m_mappedBusType
= PERIPHERAL_BUS_ANDROID
;
465 peripheralScanResult
.m_iSequence
= 0;
470 void CPeripheralBusAndroid::LogInputDevice(const CJNIViewInputDevice
& device
)
472 // Log device properties
473 CLog::Log(LOGDEBUG
, " Name: \"{}\"", device
.getName());
474 CLog::Log(LOGDEBUG
, " ID: {}", device
.getId());
475 CLog::Log(LOGDEBUG
, " Controller number: {}", device
.getControllerNumber());
476 std::string descriptor
= device
.getDescriptor();
477 if (descriptor
.size() > 14)
478 CLog::Log(LOGDEBUG
, " Descriptor: \"{}...\"", descriptor
.substr(0, 14));
480 CLog::Log(LOGDEBUG
, " Descriptor: \"{}\"", descriptor
);
481 CLog::Log(LOGDEBUG
, " Product ID: {:04X}", device
.getProductId());
482 CLog::Log(LOGDEBUG
, " Vendor ID: {:04X}", device
.getVendorId());
483 CLog::Log(LOGDEBUG
, " Has microphone: {}", device
.hasMicrophone() ? "true" : "false");
484 CLog::Log(LOGDEBUG
, " Is virtual: {}", device
.isVirtual() ? "true" : "false");
486 // Log device sources
487 CLog::Log(LOGDEBUG
, " Source flags: {:#08x}", device
.getSources());
488 for (const auto& source
: GetInputSources())
490 if (device
.supportsSource(source
.first
))
491 CLog::Log(LOGDEBUG
, " Has source: {} ({:#08x})", source
.second
, source
.first
);
495 std::vector
<int> keys(KEY_CODE_FINAL
);
496 std::iota(keys
.begin(), keys
.end(), 1);
498 auto results
= device
.hasKeys(keys
);
500 if (results
.size() != keys
.size())
502 CLog::Log(LOGERROR
, "Failed to get key status for {} keys", keys
.size());
506 for (unsigned int i
= 0; i
< keys
.size(); i
++)
509 CLog::Log(LOGDEBUG
, " Has key: {} ({})",
510 CAndroidJoystickTranslator::TranslateKeyCode(keys
[i
]), keys
[i
]);
515 const CJNIList
<CJNIViewInputDeviceMotionRange
> motionRanges
= device
.getMotionRanges();
516 for (int index
= 0; index
< motionRanges
.size(); ++index
)
518 const CJNIViewInputDeviceMotionRange motionRange
= motionRanges
.get(index
);
520 int axisId
= motionRange
.getAxis();
521 CLog::Log(LOGDEBUG
, " Has axis: {} ({})", CAndroidJoystickTranslator::TranslateAxis(axisId
),
523 CLog::Log(LOGDEBUG
, " Endpoints: [{:f}, {:f}]", motionRange
.getMin(),
524 motionRange
.getMax());
525 CLog::Log(LOGDEBUG
, " Center: {:f}", motionRange
.getFlat());
526 CLog::Log(LOGDEBUG
, " Fuzz: {:f}", motionRange
.getFuzz());
530 std::vector
<std::pair
<int, const char*>> CPeripheralBusAndroid::GetInputSources()
532 std::vector
<std::pair
<int, const char*>> sources
= {
533 {CJNIViewInputDevice::SOURCE_DPAD
, "SOURCE_DPAD"},
534 {CJNIViewInputDevice::SOURCE_GAMEPAD
, "SOURCE_GAMEPAD"},
535 {CJNIViewInputDevice::SOURCE_HDMI
, "SOURCE_HDMI"},
536 {CJNIViewInputDevice::SOURCE_JOYSTICK
, "SOURCE_JOYSTICK"},
537 {CJNIViewInputDevice::SOURCE_KEYBOARD
, "SOURCE_KEYBOARD"},
538 {CJNIViewInputDevice::SOURCE_MOUSE
, "SOURCE_MOUSE"},
539 {CJNIViewInputDevice::SOURCE_MOUSE_RELATIVE
, "SOURCE_MOUSE_RELATIVE"},
540 {CJNIViewInputDevice::SOURCE_ROTARY_ENCODER
, "SOURCE_ROTARY_ENCODER"},
541 {CJNIViewInputDevice::SOURCE_STYLUS
, "SOURCE_STYLUS"},
542 {CJNIViewInputDevice::SOURCE_TOUCHPAD
, "SOURCE_TOUCHPAD"},
543 {CJNIViewInputDevice::SOURCE_TOUCHSCREEN
, "SOURCE_TOUCHSCREEN"},
544 {CJNIViewInputDevice::SOURCE_TOUCH_NAVIGATION
, "SOURCE_TOUCH_NAVIGATION"},
545 {CJNIViewInputDevice::SOURCE_TRACKBALL
, "SOURCE_TRACKBALL"},
548 sources
.erase(std::remove_if(sources
.begin(), sources
.end(),
549 [](const std::pair
<int, const char*>& source
)
550 { return source
.first
== 0; }),