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:
JosJuice
2025-01-02 17:22:29 +01:00
parent 7508842859
commit e2e33becc9
2 changed files with 172 additions and 49 deletions

View File

@@ -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();
}