From e1ebbd0819664749ad311646f0b8285c10513753 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 2 Jan 2025 15:39:41 +0100 Subject: [PATCH 1/4] 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 e0816e408bb8..9ced73b403ff 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 @@ -8,8 +8,8 @@ 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 @@ -25,8 +25,8 @@ public void onCreate() 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 3fcd58afacb1..924496e48826 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 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 @@ private static void RequestPermission() } } - 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 static boolean QueryAdapter() 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 static boolean OpenAdapter() { 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 static boolean OpenAdapter() 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 469375614f65..98a7d5190045 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.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 @@ private static void RequestPermission() } @Keep - public static boolean QueryAdapter() + public static boolean queryAdapter() { HashMap devices = manager.getDeviceList(); for (Map.Entry pair : devices.entrySet()) @@ -77,20 +77,20 @@ public static boolean QueryAdapter() 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 static int Output(int index, byte[] buf, int size) 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 static int Output(int index, byte[] buf, int size) } @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 static boolean OpenAdapter() { 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 static boolean OpenAdapter() 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 10234ceef7a7..507cacf00676 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 bf7b1f136cf1..5764aa660b75 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 812ed34dc3c4e54dbb7abf3c80c135e782c7f9cd Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 2 Jan 2025 17:22:29 +0100 Subject: [PATCH 2/4] 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 | 182 +++++++++++++----- Source/Core/InputCommon/GCAdapter.cpp | 39 +++- 2 files changed, 173 insertions(+), 48 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 924496e48826..12ed20fec7f1 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.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 @@ private static void requestPermission() 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 static int getFd() } @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 static boolean queryAdapter() 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 static int output(byte[] rumble) @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); - } - } - - 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 5764aa660b75..b02306806d66 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 @@ -691,6 +715,11 @@ void Shutdown() if (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 44f1c2010c545556a27426e0102002085019b0c9 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 2 Jan 2025 17:37:57 +0100 Subject: [PATCH 3/4] 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 b02306806d66..fa847d8c8dd2 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(); } @@ -737,10 +739,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 @@ -836,9 +838,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 b134d7c2b9607a77fe4a8a6beb8264a177351659 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 2 Jan 2025 17:50:17 +0100 Subject: [PATCH 4/4] 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 fa847d8c8dd2..f883e0d3a957 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 @@ -549,11 +542,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); @@ -723,7 +713,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(); @@ -737,7 +727,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()) @@ -750,16 +740,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({});