Merge pull request #3593 from Sonicadvance1/Android_Mayflash_adapter

[Android] Implement support for real Wiimotes with the DolphinBar
This commit is contained in:
Ryan Houdek 2016-02-07 15:51:33 -05:00
commit 35d7c2bc78
9 changed files with 351 additions and 0 deletions

View File

@ -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

View File

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

View File

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

View File

@ -59,6 +59,11 @@
android:text="@string/settings"
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"

View File

@ -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>

View File

@ -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() ) )
{

View File

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

View File

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

View File

@ -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