SDL: Fixed exception accessing Bluetooth devices on Android 12

From 66058bbbd5083acfb0ff88c49faac76d725a711f Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 15 Nov 2021 16:52:56 -0800
Subject: [PATCH] Fixed exception accessing Bluetooth devices on Android 12

Since accessing Bluetooth prompts the user for permission on both Android and iOS, and we only need it for Steam Controller support, we'll leave it off by default. You can enable it by setting the hint SDL_HINT_JOYSTICK_HIDAPI_STEAM to "1" before calling SDL_Init()

Fixes https://github.com/libsdl-org/SDL/issues/4952
---
 WhatsNew.txt                                  |  2 +
 .../app/src/main/AndroidManifest.xml          |  8 +-
 .../java/org/libsdl/app/HIDDeviceManager.java | 18 +++-
 include/SDL_hints.h                           |  5 +-
 src/hidapi/android/hid.cpp                    | 94 ++++++++++++++-----
 src/hidapi/ios/hid.m                          | 58 ++++++++----
 src/joystick/hidapi/SDL_hidapi_gamecube.c     |  1 +
 src/joystick/hidapi/SDL_hidapi_luna.c         |  1 +
 src/joystick/hidapi/SDL_hidapi_ps4.c          |  1 +
 src/joystick/hidapi/SDL_hidapi_ps5.c          |  1 +
 src/joystick/hidapi/SDL_hidapi_stadia.c       |  1 +
 src/joystick/hidapi/SDL_hidapi_steam.c        |  1 +
 src/joystick/hidapi/SDL_hidapi_switch.c       |  1 +
 src/joystick/hidapi/SDL_hidapi_xbox360.c      |  1 +
 src/joystick/hidapi/SDL_hidapi_xbox360w.c     |  1 +
 src/joystick/hidapi/SDL_hidapi_xboxone.c      |  1 +
 src/joystick/hidapi/SDL_hidapijoystick_c.h    |  1 +
 17 files changed, 147 insertions(+), 49 deletions(-)

diff --git a/WhatsNew.txt b/WhatsNew.txt
index e6076c552c..732f1a51da 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -11,9 +11,11 @@ Linux:
 
 Android:
 * Added support for audio output and capture using AAudio on Android 8.1 and newer
+* Steam Controller support is disabled by default, and can be enabled by setting the hint SDL_HINT_JOYSTICK_HIDAPI_STEAM to "1" before calling SDL_Init()
 
 iOS:
 * Added documentation that the UIApplicationSupportsIndirectInputEvents key must be set to true in your application's Info.plist in order to get real Bluetooth mouse events.
+* Steam Controller support is disabled by default, and can be enabled by setting the hint SDL_HINT_JOYSTICK_HIDAPI_STEAM to "1" before calling SDL_Init()
 
 
 ---------------------------------------------------------------------------
diff --git a/android-project/app/src/main/AndroidManifest.xml b/android-project/app/src/main/AndroidManifest.xml
index 857253141d..d997afe4a4 100644
--- a/android-project/app/src/main/AndroidManifest.xml
+++ b/android-project/app/src/main/AndroidManifest.xml
@@ -39,9 +39,13 @@
         android:required="false" /> -->
 
     <!-- Allow downloading to the external storage on Android 5.1 and older -->
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" />
+    <!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
+
     <!-- Allow access to Bluetooth devices -->
-    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
+    <!-- <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" /> -->
+    <!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> -->
+
     <!-- Allow access to the vibrator -->
     <uses-permission android:name="android.permission.VIBRATE" />
 
diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
index bf868aea6a..5ebbb97299 100644
--- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
+++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
@@ -119,9 +119,6 @@ private HIDDeviceManager(final Context context) {
         {
             mNextDeviceId = mSharedPreferences.getInt("next_device_id", 0);
         }
-
-        initializeUSB();
-        initializeBluetooth();
     }
 
     public Context getContext() {
@@ -349,7 +346,8 @@ private void connectHIDDeviceUSB(UsbDevice usbDevice) {
     private void initializeBluetooth() {
         Log.d(TAG, "Initializing Bluetooth");
 
-        if (mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+        if (Build.VERSION.SDK_INT <= 30 &&
+            mContext.getPackageManager().checkPermission(android.Manifest.permission.BLUETOOTH, mContext.getPackageName()) != PackageManager.PERMISSION_GRANTED) {
             Log.d(TAG, "Couldn't initialize Bluetooth, missing android.permission.BLUETOOTH");
             return;
         }
@@ -541,6 +539,18 @@ private HIDDevice getDevice(int id) {
     ////////// JNI interface functions
     //////////////////////////////////////////////////////////////////////////////////////////////////////
 
+    public boolean initialize(boolean usb, boolean bluetooth) {
+        Log.v(TAG, "initialize(" + usb + ", " + bluetooth + ")");
+
+        if (usb) {
+            initializeUSB();
+        }
+        if (bluetooth) {
+            initializeBluetooth();
+        }
+        return true;
+    }
+
     public boolean openDevice(int deviceID) {
         Log.v(TAG, "openDevice deviceID=" + deviceID);
         HIDDevice device = getDevice(deviceID);
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index d7782591b2..87242879bd 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -720,9 +720,10 @@ extern "C" {
  *
  *  This variable can be set to the following values:
  *    "0"       - HIDAPI driver is not used
- *    "1"       - HIDAPI driver is used
+ *    "1"       - HIDAPI driver is used for Steam Controllers, which requires Bluetooth access
+ *                and may prompt the user for permission on iOS and Android.
  *
- *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ *  The default is "0"
  */
 #define SDL_HINT_JOYSTICK_HIDAPI_STEAM "SDL_JOYSTICK_HIDAPI_STEAM"
 
diff --git a/src/hidapi/android/hid.cpp b/src/hidapi/android/hid.cpp
index 9113a4d897..bac7bc499a 100644
--- a/src/hidapi/android/hid.cpp
+++ b/src/hidapi/android/hid.cpp
@@ -18,12 +18,17 @@
      misrepresented as being the original software.
   3. This notice may not be removed or altered from any source distribution.
 */
+#include "../../SDL_internal.h"
+
 // Purpose: A wrapper implementing "HID" API for Android
 //
 //          This layer glues the hidapi API to Android's USB and BLE stack.
 
 #if !SDL_HIDAPI_DISABLED
 
+#include "SDL_hints.h"
+#include "../../core/android/SDL_android.h"
+
 #define hid_init                        PLATFORM_hid_init
 #define hid_exit                        PLATFORM_hid_exit
 #define hid_enumerate                   PLATFORM_hid_enumerate
@@ -371,17 +376,49 @@ static void FreeHIDDeviceInfo( hid_device_info *pInfo )
 
 static jclass  g_HIDDeviceManagerCallbackClass;
 static jobject g_HIDDeviceManagerCallbackHandler;
+static jmethodID g_midHIDDeviceManagerInitialize;
 static jmethodID g_midHIDDeviceManagerOpen;
 static jmethodID g_midHIDDeviceManagerSendOutputReport;
 static jmethodID g_midHIDDeviceManagerSendFeatureReport;
 static jmethodID g_midHIDDeviceManagerGetFeatureReport;
 static jmethodID g_midHIDDeviceManagerClose;
+static bool g_initialized = false;
 
 static uint64_t get_timespec_ms( const struct timespec &ts )
 {
 	return (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
 }
 
+static void ExceptionCheck( JNIEnv *env, const char *pszClassName, const char *pszMethodName )
+{
+	if ( env->ExceptionCheck() )
+	{
+		// Get our exception
+		jthrowable jExcept = env->ExceptionOccurred();
+
+		// Clear the exception so we can call JNI again
+		env->ExceptionClear();
+
+		// Get our exception message
+		jclass jExceptClass = env->GetObjectClass( jExcept );
+		jmethodID jMessageMethod = env->GetMethodID( jExceptClass, "getMessage", "()Ljava/lang/String;" );
+		jstring jMessage = (jstring)( env->CallObjectMethod( jExcept, jMessageMethod ) );
+		const char *pszMessage = env->GetStringUTFChars( jMessage, NULL );
+
+		// ...and log it.
+		LOGE( "%s%s%s threw an exception: %s",
+			pszClassName ? pszClassName : "",
+			pszClassName ? "::" : "",
+			pszMethodName, pszMessage );
+
+		// Cleanup
+		env->ReleaseStringUTFChars( jMessage, pszMessage );
+		env->DeleteLocalRef( jMessage );
+		env->DeleteLocalRef( jExceptClass );
+		env->DeleteLocalRef( jExcept );
+	}
+}
+
 class CHIDDevice
 {
 public:
@@ -441,29 +478,7 @@ class CHIDDevice
 
 	void ExceptionCheck( JNIEnv *env, const char *pszMethodName )
 	{
-		if ( env->ExceptionCheck() )
-		{
-			// Get our exception
-			jthrowable jExcept = env->ExceptionOccurred();
-
-			// Clear the exception so we can call JNI again
-			env->ExceptionClear();
-
-			// Get our exception message
-			jclass jExceptClass = env->GetObjectClass( jExcept );
-			jmethodID jMessageMethod = env->GetMethodID( jExceptClass, "getMessage", "()Ljava/lang/String;" );
-			jstring jMessage = (jstring)( env->CallObjectMethod( jExcept, jMessageMethod ) );
-			const char *pszMessage = env->GetStringUTFChars( jMessage, NULL );
-
-			// ...and log it.
-			LOGE( "CHIDDevice::%s threw an exception: %s", pszMethodName, pszMessage );
-
-			// Cleanup
-			env->ReleaseStringUTFChars( jMessage, pszMessage );
-			env->DeleteLocalRef( jMessage );
-			env->DeleteLocalRef( jExceptClass );
-			env->DeleteLocalRef( jExcept );
-		}
+		::ExceptionCheck( env, "CHIDDevice", pszMethodName );
 	}
 
 	bool BOpen()
@@ -852,6 +867,11 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceRegisterCallba
 	if ( objClass )
 	{
 		g_HIDDeviceManagerCallbackClass = reinterpret_cast< jclass >( env->NewGlobalRef( objClass ) );
+		g_midHIDDeviceManagerInitialize = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "initialize", "(ZZ)Z" );
+		if ( !g_midHIDDeviceManagerInitialize )
+		{
+			__android_log_print(ANDROID_LOG_ERROR, TAG, "HIDDeviceRegisterCallback: callback class missing initialize" );
+		}
 		g_midHIDDeviceManagerOpen = env->GetMethodID( g_HIDDeviceManagerCallbackClass, "openDevice", "(I)Z" );
 		if ( !g_midHIDDeviceManagerOpen )
 		{
@@ -891,6 +911,7 @@ JNIEXPORT void JNICALL HID_DEVICE_MANAGER_JAVA_INTERFACE(HIDDeviceReleaseCallbac
 		g_HIDDeviceManagerCallbackClass = NULL;
 		env->DeleteGlobalRef( g_HIDDeviceManagerCallbackHandler );
 		g_HIDDeviceManagerCallbackHandler = NULL;
+		g_initialized = false;
 	}
 }
 
@@ -1025,6 +1046,33 @@ extern "C"
 
 int hid_init(void)
 {
+	if ( !g_initialized )
+	{
+		// Make sure thread is attached to JVM/env
+		JNIEnv *env;
+		g_JVM->AttachCurrentThread( &env, NULL );
+		pthread_setspecific( g_ThreadKey, (void*)env );
+
+		if ( !g_HIDDeviceManagerCallbackHandler )
+		{
+			LOGV( "hid_init() without callback handler" );
+			return -1;
+		}
+
+        // Bluetooth is currently only used for Steam Controllers, so check that hint
+        // before initializing Bluetooth, which will prompt the user for permission.
+		bool init_usb = true;
+		bool init_bluetooth = false;
+        if (SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_FALSE)) {
+			if (SDL_GetAndroidSDKVersion() < 31 ||
+				Android_JNI_RequestPermission("android.permission.BLUETOOTH_CONNECT")) {
+				init_bluetooth = true;
+			}
+        }
+		env->CallBooleanMethod( g_HIDDeviceManagerCallbackHandler, g_midHIDDeviceManagerInitialize, init_usb, init_bluetooth );
+		ExceptionCheck( env, NULL, "hid_init" );
+		g_initialized = true;	// Regardless of result, so it's only called once
+	}
 	return 0;
 }
 
diff --git a/src/hidapi/ios/hid.m b/src/hidapi/ios/hid.m
index e193a727cc..111b8f2364 100644
--- a/src/hidapi/ios/hid.m
+++ b/src/hidapi/ios/hid.m
@@ -22,6 +22,8 @@
 
 #if !SDL_HIDAPI_DISABLED
 
+#include "SDL_hints.h"
+
 #define hid_init                        PLATFORM_hid_init
 #define hid_exit                        PLATFORM_hid_exit
 #define hid_enumerate                   PLATFORM_hid_enumerate
@@ -227,24 +229,29 @@ + (instancetype)sharedInstance
 		sharedInstance = [HIDBLEManager new];
 		sharedInstance.nPendingScans = 0;
 		sharedInstance.nPendingPairs = 0;
-		
-		[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
-		[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
-
-		// receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
-		// race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
-		// that we can still screw this up.
-		// most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
-		// listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
-		// DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
-		// see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
-		sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
-		dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
-
-		// creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
-		// where any scanning gets started or connecting to existing peripherals happens, it's never already in a
-		// powered-on state for a newly launched application.
-		sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
+
+        // Bluetooth is currently only used for Steam Controllers, so check that hint
+        // before initializing Bluetooth, which will prompt the user for permission.
+		if ( SDL_GetHintBoolean( SDL_HINT_JOYSTICK_HIDAPI_STEAM, SDL_FALSE ) )
+		{
+			[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:nil];
+			[[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(appDidBecomeActiveNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
+
+			// receive reports on a high-priority serial-queue. optionally put writes on the serial queue to avoid logical
+			// race conditions talking to the controller from multiple threads, although BLE fragmentation/assembly means
+			// that we can still screw this up.
+			// most importantly we need to consume reports at a high priority to avoid the OS thinking we aren't really
+			// listening to the BLE device, as iOS on slower devices may stop delivery of packets to the app WITHOUT ACTUALLY
+			// DISCONNECTING FROM THE DEVICE if we don't react quickly enough to their delivery.
+			// see also the error-handling states in the peripheral delegate to re-open the device if it gets closed
+			sharedInstance.bleSerialQueue = dispatch_queue_create( "com.valvesoftware.steamcontroller.ble", DISPATCH_QUEUE_SERIAL );
+			dispatch_set_target_queue( sharedInstance.bleSerialQueue, dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0 ) );
+
+			// creating a CBCentralManager will always trigger a future centralManagerDidUpdateState:
+			// where any scanning gets started or connecting to existing peripherals happens, it's never already in a
+			// powered-on state for a newly launched application.
+			sharedInstance.centralManager = [[CBCentralManager alloc] initWithDelegate:sharedInstance queue:sharedInstance.bleSerialQueue];
+		}
 		sharedInstance.deviceMap = [[NSMapTable alloc] initWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory capacity:4];
 	});
 	return sharedInstance;
@@ -284,6 +291,11 @@ - (int)updateConnectedSteamControllers:(BOOL) bForce
 	static uint64_t s_unLastUpdateTick = 0;
 	static mach_timebase_info_data_t s_timebase_info;
 	
+	if ( self.centralManager == nil )
+    {
+		return 0;
+    }
+
 	if (s_timebase_info.denom == 0)
 	{
 		mach_timebase_info( &s_timebase_info );
@@ -329,6 +341,11 @@ - (int)updateConnectedSteamControllers:(BOOL) bForce
 // manual API for folks to start & stop scanning
 - (void)startScan:(int)duration
 {
+	if ( self.centralManager == nil )
+	{
+		return;
+	}
+
 	NSLog( @"BLE: requesting scan for %d seconds", duration );
 	@synchronized (self)
 	{
@@ -348,6 +365,11 @@ - (void)startScan:(int)duration
 
 - (void)stopScan
 {
+	if ( self.centralManager == nil )
+	{
+		return;
+	}
+
 	NSLog( @"BLE: stopping scan" );
 	@synchronized (self)
 	{
diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c
index 2b6f7b9d8e..cd91feb8ae 100644
--- a/src/joystick/hidapi/SDL_hidapi_gamecube.c
+++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c
@@ -539,6 +539,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
 {
     SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverGameCube_IsSupportedDevice,
     HIDAPI_DriverGameCube_GetDeviceName,
     HIDAPI_DriverGameCube_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_luna.c b/src/joystick/hidapi/SDL_hidapi_luna.c
index 90d50d41ef..fc814d0a08 100644
--- a/src/joystick/hidapi/SDL_hidapi_luna.c
+++ b/src/joystick/hidapi/SDL_hidapi_luna.c
@@ -438,6 +438,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna =
 {
     SDL_HINT_JOYSTICK_HIDAPI_LUNA,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverLuna_IsSupportedDevice,
     HIDAPI_DriverLuna_GetDeviceName,
     HIDAPI_DriverLuna_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index f0f70d80c7..08f524ab11 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -933,6 +933,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 =
 {
     SDL_HINT_JOYSTICK_HIDAPI_PS4,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverPS4_IsSupportedDevice,
     HIDAPI_DriverPS4_GetDeviceName,
     HIDAPI_DriverPS4_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c
index 5ba404775a..2e3d3c6eb9 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps5.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -1096,6 +1096,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 =
 {
     SDL_HINT_JOYSTICK_HIDAPI_PS5,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverPS5_IsSupportedDevice,
     HIDAPI_DriverPS5_GetDeviceName,
     HIDAPI_DriverPS5_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_stadia.c b/src/joystick/hidapi/SDL_hidapi_stadia.c
index 0df431555a..b6b6331758 100644
--- a/src/joystick/hidapi/SDL_hidapi_stadia.c
+++ b/src/joystick/hidapi/SDL_hidapi_stadia.c
@@ -306,6 +306,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia =
 {
     SDL_HINT_JOYSTICK_HIDAPI_STADIA,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverStadia_IsSupportedDevice,
     HIDAPI_DriverStadia_GetDeviceName,
     HIDAPI_DriverStadia_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_steam.c b/src/joystick/hidapi/SDL_hidapi_steam.c
index 1b0e76d68d..d89bd08340 100644
--- a/src/joystick/hidapi/SDL_hidapi_steam.c
+++ b/src/joystick/hidapi/SDL_hidapi_steam.c
@@ -1284,6 +1284,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam =
 {
     SDL_HINT_JOYSTICK_HIDAPI_STEAM,
     SDL_TRUE,
+    SDL_FALSE,
     HIDAPI_DriverSteam_IsSupportedDevice,
     HIDAPI_DriverSteam_GetDeviceName,
     HIDAPI_DriverSteam_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index 51c44ef2b0..056845f16f 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -1555,6 +1555,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch =
 {
     SDL_HINT_JOYSTICK_HIDAPI_SWITCH,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverSwitch_IsSupportedDevice,
     HIDAPI_DriverSwitch_GetDeviceName,
     HIDAPI_DriverSwitch_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c
index 39cbbfa99b..caa8c53dc7 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c
@@ -337,6 +337,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 =
 {
     SDL_HINT_JOYSTICK_HIDAPI_XBOX,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverXbox360_IsSupportedDevice,
     HIDAPI_DriverXbox360_GetDeviceName,
     HIDAPI_DriverXbox360_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/src/joystick/hidapi/SDL_hidapi_xbox360w.c
index ae8bbdcafd..b58aab3c7a 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360w.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360w.c
@@ -334,6 +334,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W =
 {
     SDL_HINT_JOYSTICK_HIDAPI_XBOX,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverXbox360W_IsSupportedDevice,
     HIDAPI_DriverXbox360W_GetDeviceName,
     HIDAPI_DriverXbox360W_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c
index 097deac5bd..553467269b 100644
--- a/src/joystick/hidapi/SDL_hidapi_xboxone.c
+++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c
@@ -1127,6 +1127,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne =
 {
     SDL_HINT_JOYSTICK_HIDAPI_XBOX,
     SDL_TRUE,
+    SDL_TRUE,
     HIDAPI_DriverXboxOne_IsSupportedDevice,
     HIDAPI_DriverXboxOne_GetDeviceName,
     HIDAPI_DriverXboxOne_InitDevice,
diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h
index 282b149400..3adfb95f2f 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick_c.h
+++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -88,6 +88,7 @@ typedef struct _SDL_HIDAPI_DeviceDriver
 {
     const char *hint;
     SDL_bool enabled;
+    SDL_bool enabled_default;
     SDL_bool (*IsSupportedDevice)(const char *name, SDL_GameControllerType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
     const char *(*GetDeviceName)(Uint16 vendor_id, Uint16 product_id);
     SDL_bool (*InitDevice)(SDL_HIDAPI_Device *device);