mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-05-03 09:50:35 -05:00
Merge pull request #13260 from JosJuice/android-gcadapter-hotplug-callback
Android: Detect GCAdapter hotplug using BroadcastReceiver
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
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;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
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 java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class GCAdapter
|
||||
{
|
||||
public static UsbManager manager;
|
||||
|
||||
@Keep
|
||||
static byte[] controllerPayload = new byte[37];
|
||||
|
||||
static UsbDeviceConnection usbConnection;
|
||||
static UsbInterface usbInterface;
|
||||
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();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
{
|
||||
UsbDevice dev = pair.getValue();
|
||||
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
|
||||
{
|
||||
if (!manager.hasPermission(dev))
|
||||
{
|
||||
Context context = DolphinApplication.getAppContext();
|
||||
|
||||
int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ?
|
||||
PendingIntent.FLAG_IMMUTABLE : 0;
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
|
||||
new Intent(ACTION_GC_ADAPTER_PERMISSION_GRANTED), flags);
|
||||
|
||||
manager.requestPermission(dev, pendingIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void shutdown()
|
||||
{
|
||||
usbConnection.close();
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static int getFd()
|
||||
{
|
||||
return usbConnection.getFileDescriptor();
|
||||
}
|
||||
|
||||
@Keep
|
||||
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())
|
||||
{
|
||||
UsbDevice dev = pair.getValue();
|
||||
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
|
||||
{
|
||||
if (manager.hasPermission(dev))
|
||||
return dev;
|
||||
else
|
||||
requestPermission();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void initAdapter()
|
||||
{
|
||||
byte[] init = {0x13};
|
||||
usbConnection.bulkTransfer(usbOut, init, init.length, 0);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static int input()
|
||||
{
|
||||
return usbConnection.bulkTransfer(usbIn, controllerPayload, controllerPayload.length, 16);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static int output(byte[] rumble)
|
||||
{
|
||||
return usbConnection.bulkTransfer(usbOut, rumble, 5, 16);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static boolean openAdapter()
|
||||
{
|
||||
UsbDevice dev;
|
||||
synchronized (hotplugCallbackLock)
|
||||
{
|
||||
dev = adapterDevice;
|
||||
}
|
||||
if (dev == null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.hardware.usb.UsbConfiguration;
|
||||
import android.hardware.usb.UsbConstants;
|
||||
import android.hardware.usb.UsbDevice;
|
||||
import android.hardware.usb.UsbDeviceConnection;
|
||||
import android.hardware.usb.UsbEndpoint;
|
||||
import android.hardware.usb.UsbInterface;
|
||||
import android.hardware.usb.UsbManager;
|
||||
import android.os.Build;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
import org.dolphinemu.dolphinemu.R;
|
||||
import org.dolphinemu.dolphinemu.services.USBPermService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Java_GCAdapter
|
||||
{
|
||||
public static UsbManager manager;
|
||||
|
||||
@Keep
|
||||
static byte[] controller_payload = new byte[37];
|
||||
|
||||
static UsbDeviceConnection usb_con;
|
||||
static UsbInterface usb_intf;
|
||||
static UsbEndpoint usb_in;
|
||||
static UsbEndpoint usb_out;
|
||||
|
||||
private static void RequestPermission()
|
||||
{
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
{
|
||||
UsbDevice dev = pair.getValue();
|
||||
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
|
||||
{
|
||||
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);
|
||||
|
||||
manager.requestPermission(dev, pendingIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
usb_con.close();
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static int GetFD()
|
||||
{
|
||||
return usb_con.getFileDescriptor();
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static boolean QueryAdapter()
|
||||
{
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
{
|
||||
UsbDevice dev = pair.getValue();
|
||||
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
|
||||
{
|
||||
if (manager.hasPermission(dev))
|
||||
return true;
|
||||
else
|
||||
RequestPermission();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void InitAdapter()
|
||||
{
|
||||
byte[] init = {0x13};
|
||||
usb_con.bulkTransfer(usb_out, init, init.length, 0);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static int Input()
|
||||
{
|
||||
return usb_con.bulkTransfer(usb_in, controller_payload, controller_payload.length, 16);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static int Output(byte[] rumble)
|
||||
{
|
||||
return usb_con.bulkTransfer(usb_out, rumble, 5, 16);
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static boolean OpenAdapter()
|
||||
{
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
{
|
||||
UsbDevice dev = pair.getValue();
|
||||
if (dev.getProductId() == 0x0337 && dev.getVendorId() == 0x057e)
|
||||
{
|
||||
if (manager.hasPermission(dev))
|
||||
{
|
||||
usb_con = 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);
|
||||
usb_intf = conf.getInterface(0);
|
||||
usb_con.claimInterface(usb_intf, true);
|
||||
|
||||
Log.info("GCAdapter: Number of endpoints: " + usb_intf.getEndpointCount());
|
||||
|
||||
if (usb_intf.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);
|
||||
else
|
||||
usb_out = usb_intf.getEndpoint(i);
|
||||
|
||||
InitAdapter();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
usb_con.releaseInterface(usb_intf);
|
||||
}
|
||||
}
|
||||
|
||||
Toast.makeText(DolphinApplication.getAppContext(), R.string.replug_gc_adapter,
|
||||
Toast.LENGTH_LONG).show();
|
||||
usb_con.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+20
-20
@@ -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<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> pair : devices.entrySet())
|
||||
@@ -65,7 +65,7 @@ public class Java_WiimoteAdapter
|
||||
}
|
||||
|
||||
@Keep
|
||||
public static boolean QueryAdapter()
|
||||
public static boolean queryAdapter()
|
||||
{
|
||||
HashMap<String, UsbDevice> devices = manager.getDeviceList();
|
||||
for (Map.Entry<String, UsbDevice> 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<String, UsbDevice> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user