mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-03-12 22:56:52 +01:00
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.
This commit is contained in:
parent
e1ebbd0819
commit
812ed34dc3
@ -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);
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user