diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java index 38ea670f5e..992b670b1f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/NativeLibrary.java @@ -379,6 +379,11 @@ public final class NativeLibrary /** Native EGL functions not exposed by Java bindings **/ public static native void eglBindAPI(int api); + /** + * Provides a way to refresh the connections on Wiimotes + */ + public static native void RefreshWiimotes(); + /** * The methods C++ uses to find references to Java classes and methods * are really expensive. Rather than calling them every time we want to diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java index 2f38df3373..f90956dd9a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.java @@ -33,6 +33,7 @@ import org.dolphinemu.dolphinemu.fragments.SaveStateFragment; import org.dolphinemu.dolphinemu.ui.main.MainPresenter; import org.dolphinemu.dolphinemu.utils.Animations; import org.dolphinemu.dolphinemu.utils.Java_GCAdapter; +import org.dolphinemu.dolphinemu.utils.Java_WiimoteAdapter; import org.dolphinemu.dolphinemu.utils.Log; import java.util.List; @@ -120,6 +121,7 @@ public final class EmulationActivity extends AppCompatActivity super.onCreate(savedInstanceState); Java_GCAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); + Java_WiimoteAdapter.manager = (UsbManager) getSystemService(Context.USB_SERVICE); // Picasso will take a while to load these big-ass screenshots. So don't run // the animation until we say so. @@ -392,6 +394,10 @@ public final class EmulationActivity extends AppCompatActivity return; } + case R.id.menu_refresh_wiimotes: + NativeLibrary.RefreshWiimotes(); + return; + // Screenshot capturing case R.id.menu_emulation_screenshot: NativeLibrary.SaveScreenShot(); 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/Java_WiimoteAdapter.java new file mode 100644 index 0000000000..049f930cba --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter.java @@ -0,0 +1,156 @@ +package org.dolphinemu.dolphinemu.utils; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Intent; +import android.hardware.usb.UsbConfiguration; +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 org.dolphinemu.dolphinemu.NativeLibrary; +import org.dolphinemu.dolphinemu.services.USBPermService; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +public class Java_WiimoteAdapter +{ + final static int MAX_PAYLOAD = 23; + final static int MAX_WIIMOTES = 4; + final static int TIMEOUT = 200; + final static short NINTENDO_VENDOR_ID = 0x057e; + 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]; + + public static byte[][] wiimote_payload = new byte[MAX_WIIMOTES][MAX_PAYLOAD]; + + private static void RequestPermission() + { + HashMap devices = manager.getDeviceList(); + for (Map.Entry pair : devices.entrySet()) + { + UsbDevice dev = (UsbDevice) pair.getValue(); + if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID) + { + if (!manager.hasPermission(dev)) + { + Log.warning("Requesting permission for Wiimote adapter"); + Intent intent = new Intent(); + PendingIntent pend_intent; + intent.setClass(NativeLibrary.sEmulationActivity, USBPermService.class); + pend_intent = PendingIntent.getService(NativeLibrary.sEmulationActivity, 0, intent, 0); + manager.requestPermission(dev, pend_intent); + } + } + } + } + + public static boolean QueryAdapter() + { + HashMap devices = manager.getDeviceList(); + for (Map.Entry pair : devices.entrySet()) + { + UsbDevice dev = (UsbDevice) pair.getValue(); + if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID) + { + if (manager.hasPermission(dev)) + return true; + else + RequestPermission(); + } + } + return false; + } + + public static int Input(int index) + { + return usb_con.bulkTransfer(usb_in[index], wiimote_payload[index], MAX_PAYLOAD, TIMEOUT); + } + + public static int Output(int index, byte[] buf, int size) + { + byte report_number = buf[0]; + + // Remove the report number from the buffer + buf = Arrays.copyOfRange(buf, 1, buf.length); + size--; + + final int LIBUSB_REQUEST_TYPE_CLASS = (1 << 5); + final int LIBUSB_RECIPIENT_INTERFACE = 0x1; + final int LIBUSB_ENDPOINT_OUT = 0; + + final int HID_SET_REPORT = 0x9; + final int HID_OUTPUT = (2 << 8); + + int write = usb_con.controlTransfer( + LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT, + HID_SET_REPORT, + HID_OUTPUT | report_number, + index, + buf, size, + 1000); + + if (write < 0) + return -1; + + return write + 1; + } + + 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) + return true; + + HashMap devices = manager.getDeviceList(); + for (Map.Entry pair : devices.entrySet()) + { + UsbDevice dev = (UsbDevice) pair.getValue(); + if (dev.getProductId() == NINTENDO_WIIMOTE_PRODUCT_ID && dev.getVendorId() == NINTENDO_VENDOR_ID) + { + if (manager.hasPermission(dev)) + { + usb_con = manager.openDevice(dev); + UsbConfiguration conf = dev.getConfiguration(0); + + Log.info("Number of configurations: " + dev.getConfigurationCount()); + Log.info("Number of Interfaces: " + dev.getInterfaceCount()); + Log.info("Number of Interfaces from conf: " + conf.getInterfaceCount()); + + // Sometimes the interface count is returned as zero. + // Means the device needs to be unplugged and plugged back in again + if (dev.getInterfaceCount() > 0) + { + for (int i = 0; i < MAX_WIIMOTES; ++i) + { + // One interface per Wiimote + usb_intf[i] = dev.getInterface(i); + usb_con.claimInterface(usb_intf[i], true); + + // One endpoint per Wiimote. 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()); + } + return true; + } + else + { + // XXX: Message that the device was found, but it needs to be unplugged and plugged back in? + usb_con.close(); + } + } + } + } + return false; + } +} diff --git a/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml b/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml index 44093dc324..e218fac5b5 100644 --- a/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml +++ b/Source/Android/app/src/main/res/layout/fragment_ingame_menu.xml @@ -59,6 +59,11 @@ android:text="@string/settings" style="@style/InGameMenuOption"/> +