diff options
| author | Ryan Houdek <Sonicadvance1@gmail.com> | 2016-02-07 15:51:33 -0500 |
|---|---|---|
| committer | Ryan Houdek <Sonicadvance1@gmail.com> | 2016-02-07 15:51:33 -0500 |
| commit | 35d7c2bc78007f550474f035c486bcc5f79ef60f (patch) | |
| tree | 862050acf1357424dbd0912dc5937ba1b0601393 /Source | |
| parent | 76d614ededd95deb4e6dea774541d26827d5c4cc (diff) | |
| parent | fe53461611d86384478064be6b4297a7c7e437e1 (diff) | |
Merge pull request #3593 from Sonicadvance1/Android_Mayflash_adapter
[Android] Implement support for real Wiimotes with the DolphinBar
Diffstat (limited to 'Source')
9 files changed, 351 insertions, 0 deletions
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 @@ -380,6 +380,11 @@ public final class NativeLibrary 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 * run them, do it once when we load the native library. 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<String, UsbDevice> devices = manager.getDeviceList(); + for (Map.Entry<String, UsbDevice> 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<String, UsbDevice> devices = manager.getDeviceList(); + for (Map.Entry<String, UsbDevice> 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<String, UsbDevice> devices = manager.getDeviceList(); + for (Map.Entry<String, UsbDevice> 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 @@ -60,6 +60,11 @@ style="@style/InGameMenuOption"/> <Button + android:id="@+id/menu_refresh_wiimotes" + android:text="@string/emulation_refresh_wiimotes" + style="@style/InGameMenuOption"/> + + <Button android:id="@+id/menu_exit" android:text="@string/overlay_exit_emulation" style="@style/InGameMenuOption"/> diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 4ae5e14c12..e3e309ca73 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -346,6 +346,7 @@ <string name="emulation_toggle_input">Toggle Touch Controls</string> <string name="emulation_quicksave">Quick Save</string> <string name="emulation_quickload">Quick Load</string> + <string name="emulation_refresh_wiimotes">Refresh Wiimotes</string> <!-- GC Adapter Menu--> <string name="gc_adapter_rumble">Enable Vibration</string> diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index ffa0eca40a..7d2849c96e 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -27,6 +27,7 @@ #include "Core/Host.h" #include "Core/State.h" #include "Core/HW/Wiimote.h" +#include "Core/HW/WiimoteReal/WiimoteReal.h" #include "Core/PowerPC/JitInterface.h" #include "Core/PowerPC/PowerPC.h" #include "Core/PowerPC/Profiler.h" @@ -632,6 +633,10 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr Renderer::s_ChangedSurface.Wait(); } } +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_RefreshWiimotes(JNIEnv *env, jobject obj) +{ + WiimoteReal::Refresh(); +} JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv *env, jobject obj) { @@ -646,6 +651,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_Run(JNIEnv * UICommon::SetUserDirectory(g_set_userpath); UICommon::Init(); + WiimoteReal::InitAdapterClass(); + // No use running the loop when booting fails if ( BootManager::BootCore( g_filename.c_str() ) ) { diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index b035b64859..4fe1353d23 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -256,6 +256,8 @@ elseif(UNIX) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND BLUEZ_FOUND) set(SRCS ${SRCS} HW/WiimoteReal/IONix.cpp) set(LIBS ${LIBS} bluetooth) + elseif(ANDROID) + set(SRCS ${SRCS} HW/WiimoteReal/IOAndroid.cpp) else() set(SRCS ${SRCS} HW/WiimoteReal/IODummy.cpp) endif() diff --git a/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp new file mode 100644 index 0000000000..7a2e1e249c --- /dev/null +++ b/Source/Core/Core/HW/WiimoteReal/IOAndroid.cpp @@ -0,0 +1,165 @@ +// Copyright 2016 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <jni.h> + +#include "Common/CommonTypes.h" +#include "Common/Event.h" +#include "Common/Flag.h" +#include "Common/StringUtil.h" +#include "Common/Thread.h" +#include "Common/Timer.h" +#include "Common/Logging/Log.h" + +#include "Core/HW/WiimoteReal/WiimoteReal.h" + +// Global java_vm class +extern JavaVM* g_java_vm; + +namespace WiimoteReal +{ + +// Java classes +static jclass s_adapter_class; + +class WiimoteAndroid final : public Wiimote +{ +public: + WiimoteAndroid(int index); + ~WiimoteAndroid() override; + +protected: + bool ConnectInternal() override; + void DisconnectInternal() override; + bool IsConnected() const override; + void IOWakeup() {} + int IORead(u8* buf) override; + int IOWrite(u8 const* buf, size_t len) override; + +private: + int m_mayflash_index; + bool is_connected = true; + + JNIEnv* m_env; + + jmethodID m_input_func; + jmethodID m_output_func; + + jbyteArray m_java_wiimote_payload; +}; + +WiimoteScanner::WiimoteScanner() +{} + +WiimoteScanner::~WiimoteScanner() +{} + +void WiimoteScanner::Update() +{} + +void WiimoteScanner::FindWiimotes(std::vector<Wiimote*>& found_wiimotes, Wiimote*& found_board) +{ + found_wiimotes.clear(); + found_board = nullptr; + + NOTICE_LOG(WIIMOTE, "Finding Wiimotes"); + + JNIEnv* env; + int get_env_status = g_java_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); + + if (get_env_status == JNI_EDETACHED) + g_java_vm->AttachCurrentThread(&env, nullptr); + + 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)) + { + for (int i = 0; i < MAX_WIIMOTES; ++i) + found_wiimotes.emplace_back(new WiimoteAndroid(i)); + } + + if (get_env_status == JNI_EDETACHED) + g_java_vm->DetachCurrentThread(); +} + +bool WiimoteScanner::IsReady() const +{ + return true; +} + +WiimoteAndroid::WiimoteAndroid(int index) + : Wiimote(), m_mayflash_index(index) +{ +} + +WiimoteAndroid::~WiimoteAndroid() +{ + Shutdown(); +} + +// Connect to a Wiimote with a known address. +bool WiimoteAndroid::ConnectInternal() +{ + g_java_vm->AttachCurrentThread(&m_env, nullptr); + + jfieldID payload_field = m_env->GetStaticFieldID(s_adapter_class, "wiimote_payload", "[[B"); + jobjectArray payload_object = reinterpret_cast<jobjectArray>(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"); + + is_connected = true; + + return true; +} + +void WiimoteAndroid::DisconnectInternal() +{ + g_java_vm->DetachCurrentThread(); +} + +bool WiimoteAndroid::IsConnected() const +{ + return is_connected; +} + +// positive = read packet +// negative = didn't read packet +// zero = error +int WiimoteAndroid::IORead(u8* buf) +{ + int read_size = m_env->CallStaticIntMethod(s_adapter_class, m_input_func, m_mayflash_index); + jbyte* java_data = m_env->GetByteArrayElements(m_java_wiimote_payload, nullptr); + memcpy(buf + 1, java_data, std::min(MAX_PAYLOAD - 1, read_size)); + buf[0] = 0xA1; + m_env->ReleaseByteArrayElements(m_java_wiimote_payload, java_data, 0); + return read_size <= 0 ? read_size : read_size + 1; +} + + +int WiimoteAndroid::IOWrite(u8 const* buf, size_t len) +{ + jbyteArray output_array = m_env->NewByteArray(len); + jbyte* output = m_env->GetByteArrayElements(output_array, nullptr); + memcpy(output, buf, len); + m_env->ReleaseByteArrayElements(output_array, output, 0); + int written = m_env->CallStaticIntMethod(s_adapter_class, m_output_func, m_mayflash_index, output_array, len); + m_env->DeleteLocalRef(output_array); + return written; +} + +void InitAdapterClass() +{ + JNIEnv* env; + g_java_vm->AttachCurrentThread(&env, nullptr); + + jclass adapter_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Java_WiimoteAdapter"); + s_adapter_class = reinterpret_cast<jclass>(env->NewGlobalRef(adapter_class)); +} + +} diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h index 1ca31a8d69..60c5e38bbb 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h @@ -161,4 +161,8 @@ void ChangeWiimoteSource(unsigned int index, int source); bool IsValidBluetoothName(const std::string& name); bool IsBalanceBoardName(const std::string& name); +#ifdef ANDROID +void InitAdapterClass(); +#endif + } // WiimoteReal |
