juicysfplugin/modules/juce_core/native/java/AndroidMidi.java

1000 lines
34 KiB
Java

//==============================================================================
public class BluetoothManager extends ScanCallback
{
BluetoothManager()
{
}
public String[] getMidiBluetoothAddresses()
{
return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]);
}
public String getHumanReadableStringForBluetoothAddress (String address)
{
BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
return btDevice.getName();
}
public int getBluetoothDeviceStatus (String address)
{
return getAndroidMidiDeviceManager().getBluetoothDeviceStatus (address);
}
public void startStopScan (boolean shouldStart)
{
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null)
{
Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter");
return;
}
BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
if (bluetoothLeScanner == null)
{
Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner");
return;
}
if (shouldStart)
{
ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();
scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID));
ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setScanMode (ScanSettings.SCAN_MODE_LOW_POWER)
.setScanMode (ScanSettings.MATCH_MODE_STICKY);
bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()),
scanSettingsBuilder.build(),
this);
}
else
{
bluetoothLeScanner.stopScan (this);
}
}
public boolean pairBluetoothMidiDevice(String address)
{
BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
if (btDevice == null)
{
Log.d ("JUCE", "failed to create buletooth device from address");
return false;
}
return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice);
}
public void unpairBluetoothMidiDevice (String address)
{
getAndroidMidiDeviceManager().unpairBluetoothDevice (address);
}
public void onScanFailed (int errorCode)
{
}
public void onScanResult (int callbackType, ScanResult result)
{
if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
|| callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
{
BluetoothDevice device = result.getDevice();
if (device != null)
bluetoothMidiDevices.add (device.getAddress());
}
if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST)
{
Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST");
BluetoothDevice device = result.getDevice();
if (device != null)
{
bluetoothMidiDevices.remove (device.getAddress());
unpairBluetoothMidiDevice (device.getAddress());
}
}
}
public void onBatchScanResults (List<ScanResult> results)
{
for (ScanResult result : results)
onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
}
private BluetoothLeScanner scanner;
private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
private HashSet<String> bluetoothMidiDevices = new HashSet<String>();
}
public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort
{
private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp);
public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse)
{
owner = mm;
androidPort = actualPort;
portPath = portPathToUse;
juceHost = hostToUse;
isConnected = false;
}
@Override
protected void finalize() throws Throwable
{
close();
super.finalize();
}
@Override
public boolean isInputPort()
{
return true;
}
@Override
public void start()
{
if (owner != null && androidPort != null && ! isConnected) {
androidPort.connect(this);
isConnected = true;
}
}
@Override
public void stop()
{
if (owner != null && androidPort != null && isConnected) {
androidPort.disconnect(this);
isConnected = false;
}
}
@Override
public void close()
{
if (androidPort != null) {
try {
androidPort.close();
} catch (IOException exception) {
Log.d("JUCE", "IO Exception while closing port");
}
}
if (owner != null)
owner.removePort (portPath);
owner = null;
androidPort = null;
}
@Override
public void onSend (byte[] msg, int offset, int count, long timestamp)
{
if (count > 0)
handleReceive (juceHost, msg, offset, count, timestamp);
}
@Override
public void onFlush()
{}
@Override
public void sendMidi (byte[] msg, int offset, int count)
{
}
MidiDeviceManager owner;
MidiOutputPort androidPort;
MidiPortPath portPath;
long juceHost;
boolean isConnected;
}
public static class JuceMidiOutputPort implements JuceMidiPort
{
public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse)
{
owner = mm;
androidPort = actualPort;
portPath = portPathToUse;
}
@Override
protected void finalize() throws Throwable
{
close();
super.finalize();
}
@Override
public boolean isInputPort()
{
return false;
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void sendMidi (byte[] msg, int offset, int count)
{
if (androidPort != null)
{
try {
androidPort.send(msg, offset, count);
} catch (IOException exception)
{
Log.d ("JUCE", "send midi had IO exception");
}
}
}
@Override
public void close()
{
if (androidPort != null) {
try {
androidPort.close();
} catch (IOException exception) {
Log.d("JUCE", "IO Exception while closing port");
}
}
if (owner != null)
owner.removePort (portPath);
owner = null;
androidPort = null;
}
MidiDeviceManager owner;
MidiInputPort androidPort;
MidiPortPath portPath;
}
private static class MidiPortPath extends Object
{
public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex)
{
deviceId = deviceIdToUse;
isInput = direction;
portIndex = androidIndex;
}
public int deviceId;
public int portIndex;
public boolean isInput;
@Override
public int hashCode()
{
Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127));
return i.hashCode() * (isInput ? -1 : 1);
}
@Override
public boolean equals (Object obj)
{
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MidiPortPath other = (MidiPortPath) obj;
return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId);
}
}
//==============================================================================
public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener
{
//==============================================================================
private class DummyBluetoothGattCallback extends BluetoothGattCallback
{
public DummyBluetoothGattCallback (MidiDeviceManager mm)
{
super();
owner = mm;
}
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
{
if (newState == BluetoothProfile.STATE_CONNECTED)
{
gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
owner.pairBluetoothDeviceStepTwo (gatt.getDevice());
}
}
public void onServicesDiscovered(BluetoothGatt gatt, int status) {}
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {}
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {}
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {}
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {}
public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {}
private MidiDeviceManager owner;
}
//==============================================================================
private class MidiDeviceOpenTask extends java.util.TimerTask
{
public MidiDeviceOpenTask (MidiDeviceManager deviceManager, MidiDevice device, BluetoothGatt gattToUse)
{
owner = deviceManager;
midiDevice = device;
btGatt = gattToUse;
}
@Override
public boolean cancel()
{
synchronized (MidiDeviceOpenTask.class)
{
owner = null;
boolean retval = super.cancel();
if (btGatt != null)
{
btGatt.disconnect();
btGatt.close();
btGatt = null;
}
if (midiDevice != null)
{
try
{
midiDevice.close();
}
catch (IOException e)
{}
midiDevice = null;
}
return retval;
}
}
public String getBluetoothAddress()
{
synchronized (MidiDeviceOpenTask.class)
{
if (midiDevice != null)
{
MidiDeviceInfo info = midiDevice.getInfo();
if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
{
BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
if (btDevice != null)
return btDevice.getAddress();
}
}
}
return "";
}
public BluetoothGatt getGatt() { return btGatt; }
public int getID()
{
return midiDevice.getInfo().getId();
}
@Override
public void run()
{
synchronized (MidiDeviceOpenTask.class)
{
if (owner != null && midiDevice != null)
owner.onDeviceOpenedDelayed (midiDevice);
}
}
private MidiDeviceManager owner;
private MidiDevice midiDevice;
private BluetoothGatt btGatt;
}
//==============================================================================
public MidiDeviceManager()
{
manager = (MidiManager) getSystemService (MIDI_SERVICE);
if (manager == null)
{
Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service");
return;
}
openPorts = new HashMap<MidiPortPath, WeakReference<JuceMidiPort>> ();
midiDevices = new ArrayList<Pair<MidiDevice,BluetoothGatt>>();
openTasks = new HashMap<Integer, MidiDeviceOpenTask>();
btDevicesPairing = new HashMap<String, BluetoothGatt>();
MidiDeviceInfo[] foundDevices = manager.getDevices();
for (MidiDeviceInfo info : foundDevices)
onDeviceAdded (info);
manager.registerDeviceCallback (this, null);
}
protected void finalize() throws Throwable
{
manager.unregisterDeviceCallback (this);
synchronized (MidiDeviceManager.class)
{
btDevicesPairing.clear();
for (Integer deviceID : openTasks.keySet())
openTasks.get (deviceID).cancel();
openTasks = null;
}
for (MidiPortPath key : openPorts.keySet())
openPorts.get (key).get().close();
openPorts = null;
for (Pair<MidiDevice, BluetoothGatt> device : midiDevices)
{
if (device.second != null)
{
device.second.disconnect();
device.second.close();
}
device.first.close();
}
midiDevices.clear();
super.finalize();
}
public String[] getJuceAndroidMidiInputDevices()
{
return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
}
public String[] getJuceAndroidMidiOutputDevices()
{
return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT);
}
private String[] getJuceAndroidMidiDevices (int portType)
{
// only update the list when JUCE asks for a new list
synchronized (MidiDeviceManager.class)
{
deviceInfos = getDeviceInfos();
}
ArrayList<String> portNames = new ArrayList<String>();
int index = 0;
for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index))
portNames.add (getPortName (portInfo));
String[] names = new String[portNames.size()];
return portNames.toArray (names);
}
private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput)
{
synchronized (MidiDeviceManager.class)
{
int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT);
MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index);
if (portInfo != null)
{
// ports must be opened exclusively!
if (openPorts.containsKey (portInfo))
return null;
Pair<MidiDevice,BluetoothGatt> devicePair = getMidiDevicePairForId (portInfo.deviceId);
if (devicePair != null)
{
MidiDevice device = devicePair.first;
if (device != null)
{
JuceMidiPort juceMidiPort = null;
if (isInput)
{
MidiOutputPort outputPort = device.openOutputPort(portInfo.portIndex);
if (outputPort != null)
juceMidiPort = new JuceMidiInputPort(this, outputPort, portInfo, host);
}
else
{
MidiInputPort inputPort = device.openInputPort(portInfo.portIndex);
if (inputPort != null)
juceMidiPort = new JuceMidiOutputPort(this, inputPort, portInfo);
}
if (juceMidiPort != null)
{
openPorts.put(portInfo, new WeakReference<JuceMidiPort>(juceMidiPort));
return juceMidiPort;
}
}
}
}
}
return null;
}
public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)
{
return openMidiPortWithJuceIndex (index, host, true);
}
public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)
{
return openMidiPortWithJuceIndex (index, 0, false);
}
/* 0: unpaired, 1: paired, 2: pairing */
public int getBluetoothDeviceStatus (String address)
{
synchronized (MidiDeviceManager.class)
{
if (! address.isEmpty())
{
if (findMidiDeviceForBluetoothAddress (address) != null)
return 1;
if (btDevicesPairing.containsKey (address))
return 2;
if (findOpenTaskForBluetoothAddress (address) != null)
return 2;
}
}
return 0;
}
public boolean pairBluetoothDevice (BluetoothDevice btDevice)
{
String btAddress = btDevice.getAddress();
if (btAddress.isEmpty())
return false;
synchronized (MidiDeviceManager.class)
{
if (getBluetoothDeviceStatus (btAddress) != 0)
return false;
btDevicesPairing.put (btDevice.getAddress(), null);
BluetoothGatt gatt = btDevice.connectGatt (getApplicationContext(), true, new DummyBluetoothGattCallback (this));
if (gatt != null)
{
btDevicesPairing.put (btDevice.getAddress(), gatt);
}
else
{
pairBluetoothDeviceStepTwo (btDevice);
}
}
return true;
}
public void pairBluetoothDeviceStepTwo (BluetoothDevice btDevice)
{
manager.openBluetoothDevice(btDevice, this, null);
}
public void unpairBluetoothDevice (String address)
{
if (address.isEmpty())
return;
synchronized (MidiDeviceManager.class)
{
if (btDevicesPairing.containsKey (address))
{
BluetoothGatt gatt = btDevicesPairing.get (address);
if (gatt != null)
{
gatt.disconnect();
gatt.close();
}
btDevicesPairing.remove (address);
}
MidiDeviceOpenTask openTask = findOpenTaskForBluetoothAddress (address);
if (openTask != null)
{
int deviceID = openTask.getID();
openTask.cancel();
openTasks.remove (deviceID);
}
Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (address);
if (midiDevicePair != null)
{
MidiDevice midiDevice = midiDevicePair.first;
onDeviceRemoved (midiDevice.getInfo());
try {
midiDevice.close();
}
catch (IOException exception)
{
Log.d ("JUCE", "IOException while closing midi device");
}
}
}
}
private Pair<MidiDevice, BluetoothGatt> findMidiDeviceForBluetoothAddress (String address)
{
for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
{
MidiDeviceInfo info = midiDevice.first.getInfo();
if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
{
BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
if (btDevice != null && btDevice.getAddress().equals (address))
return midiDevice;
}
}
return null;
}
private MidiDeviceOpenTask findOpenTaskForBluetoothAddress (String address)
{
for (Integer deviceID : openTasks.keySet())
{
MidiDeviceOpenTask openTask = openTasks.get (deviceID);
if (openTask.getBluetoothAddress().equals (address))
return openTask;
}
return null;
}
public void removePort (MidiPortPath path)
{
openPorts.remove (path);
}
public String getInputPortNameForJuceIndex (int index)
{
MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index);
if (portInfo != null)
return getPortName (portInfo);
return "";
}
public String getOutputPortNameForJuceIndex (int index)
{
MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index);
if (portInfo != null)
return getPortName (portInfo);
return "";
}
public void onDeviceAdded (MidiDeviceInfo info)
{
// only add standard midi devices
if (info.getType() == info.TYPE_BLUETOOTH)
return;
manager.openDevice (info, this, null);
}
public void onDeviceRemoved (MidiDeviceInfo info)
{
synchronized (MidiDeviceManager.class)
{
Pair<MidiDevice, BluetoothGatt> devicePair = getMidiDevicePairForId (info.getId());
if (devicePair != null)
{
MidiDevice midiDevice = devicePair.first;
BluetoothGatt gatt = devicePair.second;
// close all ports that use this device
boolean removedPort = true;
while (removedPort == true)
{
removedPort = false;
for (MidiPortPath key : openPorts.keySet())
{
if (key.deviceId == info.getId())
{
openPorts.get(key).get().close();
removedPort = true;
break;
}
}
}
if (gatt != null)
{
gatt.disconnect();
gatt.close();
}
midiDevices.remove (devicePair);
}
}
}
public void onDeviceStatusChanged (MidiDeviceStatus status)
{
}
@Override
public void onDeviceOpened (MidiDevice theDevice)
{
synchronized (MidiDeviceManager.class)
{
MidiDeviceInfo info = theDevice.getInfo();
int deviceID = info.getId();
BluetoothGatt gatt = null;
boolean isBluetooth = false;
if (! openTasks.containsKey (deviceID))
{
if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
{
isBluetooth = true;
BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
if (btDevice != null)
{
String btAddress = btDevice.getAddress();
if (btDevicesPairing.containsKey (btAddress))
{
gatt = btDevicesPairing.get (btAddress);
btDevicesPairing.remove (btAddress);
}
else
{
// unpair was called in the mean time
try
{
Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());
if (midiDevicePair != null)
{
gatt = midiDevicePair.second;
if (gatt != null)
{
gatt.disconnect();
gatt.close();
}
}
theDevice.close();
}
catch (IOException e)
{}
return;
}
}
}
MidiDeviceOpenTask openTask = new MidiDeviceOpenTask (this, theDevice, gatt);
openTasks.put (deviceID, openTask);
new java.util.Timer().schedule (openTask, (isBluetooth ? 2000 : 100));
}
}
}
public void onDeviceOpenedDelayed (MidiDevice theDevice)
{
synchronized (MidiDeviceManager.class)
{
int deviceID = theDevice.getInfo().getId();
if (openTasks.containsKey (deviceID))
{
if (! midiDevices.contains(theDevice))
{
BluetoothGatt gatt = openTasks.get (deviceID).getGatt();
openTasks.remove (deviceID);
midiDevices.add (new Pair<MidiDevice,BluetoothGatt> (theDevice, gatt));
}
}
else
{
// unpair was called in the mean time
MidiDeviceInfo info = theDevice.getInfo();
BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
if (btDevice != null)
{
String btAddress = btDevice.getAddress();
Pair<MidiDevice, BluetoothGatt> midiDevicePair = findMidiDeviceForBluetoothAddress (btDevice.getAddress());
if (midiDevicePair != null)
{
BluetoothGatt gatt = midiDevicePair.second;
if (gatt != null)
{
gatt.disconnect();
gatt.close();
}
}
}
try
{
theDevice.close();
}
catch (IOException e)
{}
}
}
}
public String getPortName(MidiPortPath path)
{
int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
synchronized (MidiDeviceManager.class)
{
for (MidiDeviceInfo info : deviceInfos)
{
int localIndex = 0;
if (info.getId() == path.deviceId)
{
for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
{
int portType = portInfo.getType();
if (portType == portTypeToFind)
{
int portIndex = portInfo.getPortNumber();
if (portIndex == path.portIndex)
{
String portName = portInfo.getName();
if (portName.isEmpty())
portName = (String) info.getProperties().get(info.PROPERTY_NAME);
return portName;
}
}
}
}
}
}
return "";
}
public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex)
{
int portIdx = 0;
for (MidiDeviceInfo info : deviceInfos)
{
for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
{
if (portInfo.getType() == portType)
{
if (portIdx == juceIndex)
return new MidiPortPath (info.getId(),
(portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),
portInfo.getPortNumber());
portIdx++;
}
}
}
return null;
}
private MidiDeviceInfo[] getDeviceInfos()
{
synchronized (MidiDeviceManager.class)
{
MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size()];
int idx = 0;
for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
infos[idx++] = midiDevice.first.getInfo();
return infos;
}
}
private Pair<MidiDevice, BluetoothGatt> getMidiDevicePairForId (int deviceId)
{
synchronized (MidiDeviceManager.class)
{
for (Pair<MidiDevice,BluetoothGatt> midiDevice : midiDevices)
if (midiDevice.first.getInfo().getId() == deviceId)
return midiDevice;
}
return null;
}
private MidiManager manager;
private HashMap<String, BluetoothGatt> btDevicesPairing;
private HashMap<Integer, MidiDeviceOpenTask> openTasks;
private ArrayList<Pair<MidiDevice, BluetoothGatt>> midiDevices;
private MidiDeviceInfo[] deviceInfos;
private HashMap<MidiPortPath, WeakReference<JuceMidiPort>> openPorts;
}
public MidiDeviceManager getAndroidMidiDeviceManager()
{
if (getSystemService (MIDI_SERVICE) == null)
return null;
synchronized (JuceAppActivity.class)
{
if (midiDeviceManager == null)
midiDeviceManager = new MidiDeviceManager();
}
return midiDeviceManager;
}
public BluetoothManager getAndroidBluetoothManager()
{
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null)
return null;
if (adapter.getBluetoothLeScanner() == null)
return null;
synchronized (JuceAppActivity.class)
{
if (bluetoothManager == null)
bluetoothManager = new BluetoothManager();
}
return bluetoothManager;
}