summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2025-01-02 17:22:29 +0100
committerJosJuice <josjuice@gmail.com>2025-07-27 11:21:04 +0200
commite2e33becc9934f9af850fc2e2b5c77cf0eb15683 (patch)
tree4a8f2e5c075b0fb96ee56baf565725d328bbf16a
parent7508842859bc1c791d1fff2ba514f547a59a631d (diff)
Android: Detect GCAdapter connection using BroadcastReceiver
We can register a BroadcastReceiver to have Android tell us when a GC adapter gets connected instead of having a loop where we continuously call SleepCurrentThread(1000) and poll the current status. When waiting for a GC adapter to connect, this both reduces power usage and improves responsiveness. Note that I made openAdapter get the UsbDevice that's been stored by the hotplug code instead of having openAdapter find the UsbDevice on its own like before. This is only because I want to ensure that the UsbDevice being tracked for disconnection is the same as the UsbDevice actually being used, in case the user has multiple adapters connected.
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java182
-rw-r--r--Source/Core/InputCommon/GCAdapter.cpp41
2 files changed, 173 insertions, 50 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java
index 924496e488..12ed20fec7 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java
@@ -3,8 +3,10 @@
package org.dolphinemu.dolphinemu.utils;
import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
@@ -16,10 +18,12 @@ import android.os.Build;
import android.widget.Toast;
import androidx.annotation.Keep;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import org.dolphinemu.dolphinemu.BuildConfig;
import org.dolphinemu.dolphinemu.DolphinApplication;
import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.services.USBPermService;
import java.util.HashMap;
import java.util.Map;
@@ -36,6 +40,21 @@ public class GCAdapter
static UsbEndpoint usbIn;
static UsbEndpoint usbOut;
+ private static final String ACTION_GC_ADAPTER_PERMISSION_GRANTED =
+ BuildConfig.APPLICATION_ID + ".GC_ADAPTER_PERMISSION_GRANTED";
+
+ private static final Object hotplugCallbackLock = new Object();
+ private static boolean hotplugCallbackEnabled = false;
+ private static UsbDevice adapterDevice = null;
+ private static BroadcastReceiver hotplugBroadcastReceiver = new BroadcastReceiver()
+ {
+ @Override
+ public void onReceive(Context context, Intent intent)
+ {
+ onUsbDevicesChanged();
+ }
+ };
+
private static void requestPermission()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
@@ -47,11 +66,11 @@ public class GCAdapter
if (!manager.hasPermission(dev))
{
Context context = DolphinApplication.getAppContext();
- Intent intent = new Intent(context, USBPermService.class);
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
PendingIntent.FLAG_IMMUTABLE : 0;
- PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, flags);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
+ new Intent(ACTION_GC_ADAPTER_PERMISSION_GRANTED), flags);
manager.requestPermission(dev, pendingIntent);
}
@@ -71,7 +90,16 @@ public class GCAdapter
}
@Keep
- public static boolean queryAdapter()
+ public static boolean isUsbDeviceAvailable()
+ {
+ synchronized (hotplugCallbackLock)
+ {
+ return adapterDevice != null;
+ }
+ }
+
+ @Nullable
+ private static UsbDevice queryAdapter()
{
HashMap<String, UsbDevice> devices = manager.getDeviceList();
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
@@ -80,12 +108,12 @@ public class GCAdapter
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
{
if (manager.hasPermission(dev))
- return true;
+ return dev;
else
requestPermission();
}
}
- return false;
+ return null;
}
public static void initAdapter()
@@ -109,50 +137,118 @@ public class GCAdapter
@Keep
public static boolean openAdapter()
{
- HashMap<String, UsbDevice> devices = manager.getDeviceList();
- for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
+ UsbDevice dev;
+ synchronized (hotplugCallbackLock)
{
- UsbDevice dev = pair.getValue();
- if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
+ dev = adapterDevice;
+ }
+
+ if (dev != null)
+ {
+ usbConnection = manager.openDevice(dev);
+
+ Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount());
+ Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount());
+
+ if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0)
{
- if (manager.hasPermission(dev))
+ UsbConfiguration conf = dev.getConfiguration(0);
+ usbInterface = conf.getInterface(0);
+ usbConnection.claimInterface(usbInterface, true);
+
+ Log.info("GCAdapter: Number of endpoints: " + usbInterface.getEndpointCount());
+
+ if (usbInterface.getEndpointCount() == 2)
{
- usbConnection = manager.openDevice(dev);
-
- Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount());
- Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount());
-
- if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0)
- {
- UsbConfiguration conf = dev.getConfiguration(0);
- usbInterface = conf.getInterface(0);
- usbConnection.claimInterface(usbInterface, true);
-
- Log.info("GCAdapter: Number of endpoints: " + usbInterface.getEndpointCount());
-
- if (usbInterface.getEndpointCount() == 2)
- {
- for (int i = 0; i < usbInterface.getEndpointCount(); ++i)
- if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN)
- usbIn = usbInterface.getEndpoint(i);
- else
- usbOut = usbInterface.getEndpoint(i);
-
- initAdapter();
- return true;
- }
+ for (int i = 0; i < usbInterface.getEndpointCount(); ++i)
+ if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN)
+ usbIn = usbInterface.getEndpoint(i);
else
- {
- usbConnection.releaseInterface(usbInterface);
- }
- }
-
- Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter,
- Toast.LENGTH_LONG).show();
- usbConnection.close();
+ usbOut = usbInterface.getEndpoint(i);
+
+ initAdapter();
+ return true;
+ }
+ else
+ {
+ usbConnection.releaseInterface(usbInterface);
}
}
+
+ Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter,
+ Toast.LENGTH_LONG).show();
+ usbConnection.close();
}
return false;
}
+
+ @Keep
+ public static void enableHotplugCallback()
+ {
+ synchronized (hotplugCallbackLock)
+ {
+ if (hotplugCallbackEnabled)
+ {
+ throw new IllegalStateException("enableHotplugCallback was called when already enabled");
+ }
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ filter.addAction(ACTION_GC_ADAPTER_PERMISSION_GRANTED);
+
+ ContextCompat.registerReceiver(DolphinApplication.getAppContext(), hotplugBroadcastReceiver,
+ filter, ContextCompat.RECEIVER_EXPORTED);
+
+ hotplugCallbackEnabled = true;
+
+ onUsbDevicesChanged();
+ }
+ }
+
+ @Keep
+ public static void disableHotplugCallback()
+ {
+ synchronized (hotplugCallbackLock)
+ {
+ if (hotplugCallbackEnabled)
+ {
+ DolphinApplication.getAppContext().unregisterReceiver(hotplugBroadcastReceiver);
+ hotplugCallbackEnabled = false;
+ adapterDevice = null;
+ }
+ }
+ }
+
+ public static void onUsbDevicesChanged()
+ {
+ synchronized (hotplugCallbackLock)
+ {
+ if (adapterDevice != null)
+ {
+ boolean adapterStillConnected = manager.getDeviceList().entrySet().stream()
+ .anyMatch(pair -> pair.getValue().getDeviceId() == adapterDevice.getDeviceId());
+
+ if (!adapterStillConnected)
+ {
+ adapterDevice = null;
+ onAdapterDisconnected();
+ }
+ }
+
+ if (adapterDevice == null)
+ {
+ UsbDevice newAdapter = queryAdapter();
+ if (newAdapter != null)
+ {
+ adapterDevice = newAdapter;
+ onAdapterConnected();
+ }
+ }
+ }
+ }
+
+ private static native void onAdapterConnected();
+
+ private static native void onAdapterDisconnected();
}
diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp
index 103af024fd..2da4cf3f15 100644
--- a/Source/Core/InputCommon/GCAdapter.cpp
+++ b/Source/Core/InputCommon/GCAdapter.cpp
@@ -144,9 +144,9 @@ static std::mutex s_write_mutex;
static std::thread s_adapter_detect_thread;
static Common::Flag s_adapter_detect_thread_running;
-#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
static Common::Event s_hotplug_event;
+#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
static std::function<void(void)> s_detect_callback;
#if defined(__FreeBSD__) && __FreeBSD__ >= 11
@@ -344,6 +344,23 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl
return 0;
}
#endif
+#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
+extern "C" {
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterConnected(JNIEnv* env, jclass)
+{
+ INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter connected");
+ if (!s_detected)
+ s_hotplug_event.Set();
+}
+
+JNIEXPORT void JNICALL
+Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterDisconnected(JNIEnv* env, jclass)
+{
+ INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter disconnected");
+}
+}
#endif
static void ScanThreadFunc()
@@ -393,15 +410,22 @@ static void ScanThreadFunc()
#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
JNIEnv* const env = IDCache::GetEnvForThread();
- const jmethodID queryadapter_func =
- env->GetStaticMethodID(s_adapter_class, "queryAdapter", "()Z");
+ const jmethodID enable_hotplug_callback_func =
+ env->GetStaticMethodID(s_adapter_class, "enableHotplugCallback", "()V");
+ env->CallStaticVoidMethod(s_adapter_class, enable_hotplug_callback_func);
+
+ const jmethodID is_usb_device_available_func =
+ env->GetStaticMethodID(s_adapter_class, "isUsbDeviceAvailable", "()Z");
while (s_adapter_detect_thread_running.IsSet())
{
if (!s_detected && UseAdapter() &&
- env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func))
+ env->CallStaticBooleanMethod(s_adapter_class, is_usb_device_available_func))
+ {
Setup();
- Common::SleepCurrentThread(1000);
+ }
+
+ s_hotplug_event.Wait();
}
#endif
@@ -484,9 +508,7 @@ void StopScanThread()
{
if (s_adapter_detect_thread_running.TestAndClear())
{
-#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION
s_hotplug_event.Set();
-#endif
s_adapter_detect_thread.join();
}
}
@@ -691,6 +713,11 @@ void Shutdown()
if (s_libusb_context && s_libusb_context->IsValid() && s_libusb_hotplug_enabled)
libusb_hotplug_deregister_callback(*s_libusb_context, s_hotplug_handle);
#endif
+#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION
+ JNIEnv* const env = IDCache::GetEnvForThread();
+ const jmethodID disable_hotplug_callback_func =
+ env->GetStaticMethodID(s_adapter_class, "disableHotplugCallback", "()V");
+ env->CallStaticVoidMethod(s_adapter_class, disable_hotplug_callback_func);
#endif
Reset(CalledFromReadThread::No);