From 7508842859bc1c791d1fff2ba514f547a59a631d Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 2 Jan 2025 15:39:41 +0100 Subject: [PATCH 1/5] Android: Clean up naming in Java_GCAdapter and Java_WiimoteAdapter This isn't how we name things in Java/Kotlin. --- .../dolphinemu/DolphinApplication.java | 8 +-- .../{Java_GCAdapter.java => GCAdapter.java} | 64 +++++++++---------- ...iimoteAdapter.java => WiimoteAdapter.java} | 40 ++++++------ Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp | 12 ++-- Source/Core/InputCommon/GCAdapter.cpp | 14 ++-- 5 files changed, 69 insertions(+), 69 deletions(-) rename Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/{Java_GCAdapter.java => GCAdapter.java} (66%) rename Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/{Java_WiimoteAdapter.java => WiimoteAdapter.java} (80%) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java index 8ff78ebb0e6..91200852a19 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/DolphinApplication.java @@ -9,8 +9,8 @@ import android.hardware.usb.UsbManager; import org.dolphinemu.dolphinemu.utils.ActivityTracker; import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; -import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; -import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; +import org.dolphinemu.dolphinemu.utils.GCAdapter; +import org.dolphinemu.dolphinemu.utils.WiimoteAdapter; import org.dolphinemu.dolphinemu.utils.VolleyUtil; public class DolphinApplication extends Application @@ -28,8 +28,8 @@ public class DolphinApplication extends Application VolleyUtil.init(getApplicationContext()); System.loadLibrary("main"); - Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); - Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); + GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); + WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); if (DirectoryInitialization.shouldStart(getApplicationContext())) DirectoryInitialization.start(getApplicationContext()); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_GCAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java similarity index 66% rename from Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_GCAdapter.java rename to Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java index 3fcd58afacb..924496e4882 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_GCAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/GCAdapter.java @@ -24,19 +24,19 @@ import org.dolphinemu.dolphinemu.services.USBPermService; import java.util.HashMap; import java.util.Map; -public class Java_GCAdapter +public class GCAdapter { public static UsbManager manager; @Keep - static byte[] controller_payload = new byte[37]; + static byte[] controllerPayload = new byte[37]; - static UsbDeviceConnection usb_con; - static UsbInterface usb_intf; - static UsbEndpoint usb_in; - static UsbEndpoint usb_out; + static UsbDeviceConnection usbConnection; + static UsbInterface usbInterface; + static UsbEndpoint usbIn; + static UsbEndpoint usbOut; - private static void RequestPermission() + private static void requestPermission() { HashMap devices = manager.getDeviceList(); for (Map.Entry pair : devices.entrySet()) @@ -59,19 +59,19 @@ public class Java_GCAdapter } } - public static void Shutdown() + public static void shutdown() { - usb_con.close(); + usbConnection.close(); } @Keep - public static int GetFD() + public static int getFd() { - return usb_con.getFileDescriptor(); + return usbConnection.getFileDescriptor(); } @Keep - public static boolean QueryAdapter() + public static boolean queryAdapter() { HashMap devices = manager.getDeviceList(); for (Map.Entry pair : devices.entrySet()) @@ -82,32 +82,32 @@ public class Java_GCAdapter if (manager.hasPermission(dev)) return true; else - RequestPermission(); + requestPermission(); } } return false; } - public static void InitAdapter() + public static void initAdapter() { byte[] init = {0x13}; - usb_con.bulkTransfer(usb_out, init, init.length, 0); + usbConnection.bulkTransfer(usbOut, init, init.length, 0); } @Keep - public static int Input() + public static int input() { - return usb_con.bulkTransfer(usb_in, controller_payload, controller_payload.length, 16); + return usbConnection.bulkTransfer(usbIn, controllerPayload, controllerPayload.length, 16); } @Keep - public static int Output(byte[] rumble) + public static int output(byte[] rumble) { - return usb_con.bulkTransfer(usb_out, rumble, 5, 16); + return usbConnection.bulkTransfer(usbOut, rumble, 5, 16); } @Keep - public static boolean OpenAdapter() + public static boolean openAdapter() { HashMap devices = manager.getDeviceList(); for (Map.Entry pair : devices.entrySet()) @@ -117,7 +117,7 @@ public class Java_GCAdapter { if (manager.hasPermission(dev)) { - usb_con = manager.openDevice(dev); + usbConnection = manager.openDevice(dev); Log.info("GCAdapter: Number of configurations: " + dev.getConfigurationCount()); Log.info("GCAdapter: Number of interfaces: " + dev.getInterfaceCount()); @@ -125,31 +125,31 @@ public class Java_GCAdapter if (dev.getConfigurationCount() > 0 && dev.getInterfaceCount() > 0) { UsbConfiguration conf = dev.getConfiguration(0); - usb_intf = conf.getInterface(0); - usb_con.claimInterface(usb_intf, true); + usbInterface = conf.getInterface(0); + usbConnection.claimInterface(usbInterface, true); - Log.info("GCAdapter: Number of endpoints: " + usb_intf.getEndpointCount()); + Log.info("GCAdapter: Number of endpoints: " + usbInterface.getEndpointCount()); - if (usb_intf.getEndpointCount() == 2) + if (usbInterface.getEndpointCount() == 2) { - for (int i = 0; i < usb_intf.getEndpointCount(); ++i) - if (usb_intf.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN) - usb_in = usb_intf.getEndpoint(i); + for (int i = 0; i < usbInterface.getEndpointCount(); ++i) + if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN) + usbIn = usbInterface.getEndpoint(i); else - usb_out = usb_intf.getEndpoint(i); + usbOut = usbInterface.getEndpoint(i); - InitAdapter(); + initAdapter(); return true; } else { - usb_con.releaseInterface(usb_intf); + usbConnection.releaseInterface(usbInterface); } } Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter, Toast.LENGTH_LONG).show(); - usb_con.close(); + usbConnection.close(); } } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiimoteAdapter.java similarity index 80% rename from Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java rename to Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiimoteAdapter.java index 469375614f6..98a7d519004 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/WiimoteAdapter.java @@ -22,7 +22,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; -public class Java_WiimoteAdapter +public class WiimoteAdapter { final static int MAX_PAYLOAD = 23; final static int MAX_WIIMOTES = 4; @@ -31,14 +31,14 @@ public class Java_WiimoteAdapter final static short NINTENDO_WIIMOTE_PRODUCT_ID = 0x0306; public static UsbManager manager; - static UsbDeviceConnection usb_con; - static UsbInterface[] usb_intf = new UsbInterface[MAX_WIIMOTES]; - static UsbEndpoint[] usb_in = new UsbEndpoint[MAX_WIIMOTES]; + static UsbDeviceConnection usbConnection; + static UsbInterface[] usbInterface = new UsbInterface[MAX_WIIMOTES]; + static UsbEndpoint[] usbIn = new UsbEndpoint[MAX_WIIMOTES]; @Keep - public static byte[][] wiimote_payload = new byte[MAX_WIIMOTES][MAX_PAYLOAD]; + public static byte[][] wiimotePayload = new byte[MAX_WIIMOTES][MAX_PAYLOAD]; - private static void RequestPermission() + private static void requestPermission() { HashMap devices = manager.getDeviceList(); for (Map.Entry pair : devices.entrySet()) @@ -65,7 +65,7 @@ public class Java_WiimoteAdapter } @Keep - public static boolean QueryAdapter() + public static boolean queryAdapter() { HashMap devices = manager.getDeviceList(); for (Map.Entry pair : devices.entrySet()) @@ -77,20 +77,20 @@ public class Java_WiimoteAdapter if (manager.hasPermission(dev)) return true; else - RequestPermission(); + requestPermission(); } } return false; } @Keep - public static int Input(int index) + public static int input(int index) { - return usb_con.bulkTransfer(usb_in[index], wiimote_payload[index], MAX_PAYLOAD, TIMEOUT); + return usbConnection.bulkTransfer(usbIn[index], wiimotePayload[index], MAX_PAYLOAD, TIMEOUT); } @Keep - public static int Output(int index, byte[] buf, int size) + public static int output(int index, byte[] buf, int size) { byte report_number = buf[0]; @@ -105,7 +105,7 @@ public class Java_WiimoteAdapter final int HID_SET_REPORT = 0x9; final int HID_OUTPUT = (2 << 8); - int write = usb_con.controlTransfer( + int write = usbConnection.controlTransfer( LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, HID_SET_REPORT, HID_OUTPUT | report_number, @@ -120,10 +120,10 @@ public class Java_WiimoteAdapter } @Keep - public static boolean OpenAdapter() + public static boolean openAdapter() { // If the adapter is already open. Don't attempt to do it again - if (usb_con != null && usb_con.getFileDescriptor() != -1) + if (usbConnection != null && usbConnection.getFileDescriptor() != -1) return true; HashMap devices = manager.getDeviceList(); @@ -135,7 +135,7 @@ public class Java_WiimoteAdapter { if (manager.hasPermission(dev)) { - usb_con = manager.openDevice(dev); + usbConnection = manager.openDevice(dev); UsbConfiguration conf = dev.getConfiguration(0); Log.info("Number of configurations: " + dev.getConfigurationCount()); @@ -149,20 +149,20 @@ public class Java_WiimoteAdapter for (int i = 0; i < MAX_WIIMOTES; ++i) { // One interface per Wii Remote - usb_intf[i] = dev.getInterface(i); - usb_con.claimInterface(usb_intf[i], true); + usbInterface[i] = dev.getInterface(i); + usbConnection.claimInterface(usbInterface[i], true); // One endpoint per Wii Remote. Input only // Output reports go through the control channel. - usb_in[i] = usb_intf[i].getEndpoint(0); - Log.info("Interface " + i + " endpoint count:" + usb_intf[i].getEndpointCount()); + usbIn[i] = usbInterface[i].getEndpoint(0); + Log.info("Interface " + i + " endpoint count:" + usbInterface[i].getEndpointCount()); } return true; } else { // XXX: Message that the device was found, but it needs to be unplugged and plugged back in? - usb_con.close(); + usbConnection.close(); } } } diff --git a/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp index 10234ceef7a..507cacf0067 100644 --- a/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp +++ b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp @@ -30,8 +30,8 @@ void WiimoteScannerAndroid::FindWiimotes(std::vector& found_wiimotes, JNIEnv* env = IDCache::GetEnvForThread(); - jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z"); - jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z"); + jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z"); + jmethodID queryadapter_func = env->GetStaticMethodID(s_adapter_class, "queryAdapter", "()Z"); if (env->CallStaticBooleanMethod(s_adapter_class, queryadapter_func) && env->CallStaticBooleanMethod(s_adapter_class, openadapter_func)) @@ -55,15 +55,15 @@ bool WiimoteAndroid::ConnectInternal() { m_env = IDCache::GetEnvForThread(); - jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimote_payload", "[[B"); + jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimotePayload", "[[B"); jobjectArray payload_object = reinterpret_cast(m_env->GetStaticObjectField(s_adapter_class, payload_field)); m_java_wiimote_payload = (jbyteArray)m_env->GetObjectArrayElement(payload_object, m_mayflash_index); // Get function pointers - m_input_func = m_env->GetStaticMethodID(s_adapter_class, "Input", "(I)I"); - m_output_func = m_env->GetStaticMethodID(s_adapter_class, "Output", "(I[BI)I"); + m_input_func = m_env->GetStaticMethodID(s_adapter_class, "input", "(I)I"); + m_output_func = m_env->GetStaticMethodID(s_adapter_class, "output", "(I[BI)I"); is_connected = true; @@ -110,7 +110,7 @@ int WiimoteAndroid::IOWrite(u8 const* buf, size_t len) void InitAdapterClass() { JNIEnv* env = IDCache::GetEnvForThread(); - jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter"); + jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/WiimoteAdapter"); s_adapter_class = reinterpret_cast(env->NewGlobalRef(adapter_class)); } } // namespace WiimoteReal diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 5cfe5139357..103af024fd7 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -180,14 +180,14 @@ static void ReadThreadFunc() bool first_read = true; JNIEnv* const env = IDCache::GetEnvForThread(); - const jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controller_payload", "[B"); + const jfieldID payload_field = env->GetStaticFieldID(s_adapter_class, "controllerPayload", "[B"); jobject payload_object = env->GetStaticObjectField(s_adapter_class, payload_field); auto* const java_controller_payload = reinterpret_cast(&payload_object); // Get function pointers - const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "GetFD", "()I"); - const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "Input", "()I"); - const jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "OpenAdapter", "()Z"); + const jmethodID getfd_func = env->GetStaticMethodID(s_adapter_class, "getFd", "()I"); + const jmethodID input_func = env->GetStaticMethodID(s_adapter_class, "input", "()I"); + const jmethodID openadapter_func = env->GetStaticMethodID(s_adapter_class, "openAdapter", "()Z"); const bool connected = env->CallStaticBooleanMethod(s_adapter_class, openadapter_func); @@ -279,7 +279,7 @@ static void WriteThreadFunc() int size = 0; #elif GCADAPTER_USE_ANDROID_IMPLEMENTATION JNIEnv* const env = IDCache::GetEnvForThread(); - const jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "Output", "([B)I"); + const jmethodID output_func = env->GetStaticMethodID(s_adapter_class, "output", "([B)I"); #endif while (s_write_adapter_thread_running.IsSet()) @@ -394,7 +394,7 @@ static void ScanThreadFunc() JNIEnv* const env = IDCache::GetEnvForThread(); const jmethodID queryadapter_func = - env->GetStaticMethodID(s_adapter_class, "QueryAdapter", "()Z"); + env->GetStaticMethodID(s_adapter_class, "queryAdapter", "()Z"); while (s_adapter_detect_thread_running.IsSet()) { @@ -456,7 +456,7 @@ void Init() #elif GCADAPTER_USE_ANDROID_IMPLEMENTATION JNIEnv* const env = IDCache::GetEnvForThread(); - const jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_GCAdapter"); + const jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/GCAdapter"); s_adapter_class = reinterpret_cast(env->NewGlobalRef(adapter_class)); #endif From e2e33becc9934f9af850fc2e2b5c77cf0eb15683 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 2 Jan 2025 17:22:29 +0100 Subject: [PATCH 2/5] 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. --- .../dolphinemu/utils/GCAdapter.java | 180 ++++++++++++++---- Source/Core/InputCommon/GCAdapter.cpp | 41 +++- 2 files changed, 172 insertions(+), 49 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 924496e4882..12ed20fec7f 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 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 devices = manager.getDeviceList(); for (Map.Entry 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 devices = manager.getDeviceList(); - for (Map.Entry 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); - } - } + usbOut = usbInterface.getEndpoint(i); - Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter, - Toast.LENGTH_LONG).show(); - usbConnection.close(); + 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 103af024fd7..2da4cf3f15a 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 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); From 185569778c062f52bdd87137f74dc0d4483b74cb Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 2 Jan 2025 17:37:57 +0100 Subject: [PATCH 3/5] Android: Detect GCAdapter disconnection using BroadcastReceiver This lets us get rid of the Reset call in ProcessInputPayload, which was causing us some threading headaches (see 74ed5e5532). Instead we handle disconnection in the same way as the libusb implementation does. --- Source/Core/InputCommon/GCAdapter.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 2da4cf3f15a..38c8220737d 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -134,10 +134,9 @@ static std::thread s_write_adapter_thread; static Common::Flag s_write_adapter_thread_running; static Common::Event s_write_happened; -static std::mutex s_read_mutex; -#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION static std::mutex s_init_mutex; -#elif GCADAPTER_USE_ANDROID_IMPLEMENTATION +static std::mutex s_read_mutex; +#if GCADAPTER_USE_ANDROID_IMPLEMENTATION static std::mutex s_write_mutex; #endif @@ -359,6 +358,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterDisconnected(JNIEnv* env, jclass) { INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter disconnected"); + if (s_detected) + Reset(CalledFromReadThread::No); } } #endif @@ -422,6 +423,7 @@ static void ScanThreadFunc() if (!s_detected && UseAdapter() && env->CallStaticBooleanMethod(s_adapter_class, is_usb_device_available_func)) { + std::lock_guard lk(s_init_mutex); Setup(); } @@ -735,10 +737,10 @@ void Shutdown() static void Reset(CalledFromReadThread called_from_read_thread) { -#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION std::unique_lock lock(s_init_mutex, std::defer_lock); if (!lock.try_lock()) return; +#if GCADAPTER_USE_LIBUSB_IMPLEMENTATION if (s_status != AdapterStatus::Detected) return; #elif GCADAPTER_USE_ANDROID_IMPLEMENTATION @@ -834,9 +836,6 @@ void ProcessInputPayload(const u8* data, std::size_t size) // This can occur for a few frames on initialization. ERROR_LOG_FMT(CONTROLLERINTERFACE, "error reading payload (size: {}, type: {:02x})", size, data[0]); -#if GCADAPTER_USE_ANDROID_IMPLEMENTATION - Reset(CalledFromReadThread::Yes); -#endif } else { From 1c7df370d9d5b443b5878c38aa03e1a64437309a Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 2 Jan 2025 17:50:17 +0100 Subject: [PATCH 4/5] Revert "Android/GCAdapter: Don't join current thread" This reverts commit 74ed5e55326b9d8e67dfbb8dd6f61d04bcae2445. It solves a problem that no longer exists. --- Source/Core/InputCommon/GCAdapter.cpp | 34 +++++++-------------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 38c8220737d..df212e8f4e8 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -66,13 +66,7 @@ static void AddGCAdapter(libusb_device* device); static void ResetRumbleLockNeeded(); #endif -enum class CalledFromReadThread -{ - No, - Yes, -}; - -static void Reset(CalledFromReadThread called_from_read_thread); +static void Reset(); static void Setup(); static void ProcessInputPayload(const u8* data, std::size_t size); static void ReadThreadFunc(); @@ -129,7 +123,6 @@ static std::atomic s_controller_write_payload_size{0}; static std::thread s_read_adapter_thread; static Common::Flag s_read_adapter_thread_running; -static Common::Flag s_read_adapter_thread_needs_joining; static std::thread s_write_adapter_thread; static Common::Flag s_write_adapter_thread_running; static Common::Event s_write_happened; @@ -330,7 +323,7 @@ static int HotplugCallback(libusb_context* ctx, libusb_device* dev, libusb_hotpl else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { if (s_handle != nullptr && libusb_get_device(s_handle) == dev) - Reset(CalledFromReadThread::No); + Reset(); // Reset a potential error status now that the adapter is unplugged if (s_status == AdapterStatus::Error) @@ -359,7 +352,7 @@ Java_org_dolphinemu_dolphinemu_utils_GCAdapter_onAdapterDisconnected(JNIEnv* env { INFO_LOG_FMT(CONTROLLERINTERFACE, "GC adapter disconnected"); if (s_detected) - Reset(CalledFromReadThread::No); + Reset(); } } #endif @@ -547,11 +540,8 @@ static void Setup() s_detected = true; // Make sure the thread isn't in the middle of shutting down while starting a new one - if (s_read_adapter_thread_needs_joining.TestAndClear() || - s_read_adapter_thread_running.TestAndClear()) - { + if (s_read_adapter_thread_running.TestAndClear()) s_read_adapter_thread.join(); - } s_read_adapter_thread_running.Set(true); s_read_adapter_thread = std::thread(ReadThreadFunc); @@ -721,7 +711,7 @@ void Shutdown() env->GetStaticMethodID(s_adapter_class, "disableHotplugCallback", "()V"); env->CallStaticVoidMethod(s_adapter_class, disable_hotplug_callback_func); #endif - Reset(CalledFromReadThread::No); + Reset(); #if GCADAPTER_USE_LIBUSB_IMPLEMENTATION s_libusb_context.reset(); @@ -735,7 +725,7 @@ void Shutdown() } } -static void Reset(CalledFromReadThread called_from_read_thread) +static void Reset() { std::unique_lock lock(s_init_mutex, std::defer_lock); if (!lock.try_lock()) @@ -748,16 +738,8 @@ static void Reset(CalledFromReadThread called_from_read_thread) return; #endif - if (called_from_read_thread == CalledFromReadThread::No) - { - if (s_read_adapter_thread_running.TestAndClear()) - s_read_adapter_thread.join(); - } - else - { - s_read_adapter_thread_needs_joining.Set(); - s_read_adapter_thread_running.Clear(); - } + if (s_read_adapter_thread_running.TestAndClear()) + s_read_adapter_thread.join(); // The read thread will close the write thread s_port_states.fill({}); From 8524e725a8e883a0c763b6d791095aba5ff5e5ab Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 27 Jul 2025 11:37:45 +0200 Subject: [PATCH 5/5] Android: Add additional null check in GCAdapter Just in case, since the documentation says this could be null. --- .../dolphinemu/utils/GCAdapter.java | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 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 12ed20fec7f..b9e67e654f3 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 @@ -142,43 +142,48 @@ public class GCAdapter { dev = adapterDevice; } - - if (dev != null) + 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) - { - 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; - } - else - { - usbConnection.releaseInterface(usbInterface); - } - } - - Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter, - Toast.LENGTH_LONG).show(); - usbConnection.close(); + return false; } + + usbConnection = manager.openDevice(dev); + if (usbConnection == null) + { + return false; + } + + 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; + } + else + { + usbConnection.releaseInterface(usbInterface); + } + } + + Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter, + Toast.LENGTH_LONG).show(); + usbConnection.close(); return false; }