package org.protocoderrunner.apprunner.api.other;
import android.content.Context;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jp.kshoji.driver.midi.device.MidiInputDevice;
import jp.kshoji.driver.midi.device.MidiOutputDevice;
import jp.kshoji.driver.midi.listener.OnMidiDeviceAttachedListener;
import jp.kshoji.driver.midi.listener.OnMidiDeviceDetachedListener;
import jp.kshoji.driver.midi.listener.OnMidiInputEventListener;
import jp.kshoji.driver.midi.thread.MidiDeviceConnectionWatcher;
import jp.kshoji.driver.midi.util.Constants;
import jp.kshoji.driver.midi.util.UsbMidiDeviceUtils;
import jp.kshoji.driver.usb.util.DeviceFilter;
/**
* Driver for USB MIDI devices.
*
* @author K.Shoji
*/
public abstract class UsbMidiDriver implements OnMidiDeviceDetachedListener, OnMidiDeviceAttachedListener, OnMidiInputEventListener {
private boolean isOpen = false;
/**
* Implementation for multiple device connections.
*
* @author K.Shoji
*/
final class OnMidiDeviceAttachedListenerImpl implements OnMidiDeviceAttachedListener {
private final UsbManager usbManager;
/**
* constructor
*
* @param usbManager
*/
public OnMidiDeviceAttachedListenerImpl(UsbManager usbManager) {
this.usbManager = usbManager;
}
/*
* (non-Javadoc)
* @see jp.kshoji.driver.midi.listener.OnMidiDeviceAttachedListener#onDeviceAttached(android.hardware.usb.UsbDevice)
*/
@Override
public synchronized void onDeviceAttached(UsbDevice attachedDevice) {
// these fields are null; when this event fired while Activity destroying.
if (midiInputDevices == null || midiOutputDevices == null || deviceConnections == null) {
// nothing to do
return;
}
deviceConnectionWatcher.notifyDeviceGranted();
UsbDeviceConnection deviceConnection = usbManager.openDevice(attachedDevice);
if (deviceConnection == null) {
return;
}
deviceConnections.put(attachedDevice, deviceConnection);
List<DeviceFilter> deviceFilters = DeviceFilter.getDeviceFilters(context.getApplicationContext());
Set<MidiInputDevice> foundInputDevices = UsbMidiDeviceUtils.findMidiInputDevices(attachedDevice, deviceConnection, deviceFilters, UsbMidiDriver.this);
for (MidiInputDevice midiInputDevice : foundInputDevices) {
try {
Set<MidiInputDevice> inputDevices = midiInputDevices.get(attachedDevice);
if (inputDevices == null) {
inputDevices = new HashSet<MidiInputDevice>();
}
inputDevices.add(midiInputDevice);
midiInputDevices.put(attachedDevice, inputDevices);
} catch (IllegalArgumentException iae) {
Log.d(Constants.TAG, "This device didn't have any input endpoints.", iae);
}
}
Set<MidiOutputDevice> foundOutputDevices = UsbMidiDeviceUtils.findMidiOutputDevices(attachedDevice, deviceConnection, deviceFilters);
for (MidiOutputDevice midiOutputDevice : foundOutputDevices) {
try {
Set<MidiOutputDevice> outputDevices = midiOutputDevices.get(attachedDevice);
if (outputDevices == null) {
outputDevices = new HashSet<MidiOutputDevice>();
}
outputDevices.add(midiOutputDevice);
midiOutputDevices.put(attachedDevice, outputDevices);
} catch (IllegalArgumentException iae) {
Log.d(Constants.TAG, "This device didn't have any output endpoints.", iae);
}
}
Log.d(Constants.TAG, "Device " + attachedDevice.getDeviceName() + " has been attached.");
UsbMidiDriver.this.onDeviceAttached(attachedDevice);
}
}
/**
* Implementation for multiple device connections.
*
* @author K.Shoji
*/
final class OnMidiDeviceDetachedListenerImpl implements OnMidiDeviceDetachedListener {
/*
* (non-Javadoc)
* @see jp.kshoji.driver.midi.listener.OnMidiDeviceDetachedListener#onDeviceDetached(android.hardware.usb.UsbDevice)
*/
@Override
public synchronized void onDeviceDetached(UsbDevice detachedDevice) {
// these fields are null; when this event fired while Activity destroying.
if (midiInputDevices == null || midiOutputDevices == null || deviceConnections == null) {
// nothing to do
return;
}
AsyncTask<UsbDevice, Void, Void> task = new AsyncTask<UsbDevice, Void, Void>() {
@Override
protected Void doInBackground(UsbDevice... params) {
if (params == null || params.length < 1) {
return null;
}
UsbDevice usbDevice = params[0];
// Stop input device's thread.
Set<MidiInputDevice> inputDevices = midiInputDevices.get(usbDevice);
if (inputDevices != null && inputDevices.size() > 0) {
for (MidiInputDevice inputDevice : inputDevices) {
if (inputDevice != null) {
inputDevice.stop();
}
}
midiInputDevices.remove(usbDevice);
}
Set<MidiOutputDevice> outputDevices = midiOutputDevices.get(usbDevice);
if (outputDevices != null) {
for (MidiOutputDevice outputDevice : outputDevices) {
if (outputDevice != null) {
outputDevice.stop();
}
}
midiOutputDevices.remove(usbDevice);
}
UsbDeviceConnection deviceConnection = deviceConnections.get(usbDevice);
if (deviceConnection != null) {
deviceConnection.close();
deviceConnections.remove(usbDevice);
}
Log.d(Constants.TAG, "Device " + usbDevice.getDeviceName() + " has been detached.");
Message message = Message.obtain(deviceDetachedHandler);
message.obj = usbDevice;
deviceDetachedHandler.sendMessage(message);
return null;
}
};
task.execute(detachedDevice);
}
}
Map<UsbDevice, UsbDeviceConnection> deviceConnections = null;
Map<UsbDevice, Set<MidiInputDevice>> midiInputDevices = null;
Map<UsbDevice, Set<MidiOutputDevice>> midiOutputDevices = null;
OnMidiDeviceAttachedListener deviceAttachedListener = null;
OnMidiDeviceDetachedListener deviceDetachedListener = null;
Handler deviceDetachedHandler = null;
MidiDeviceConnectionWatcher deviceConnectionWatcher = null;
private final Context context;
/**
* Constructor
*
* @param context Activity context
*/
protected UsbMidiDriver(Context context) {
this.context = context;
}
/**
* Starts using UsbMidiDriver.
*
* Starts the USB device watching and communicating thread.
*/
public final void open() {
if (isOpen) {
// already opened
return;
}
isOpen = true;
deviceConnections = new HashMap<UsbDevice, UsbDeviceConnection>();
midiInputDevices = new HashMap<UsbDevice, Set<MidiInputDevice>>();
midiOutputDevices = new HashMap<UsbDevice, Set<MidiOutputDevice>>();
UsbManager usbManager = (UsbManager) context.getApplicationContext().getSystemService(Context.USB_SERVICE);
deviceAttachedListener = new OnMidiDeviceAttachedListenerImpl(usbManager);
deviceDetachedListener = new OnMidiDeviceDetachedListenerImpl();
deviceDetachedHandler = new Handler(new Handler.Callback() {
/*
* (non-Javadoc)
* @see android.os.Handler.Callback#handleMessage(android.os.Message)
*/
@Override
public boolean handleMessage(Message msg) {
Log.d(Constants.TAG, "(handleMessage) detached device:" + msg.obj);
UsbDevice usbDevice = (UsbDevice) msg.obj;
onDeviceDetached(usbDevice);
return true;
}
});
deviceConnectionWatcher = new MidiDeviceConnectionWatcher(context.getApplicationContext(), usbManager, deviceAttachedListener, deviceDetachedListener);
}
/**
* Stops using UsbMidiDriver.
*
* Shutdown the USB device communicating thread.
* The all connected devices will be closed.
*/
public final void close() {
if (!isOpen) {
// already closed
return;
}
isOpen = false;
deviceConnectionWatcher.stop();
deviceConnectionWatcher = null;
if (midiInputDevices != null) {
for (Set<MidiInputDevice> inputDevices : midiInputDevices.values()) {
if (inputDevices != null) {
for (MidiInputDevice inputDevice : inputDevices) {
if (inputDevice != null) {
inputDevice.stop();
}
}
}
}
midiInputDevices.clear();
}
midiInputDevices = null;
if (midiOutputDevices != null) {
midiOutputDevices.clear();
}
midiOutputDevices = null;
deviceConnections = null;
}
/**
* Suspends receiving/transmitting MIDI messages.
* All events will be discarded until the devices being resumed.
*/
protected final void suspend() {
if (midiInputDevices != null) {
for (Set<MidiInputDevice> inputDevices : midiInputDevices.values()) {
if (inputDevices != null) {
for (MidiInputDevice inputDevice : inputDevices) {
if (inputDevice != null) {
inputDevice.suspend();
}
}
}
}
}
if (midiOutputDevices != null) {
for (Set<MidiOutputDevice> outputDevices : midiOutputDevices.values()) {
if (outputDevices != null) {
for (MidiOutputDevice outputDevice : outputDevices) {
if (outputDevice != null) {
outputDevice.suspend();
}
}
}
}
}
}
/**
* Resumes from {@link #suspend()}
*/
protected final void resume() {
if (midiInputDevices != null) {
for (Set<MidiInputDevice> inputDevices : midiInputDevices.values()) {
if (inputDevices != null) {
for (MidiInputDevice inputDevice : inputDevices) {
if (inputDevice != null) {
inputDevice.resume();
}
}
}
}
}
if (midiOutputDevices != null) {
for (Set<MidiOutputDevice> outputDevices : midiOutputDevices.values()) {
if (outputDevices != null) {
for (MidiOutputDevice outputDevice : outputDevices) {
if (outputDevice != null) {
outputDevice.resume();
}
}
}
}
}
}
/**
* Get connected USB MIDI devices.
*
* @return connected UsbDevice set
*/
public final Set<UsbDevice> getConnectedUsbDevices() {
if (deviceConnectionWatcher != null) {
deviceConnectionWatcher.checkConnectedDevicesImmediately();
}
if (deviceConnections != null) {
return Collections.unmodifiableSet(deviceConnections.keySet());
}
return Collections.unmodifiableSet(new HashSet<UsbDevice>());
}
/**
* Get MIDI output device, if available.
*
* @param usbDevice
* @return {@link Set<MidiOutputDevice>}
*/
public final Set<MidiOutputDevice> getMidiOutputDevices(UsbDevice usbDevice) {
if (deviceConnectionWatcher != null) {
deviceConnectionWatcher.checkConnectedDevicesImmediately();
}
if (midiOutputDevices != null && midiOutputDevices.get(usbDevice) != null) {
return Collections.unmodifiableSet(midiOutputDevices.get(usbDevice));
}
return Collections.unmodifiableSet(new HashSet<MidiOutputDevice>());
}
/**
* RPN message
* This method is just the utility method, do not need to be implemented necessarily by subclass.
*
* @param sender
* @param cable
* @param channel
* @param function 14bits
* @param valueMSB higher 7bits
* @param valueLSB lower 7bits. -1 if value has no LSB. If you know the function's parameter value have LSB, you must ignore when valueLSB < 0.
*/
@Override
public void onMidiRPNReceived(MidiInputDevice sender, int cable, int channel, int function, int valueMSB, int valueLSB) {
// do nothing in this implementation
}
/**
* NRPN message
* This method is just the utility method, do not need to be implemented necessarily by subclass.
*
* @param sender
* @param cable
* @param channel
* @param function 14bits
* @param valueMSB higher 7bits
* @param valueLSB lower 7bits. -1 if value has no LSB. If you know the function's parameter value have LSB, you must ignore when valueLSB < 0.
*/
@Override
public void onMidiNRPNReceived(MidiInputDevice sender, int cable, int channel, int function, int valueMSB, int valueLSB) {
// do nothing in this implementation
}
}