From dcdcfaa7e9744ba721f1726868eb5b6488c516ce Mon Sep 17 00:00:00 2001 From: hogliux Date: Thu, 30 Mar 2017 17:03:26 +0100 Subject: [PATCH] Added several improvements to Android BLE MIDI reliability --- .../com/yourcompany/jucedemo/JuceDemo.java | 34 +- .../com/yourcompany/miditest/MidiTest.java | 910 +++++++----------- .../JUCENetworkGraphicsDemo.java | 34 +- .../yourcompany/oscreceiver/OSCReceiver.java | 34 +- .../com/yourcompany/oscsender/OSCSender.java | 34 +- .../juce/jucedemoplugin/JuceDemoPlugin.java | 910 +++++++----------- .../AudioPerformanceTest.java | 910 +++++++----------- .../juce_core/native/java/AndroidMidi.java | 876 +++++++---------- .../native/java/JuceAppActivity.java | 34 +- 9 files changed, 1356 insertions(+), 2420 deletions(-) diff --git a/examples/Demo/Builds/Android/app/src/main/java/com/yourcompany/jucedemo/JuceDemo.java b/examples/Demo/Builds/Android/app/src/main/java/com/yourcompany/jucedemo/JuceDemo.java index 8d6779e0a5..1e122134ba 100644 --- a/examples/Demo/Builds/Android/app/src/main/java/com/yourcompany/jucedemo/JuceDemo.java +++ b/examples/Demo/Builds/Android/app/src/main/java/com/yourcompany/jucedemo/JuceDemo.java @@ -55,6 +55,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; +import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import java.io.*; @@ -145,38 +146,6 @@ public class JuceDemo extends Activity //============================================================================== - public static class MidiPortID extends Object - { - public MidiPortID (int index, boolean direction) - { - androidIndex = index; - isInput = direction; - } - - public int androidIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer (androidIndex); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortID other = (MidiPortID) obj; - return (androidIndex == other.androidIndex && isInput == other.isInput); - } - } - public interface JuceMidiPort { boolean isInputPort(); @@ -186,7 +155,6 @@ public class JuceDemo extends Activity void stop(); void close(); - MidiPortID getPortId(); // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count); diff --git a/examples/MidiTest/Builds/Android/app/src/main/java/com/yourcompany/miditest/MidiTest.java b/examples/MidiTest/Builds/Android/app/src/main/java/com/yourcompany/miditest/MidiTest.java index 4306273468..44a2557371 100644 --- a/examples/MidiTest/Builds/Android/app/src/main/java/com/yourcompany/miditest/MidiTest.java +++ b/examples/MidiTest/Builds/Android/app/src/main/java/com/yourcompany/miditest/MidiTest.java @@ -55,6 +55,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; +import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import java.io.*; @@ -160,38 +161,6 @@ public class MidiTest extends Activity } //============================================================================== - public static class MidiPortID extends Object - { - public MidiPortID (int index, boolean direction) - { - androidIndex = index; - isInput = direction; - } - - public int androidIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer (androidIndex); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortID other = (MidiPortID) obj; - return (androidIndex == other.androidIndex && isInput == other.isInput); - } - } - public interface JuceMidiPort { boolean isInputPort(); @@ -201,7 +170,6 @@ public class MidiTest extends Activity void stop(); void close(); - MidiPortID getPortId(); // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count); @@ -279,17 +247,7 @@ public class MidiTest extends Activity return false; } - MidiManager mm = (MidiManager) getSystemService (MIDI_SERVICE); - - PhysicalMidiDevice midiDevice = PhysicalMidiDevice.fromBluetoothLeDevice (btDevice, mm); - - if (midiDevice != null) - { - getAndroidMidiDeviceManager().addDeviceToList (midiDevice); - return true; - } - - return false; + return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice); } public void unpairBluetoothMidiDevice (String address) @@ -341,11 +299,20 @@ public class MidiTest extends Activity { private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp); - public JuceMidiInputPort (PhysicalMidiDevice device, long host, MidiOutputPort midiPort) + public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse) { - parent = device; - juceHost = host; - port = midiPort; + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + juceHost = hostToUse; + isConnected = false; + } + + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); } @Override @@ -357,36 +324,40 @@ public class MidiTest extends Activity @Override public void start() { - port.connect (this); + if (owner != null && androidPort != null && ! isConnected) { + androidPort.connect(this); + isConnected = true; + } } @Override public void stop() { - port.disconnect (this); + if (owner != null && androidPort != null && isConnected) { + androidPort.disconnect(this); + isConnected = false; + } } @Override public void close() { - stop(); - - try - { - port.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiInputPort::close: IOException = " + e.toString()); + if (androidPort != null) { + try { + androidPort.close(); + } catch (IOException exception) { + Log.d("JUCE", "IO Exception while closing port"); + } } - if (parent != null) - { - parent.removePort (port.getPortNumber(), true); - parent = null; - } + 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) @@ -394,27 +365,35 @@ public class MidiTest extends Activity } @Override - public MidiPortID getPortId() - { - return new MidiPortID (port.getPortNumber(), true); - } + public void onFlush() + {} @Override public void sendMidi (byte[] msg, int offset, int count) { } - private PhysicalMidiDevice parent = null; - private long juceHost = 0; - private MidiOutputPort port; + MidiDeviceManager owner; + MidiOutputPort androidPort; + MidiPortPath portPath; + long juceHost; + boolean isConnected; } public static class JuceMidiOutputPort implements JuceMidiPort { - public JuceMidiOutputPort (PhysicalMidiDevice device, MidiInputPort midiPort) + public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse) { - parent = device; - port = midiPort; + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + } + + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); } @Override @@ -436,425 +415,81 @@ public class MidiTest extends Activity @Override public void sendMidi (byte[] msg, int offset, int count) { - try + if (androidPort != null) { - port.send(msg, offset, count); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiOutputPort::sendMidi: IOException = " + e.toString()); + try { + androidPort.send(msg, offset, count); + } catch (IOException exception) + { + Log.d ("JUCE", "send midi had IO exception"); + } } } @Override public void close() { - try - { - port.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiOutputPort::close: IOException = " + e.toString()); + if (androidPort != null) { + try { + androidPort.close(); + } catch (IOException exception) { + Log.d("JUCE", "IO Exception while closing port"); + } } - if (parent != null) - { - parent.removePort (port.getPortNumber(), false); - parent = null; - } + if (owner != null) + owner.removePort (portPath); + + owner = null; + androidPort = null; } - - @Override - public MidiPortID getPortId() - { - return new MidiPortID (port.getPortNumber(), false); - } - - private PhysicalMidiDevice parent = null; - private MidiInputPort port; + MidiDeviceManager owner; + MidiInputPort androidPort; + MidiPortPath portPath; } - public static class PhysicalMidiDevice + private static class MidiPortPath extends Object { - private static class MidiDeviceThread extends Thread + public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex) { - public Handler handler = null; - public Object sync = null; + deviceId = deviceIdToUse; + isInput = direction; + portIndex = androidIndex; - public MidiDeviceThread (Object syncrhonization) - { - sync = syncrhonization; - } - - public void run() - { - Looper.prepare(); - - synchronized (sync) - { - handler = new Handler(); - sync.notifyAll(); - } - - Looper.loop(); - } } - private static class MidiDeviceOpenCallback implements MidiManager.OnDeviceOpenedListener + public int deviceId; + public int portIndex; + public boolean isInput; + + @Override + public int hashCode() { - public Object sync = null; - public boolean isWaiting = true; - public android.media.midi.MidiDevice theDevice = null; - - public MidiDeviceOpenCallback (Object waiter) - { - sync = waiter; - } - - public void onDeviceOpened (MidiDevice device) - { - synchronized (sync) - { - theDevice = device; - isWaiting = false; - sync.notifyAll(); - } - } + Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127)); + return i.hashCode() * (isInput ? -1 : 1); } - public static PhysicalMidiDevice fromBluetoothLeDevice (BluetoothDevice bluetoothDevice, MidiManager mm) + @Override + public boolean equals (Object obj) { - Object waitForCreation = new Object(); - MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); - thread.start(); + if (obj == null) + return false; - synchronized (waitForCreation) - { - while (thread.handler == null) - { - try - { - waitForCreation.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "Wait was interrupted but we don't care"); - } - } - } + if (getClass() != obj.getClass()) + return false; - Object waitForDevice = new Object(); - - MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); - - synchronized (waitForDevice) - { - mm.openBluetoothDevice (bluetoothDevice, openCallback, thread.handler); - - while (openCallback.isWaiting) - { - try - { - waitForDevice.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "Wait was interrupted but we don't care"); - } - } - } - - if (openCallback.theDevice == null) - { - Log.d ("JUCE", "openBluetoothDevice failed"); - return null; - } - - PhysicalMidiDevice device = new PhysicalMidiDevice(); - - device.handle = openCallback.theDevice; - device.info = device.handle.getInfo(); - device.bluetoothAddress = bluetoothDevice.getAddress(); - device.midiManager = mm; - - return device; + MidiPortPath other = (MidiPortPath) obj; + return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId); } - - public void unpair() - { - if (! bluetoothAddress.equals ("") && handle != null) - { - JuceMidiPort ports[] = new JuceMidiPort[0]; - ports = juceOpenedPorts.values().toArray(ports); - - for (int i = 0; i < ports.length; ++i) - ports[i].close(); - - juceOpenedPorts.clear(); - - try - { - handle.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "handle.close(): IOException = " + e.toString()); - } - - handle = null; - } - } - - public static PhysicalMidiDevice fromMidiDeviceInfo (MidiDeviceInfo info, MidiManager mm) - { - PhysicalMidiDevice device = new PhysicalMidiDevice(); - device.info = info; - device.midiManager = mm; - return device; - } - - public PhysicalMidiDevice() - { - bluetoothAddress = ""; - juceOpenedPorts = new Hashtable(); - handle = null; - } - - public MidiDeviceInfo.PortInfo[] getPorts() - { - return info.getPorts(); - } - - public String getHumanReadableNameForPort (MidiDeviceInfo.PortInfo port, int portIndexToUseInName, boolean addPortNumberToName) - { - if (addPortNumberToName) - { - String portName = port.getName(); - - if (portName.equals("")) - portName = ((port.getType() == MidiDeviceInfo.PortInfo.TYPE_OUTPUT) ? "Out " : "In ") - + Integer.toString(portIndexToUseInName); - - return getHumanReadableDeviceName() + " " + portName; - } - - return getHumanReadableDeviceName(); - } - - public String getHumanReadableNameForPort (int portType, int androidPortID, int portIndexToUseInName) - { - MidiDeviceInfo.PortInfo[] ports = info.getPorts(); - - int numTotalPorts = 0; - - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) - { - numTotalPorts++; - } - } - - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) - { - if (port.getPortNumber() == androidPortID) - return getHumanReadableNameForPort (port, portIndexToUseInName, (numTotalPorts > 1)); - } - } - - return "Unknown"; - } - - public String getHumanReadableDeviceName() - { - Bundle bundle = info.getProperties(); - return bundle.getString (MidiDeviceInfo.PROPERTY_NAME , "Unknown device"); - } - - public void checkIfDeviceCanBeClosed() - { - if (juceOpenedPorts.size() == 0) - { - // never close bluetooth LE devices, otherwise they unpair and we have - // no idea how many ports they have. - // Only remove bluetooth devices when we specifically unpair - if (bluetoothAddress.equals ("")) - { - try - { - handle.close(); - handle = null; - } - catch (IOException e) - { - Log.d ("JUCE", "PhysicalMidiDevice::checkIfDeviceCanBeClosed: IOException = " + e.toString()); - } - } - } - } - - public void removePort (int portIdx, boolean isInput) - { - MidiPortID portID = new MidiPortID (portIdx, isInput); - JuceMidiPort port = juceOpenedPorts.get (portID); - - if (port != null) - { - juceOpenedPorts.remove (portID); - checkIfDeviceCanBeClosed(); - return; - } - - // tried to remove a port that was never added - assert false; - } - - public JuceMidiPort openPort (int portIdx, boolean isInput, long host) - { - open(); - - if (handle == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: handle = null, device not open"); - return null; - } - - // make sure that the port is not already open - if (findPortForIdx (portIdx, isInput) != null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openInputPort: port already open, not opening again!"); - return null; - } - - JuceMidiPort retval = null; - - if (isInput) - { - MidiOutputPort androidPort = handle.openOutputPort (portIdx); - - if (androidPort == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openOutputPort (portIdx = " - + Integer.toString (portIdx) + ") failed!"); - return null; - } - - retval = new JuceMidiInputPort (this, host, androidPort); - } - else - { - MidiInputPort androidPort = handle.openInputPort (portIdx); - - if (androidPort == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openInputPort (portIdx = " - + Integer.toString (portIdx) + ") failed!"); - return null; - } - - retval = new JuceMidiOutputPort (this, androidPort); - } - - juceOpenedPorts.put (new MidiPortID (portIdx, isInput), retval); - return retval; - } - - private JuceMidiPort findPortForIdx (int idx, boolean isInput) - { - return juceOpenedPorts.get (new MidiPortID (idx, isInput)); - } - - // opens the device - private synchronized void open() - { - if (handle != null) - return; - - Object waitForCreation = new Object(); - MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); - thread.start(); - - synchronized(waitForCreation) - { - while (thread.handler == null) - { - try - { - waitForCreation.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "wait was interrupted but we don't care"); - } - } - } - - Object waitForDevice = new Object(); - - MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); - - synchronized (waitForDevice) - { - midiManager.openDevice (info, openCallback, thread.handler); - - while (openCallback.isWaiting) - { - try - { - waitForDevice.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "wait was interrupted but we don't care"); - } - } - } - - handle = openCallback.theDevice; - } - - private MidiDeviceInfo info; - private Hashtable juceOpenedPorts; - public MidiDevice handle; - public String bluetoothAddress; - private MidiManager midiManager; } //============================================================================== - public class MidiDeviceManager extends MidiManager.DeviceCallback + public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener { - public class MidiPortPath - { - public PhysicalMidiDevice midiDevice; - public int androidMidiPortID; - public int portIndexToUseInName; - } - - public class JuceDeviceList - { - public ArrayList inPorts = new ArrayList(); - public ArrayList outPorts = new ArrayList(); - } - - // We need to keep a thread local copy of the devices - // which we returned the last time - // getJuceAndroidMidiIn/OutputDevices() was called - private final ThreadLocal lastDevicesReturned = - new ThreadLocal() - { - @Override protected JuceDeviceList initialValue() - { - return new JuceDeviceList(); - } - }; public MidiDeviceManager() { - physicalMidiDevices = new ArrayList(); manager = (MidiManager) getSystemService (MIDI_SERVICE); if (manager == null) @@ -863,188 +498,319 @@ public class MidiTest extends Activity return; } - manager.registerDeviceCallback (this, null); + openPorts = new HashMap> (); + midiDevices = new ArrayList(); MidiDeviceInfo[] foundDevices = manager.getDevices(); - for (MidiDeviceInfo info : foundDevices) - physicalMidiDevices.add (PhysicalMidiDevice.fromMidiDeviceInfo (info, manager)); + onDeviceAdded (info); + + manager.registerDeviceCallback (this, null); } - // specifically add a device to the list - public void addDeviceToList (PhysicalMidiDevice device) + protected void finalize() throws Throwable { - physicalMidiDevices.add (device); - } + manager.unregisterDeviceCallback (this); - public void unpairBluetoothDevice (String address) - { - for (int i = 0; i < physicalMidiDevices.size(); ++i) - { - PhysicalMidiDevice device = physicalMidiDevices.get(i); + for (MidiPortPath key : openPorts.keySet()) + openPorts.get (key).get().close(); - if (device.bluetoothAddress.equals (address)) - { - physicalMidiDevices.remove (i); - device.unpair(); - return; - } - } - } + openPorts = null; - public boolean isBluetoothDevicePaired (String address) - { - for (int i = 0; i < physicalMidiDevices.size(); ++i) - { - PhysicalMidiDevice device = physicalMidiDevices.get(i); + for (MidiDevice device : midiDevices) + device.close(); - if (device.bluetoothAddress.equals (address)) - return true; - } - - return false; + super.finalize(); } public String[] getJuceAndroidMidiInputDevices() { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); } public String[] getJuceAndroidMidiOutputDevices() { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); } private String[] getJuceAndroidMidiDevices (int portType) { - ArrayList listOfReturnedDevices = new ArrayList(); - List deviceNames = new ArrayList(); - - for (PhysicalMidiDevice physicalMidiDevice : physicalMidiDevices) + // only update the list when JUCE asks for a new list + synchronized (MidiDeviceManager.class) { - int portIdx = 0; - MidiDeviceInfo.PortInfo[] ports = physicalMidiDevice.getPorts(); + deviceInfos = getDeviceInfos(); + } - int numberOfPorts = 0; - for (MidiDeviceInfo.PortInfo port : ports) + ArrayList portNames = new ArrayList(); + + 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) { - if (port.getType() == portType) - { - numberOfPorts++; - } - } + // ports must be opened exclusively! + if (openPorts.containsKey (portInfo)) + return null; - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) + MidiDevice device = getMidiDeviceForId (portInfo.deviceId); + if (device != null) { - MidiPortPath path = new MidiPortPath(); - path.midiDevice = physicalMidiDevice; - path.androidMidiPortID = port.getPortNumber(); - path.portIndexToUseInName = ++portIdx; - listOfReturnedDevices.add (path); + JuceMidiPort juceMidiPort = null; - deviceNames.add (physicalMidiDevice.getHumanReadableNameForPort (port, - path.portIndexToUseInName, - (numberOfPorts > 1))); + 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)); + + return juceMidiPort; + } } } } - String[] deviceNamesArray = new String[deviceNames.size()]; - - if (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT) - { - lastDevicesReturned.get().inPorts.clear(); - lastDevicesReturned.get().inPorts.addAll (listOfReturnedDevices); - } - else - { - lastDevicesReturned.get().outPorts.clear(); - lastDevicesReturned.get().outPorts.addAll (listOfReturnedDevices); - } - - return deviceNames.toArray(deviceNamesArray); + return null; } public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) { - ArrayList lastDevices = lastDevicesReturned.get().inPorts; - - if (index >= lastDevices.size() || index < 0) - return null; - - MidiPortPath path = lastDevices.get (index); - return path.midiDevice.openPort (path.androidMidiPortID, true, host); + return openMidiPortWithJuceIndex (index, host, true); } public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().outPorts; + return openMidiPortWithJuceIndex (index, 0, false); + } - if (index >= lastDevices.size() || index < 0) - return null; + public boolean isBluetoothDevicePaired (String address) + { + return (findMidiDeviceForBluetoothAddress (address) != null); + } - MidiPortPath path = lastDevices.get (index); - return path.midiDevice.openPort (path.androidMidiPortID, false, 0); + public boolean pairBluetoothDevice (BluetoothDevice btDevice) + { + manager.openBluetoothDevice(btDevice, this, null); + return true; + } + + public void unpairBluetoothDevice (String address) + { + synchronized (MidiDeviceManager.class) + { + MidiDevice midiDevice = findMidiDeviceForBluetoothAddress (address); + if (midiDevice != null) + { + onDeviceRemoved (midiDevice.getInfo()); + + try { + midiDevice.close(); + } + catch (IOException exception) + { + Log.d ("JUCE", "IOException while closing midi device"); + } + } + } + } + + private MidiDevice findMidiDeviceForBluetoothAddress (String address) + { + for (MidiDevice midiDevice : midiDevices) + { + MidiDeviceInfo info = midiDevice.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; + } + + public void removePort (MidiPortPath path) + { + openPorts.remove (path); } public String getInputPortNameForJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().inPorts; + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); + if (portInfo != null) + return getPortName (portInfo); - if (index >= lastDevices.size() || index < 0) - return ""; - - MidiPortPath path = lastDevices.get (index); - - return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_INPUT, - path.androidMidiPortID, - path.portIndexToUseInName); + return ""; } public String getOutputPortNameForJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().outPorts; + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); + if (portInfo != null) + return getPortName (portInfo); - if (index >= lastDevices.size() || index < 0) - return ""; - - MidiPortPath path = lastDevices.get (index); - - return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, - path.androidMidiPortID, - path.portIndexToUseInName); + return ""; } public void onDeviceAdded (MidiDeviceInfo info) { - PhysicalMidiDevice device = PhysicalMidiDevice.fromMidiDeviceInfo (info, manager); + // only add standard midi devices + if (info.getType() == info.TYPE_BLUETOOTH) + return; - // Do not add bluetooth devices as they are already added by the native bluetooth dialog - if (info.getType() != MidiDeviceInfo.TYPE_BLUETOOTH) - physicalMidiDevices.add (device); + manager.openDevice (info, this, null); } public void onDeviceRemoved (MidiDeviceInfo info) { - for (int i = 0; i < physicalMidiDevices.size(); ++i) + synchronized (MidiDeviceManager.class) { - if (physicalMidiDevices.get(i).info.getId() == info.getId()) - { - physicalMidiDevices.remove (i); - return; + MidiDevice device = getMidiDeviceForId (info.getId()); + + // 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 (device != null) + midiDevices.remove (device); } - // Don't assert here as this may be called again after a bluetooth device is unpaired } public void onDeviceStatusChanged (MidiDeviceStatus status) { } - private ArrayList physicalMidiDevices; + @Override + public void onDeviceOpened (MidiDevice theDevice) + { + synchronized (MidiDeviceManager.class) + { + // make sure it's not already there + if (! midiDevices.contains(theDevice)) + midiDevices.add (theDevice); + } + } + + 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 (MidiDevice midiDevice : midiDevices) + infos[idx++] = midiDevice.getInfo(); + + return infos; + } + } + + private MidiDevice getMidiDeviceForId (int deviceId) + { + synchronized (MidiDeviceManager.class) + { + for (MidiDevice midiDevice : midiDevices) + if (midiDevice.getInfo().getId() == deviceId) + return midiDevice; + } + + return null; + } + private MidiManager manager; + private ArrayList midiDevices; + private MidiDeviceInfo[] deviceInfos; + private HashMap> openPorts; } public MidiDeviceManager getAndroidMidiDeviceManager() diff --git a/examples/NetworkGraphicsDemo/Builds/Android/app/src/main/java/com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo.java b/examples/NetworkGraphicsDemo/Builds/Android/app/src/main/java/com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo.java index 37be15d5c5..1263e00076 100644 --- a/examples/NetworkGraphicsDemo/Builds/Android/app/src/main/java/com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo.java +++ b/examples/NetworkGraphicsDemo/Builds/Android/app/src/main/java/com/juce/networkgraphicsdemo/JUCENetworkGraphicsDemo.java @@ -55,6 +55,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; +import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import java.io.*; @@ -145,38 +146,6 @@ public class JUCENetworkGraphicsDemo extends Activity //============================================================================== - public static class MidiPortID extends Object - { - public MidiPortID (int index, boolean direction) - { - androidIndex = index; - isInput = direction; - } - - public int androidIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer (androidIndex); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortID other = (MidiPortID) obj; - return (androidIndex == other.androidIndex && isInput == other.isInput); - } - } - public interface JuceMidiPort { boolean isInputPort(); @@ -186,7 +155,6 @@ public class JUCENetworkGraphicsDemo extends Activity void stop(); void close(); - MidiPortID getPortId(); // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count); diff --git a/examples/OSCReceiver/Builds/Android/app/src/main/java/com/yourcompany/oscreceiver/OSCReceiver.java b/examples/OSCReceiver/Builds/Android/app/src/main/java/com/yourcompany/oscreceiver/OSCReceiver.java index 6dcf032a8e..fdc895101a 100644 --- a/examples/OSCReceiver/Builds/Android/app/src/main/java/com/yourcompany/oscreceiver/OSCReceiver.java +++ b/examples/OSCReceiver/Builds/Android/app/src/main/java/com/yourcompany/oscreceiver/OSCReceiver.java @@ -55,6 +55,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; +import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import java.io.*; @@ -145,38 +146,6 @@ public class OSCReceiver extends Activity //============================================================================== - public static class MidiPortID extends Object - { - public MidiPortID (int index, boolean direction) - { - androidIndex = index; - isInput = direction; - } - - public int androidIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer (androidIndex); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortID other = (MidiPortID) obj; - return (androidIndex == other.androidIndex && isInput == other.isInput); - } - } - public interface JuceMidiPort { boolean isInputPort(); @@ -186,7 +155,6 @@ public class OSCReceiver extends Activity void stop(); void close(); - MidiPortID getPortId(); // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count); diff --git a/examples/OSCSender/Builds/Android/app/src/main/java/com/yourcompany/oscsender/OSCSender.java b/examples/OSCSender/Builds/Android/app/src/main/java/com/yourcompany/oscsender/OSCSender.java index 98d02c3dcd..75281fa797 100644 --- a/examples/OSCSender/Builds/Android/app/src/main/java/com/yourcompany/oscsender/OSCSender.java +++ b/examples/OSCSender/Builds/Android/app/src/main/java/com/yourcompany/oscsender/OSCSender.java @@ -55,6 +55,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; +import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import java.io.*; @@ -145,38 +146,6 @@ public class OSCSender extends Activity //============================================================================== - public static class MidiPortID extends Object - { - public MidiPortID (int index, boolean direction) - { - androidIndex = index; - isInput = direction; - } - - public int androidIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer (androidIndex); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortID other = (MidiPortID) obj; - return (androidIndex == other.androidIndex && isInput == other.isInput); - } - } - public interface JuceMidiPort { boolean isInputPort(); @@ -186,7 +155,6 @@ public class OSCSender extends Activity void stop(); void close(); - MidiPortID getPortId(); // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count); diff --git a/examples/audio plugin demo/Builds/Android/app/src/main/java/com/juce/jucedemoplugin/JuceDemoPlugin.java b/examples/audio plugin demo/Builds/Android/app/src/main/java/com/juce/jucedemoplugin/JuceDemoPlugin.java index e88d91219e..788a8700ea 100644 --- a/examples/audio plugin demo/Builds/Android/app/src/main/java/com/juce/jucedemoplugin/JuceDemoPlugin.java +++ b/examples/audio plugin demo/Builds/Android/app/src/main/java/com/juce/jucedemoplugin/JuceDemoPlugin.java @@ -55,6 +55,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; +import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import java.io.*; @@ -160,38 +161,6 @@ public class JuceDemoPlugin extends Activity } //============================================================================== - public static class MidiPortID extends Object - { - public MidiPortID (int index, boolean direction) - { - androidIndex = index; - isInput = direction; - } - - public int androidIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer (androidIndex); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortID other = (MidiPortID) obj; - return (androidIndex == other.androidIndex && isInput == other.isInput); - } - } - public interface JuceMidiPort { boolean isInputPort(); @@ -201,7 +170,6 @@ public class JuceDemoPlugin extends Activity void stop(); void close(); - MidiPortID getPortId(); // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count); @@ -279,17 +247,7 @@ public class JuceDemoPlugin extends Activity return false; } - MidiManager mm = (MidiManager) getSystemService (MIDI_SERVICE); - - PhysicalMidiDevice midiDevice = PhysicalMidiDevice.fromBluetoothLeDevice (btDevice, mm); - - if (midiDevice != null) - { - getAndroidMidiDeviceManager().addDeviceToList (midiDevice); - return true; - } - - return false; + return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice); } public void unpairBluetoothMidiDevice (String address) @@ -341,11 +299,20 @@ public class JuceDemoPlugin extends Activity { private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp); - public JuceMidiInputPort (PhysicalMidiDevice device, long host, MidiOutputPort midiPort) + public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse) { - parent = device; - juceHost = host; - port = midiPort; + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + juceHost = hostToUse; + isConnected = false; + } + + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); } @Override @@ -357,36 +324,40 @@ public class JuceDemoPlugin extends Activity @Override public void start() { - port.connect (this); + if (owner != null && androidPort != null && ! isConnected) { + androidPort.connect(this); + isConnected = true; + } } @Override public void stop() { - port.disconnect (this); + if (owner != null && androidPort != null && isConnected) { + androidPort.disconnect(this); + isConnected = false; + } } @Override public void close() { - stop(); - - try - { - port.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiInputPort::close: IOException = " + e.toString()); + if (androidPort != null) { + try { + androidPort.close(); + } catch (IOException exception) { + Log.d("JUCE", "IO Exception while closing port"); + } } - if (parent != null) - { - parent.removePort (port.getPortNumber(), true); - parent = null; - } + 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) @@ -394,27 +365,35 @@ public class JuceDemoPlugin extends Activity } @Override - public MidiPortID getPortId() - { - return new MidiPortID (port.getPortNumber(), true); - } + public void onFlush() + {} @Override public void sendMidi (byte[] msg, int offset, int count) { } - private PhysicalMidiDevice parent = null; - private long juceHost = 0; - private MidiOutputPort port; + MidiDeviceManager owner; + MidiOutputPort androidPort; + MidiPortPath portPath; + long juceHost; + boolean isConnected; } public static class JuceMidiOutputPort implements JuceMidiPort { - public JuceMidiOutputPort (PhysicalMidiDevice device, MidiInputPort midiPort) + public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse) { - parent = device; - port = midiPort; + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + } + + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); } @Override @@ -436,425 +415,81 @@ public class JuceDemoPlugin extends Activity @Override public void sendMidi (byte[] msg, int offset, int count) { - try + if (androidPort != null) { - port.send(msg, offset, count); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiOutputPort::sendMidi: IOException = " + e.toString()); + try { + androidPort.send(msg, offset, count); + } catch (IOException exception) + { + Log.d ("JUCE", "send midi had IO exception"); + } } } @Override public void close() { - try - { - port.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiOutputPort::close: IOException = " + e.toString()); + if (androidPort != null) { + try { + androidPort.close(); + } catch (IOException exception) { + Log.d("JUCE", "IO Exception while closing port"); + } } - if (parent != null) - { - parent.removePort (port.getPortNumber(), false); - parent = null; - } + if (owner != null) + owner.removePort (portPath); + + owner = null; + androidPort = null; } - - @Override - public MidiPortID getPortId() - { - return new MidiPortID (port.getPortNumber(), false); - } - - private PhysicalMidiDevice parent = null; - private MidiInputPort port; + MidiDeviceManager owner; + MidiInputPort androidPort; + MidiPortPath portPath; } - public static class PhysicalMidiDevice + private static class MidiPortPath extends Object { - private static class MidiDeviceThread extends Thread + public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex) { - public Handler handler = null; - public Object sync = null; + deviceId = deviceIdToUse; + isInput = direction; + portIndex = androidIndex; - public MidiDeviceThread (Object syncrhonization) - { - sync = syncrhonization; - } - - public void run() - { - Looper.prepare(); - - synchronized (sync) - { - handler = new Handler(); - sync.notifyAll(); - } - - Looper.loop(); - } } - private static class MidiDeviceOpenCallback implements MidiManager.OnDeviceOpenedListener + public int deviceId; + public int portIndex; + public boolean isInput; + + @Override + public int hashCode() { - public Object sync = null; - public boolean isWaiting = true; - public android.media.midi.MidiDevice theDevice = null; - - public MidiDeviceOpenCallback (Object waiter) - { - sync = waiter; - } - - public void onDeviceOpened (MidiDevice device) - { - synchronized (sync) - { - theDevice = device; - isWaiting = false; - sync.notifyAll(); - } - } + Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127)); + return i.hashCode() * (isInput ? -1 : 1); } - public static PhysicalMidiDevice fromBluetoothLeDevice (BluetoothDevice bluetoothDevice, MidiManager mm) + @Override + public boolean equals (Object obj) { - Object waitForCreation = new Object(); - MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); - thread.start(); + if (obj == null) + return false; - synchronized (waitForCreation) - { - while (thread.handler == null) - { - try - { - waitForCreation.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "Wait was interrupted but we don't care"); - } - } - } + if (getClass() != obj.getClass()) + return false; - Object waitForDevice = new Object(); - - MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); - - synchronized (waitForDevice) - { - mm.openBluetoothDevice (bluetoothDevice, openCallback, thread.handler); - - while (openCallback.isWaiting) - { - try - { - waitForDevice.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "Wait was interrupted but we don't care"); - } - } - } - - if (openCallback.theDevice == null) - { - Log.d ("JUCE", "openBluetoothDevice failed"); - return null; - } - - PhysicalMidiDevice device = new PhysicalMidiDevice(); - - device.handle = openCallback.theDevice; - device.info = device.handle.getInfo(); - device.bluetoothAddress = bluetoothDevice.getAddress(); - device.midiManager = mm; - - return device; + MidiPortPath other = (MidiPortPath) obj; + return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId); } - - public void unpair() - { - if (! bluetoothAddress.equals ("") && handle != null) - { - JuceMidiPort ports[] = new JuceMidiPort[0]; - ports = juceOpenedPorts.values().toArray(ports); - - for (int i = 0; i < ports.length; ++i) - ports[i].close(); - - juceOpenedPorts.clear(); - - try - { - handle.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "handle.close(): IOException = " + e.toString()); - } - - handle = null; - } - } - - public static PhysicalMidiDevice fromMidiDeviceInfo (MidiDeviceInfo info, MidiManager mm) - { - PhysicalMidiDevice device = new PhysicalMidiDevice(); - device.info = info; - device.midiManager = mm; - return device; - } - - public PhysicalMidiDevice() - { - bluetoothAddress = ""; - juceOpenedPorts = new Hashtable(); - handle = null; - } - - public MidiDeviceInfo.PortInfo[] getPorts() - { - return info.getPorts(); - } - - public String getHumanReadableNameForPort (MidiDeviceInfo.PortInfo port, int portIndexToUseInName, boolean addPortNumberToName) - { - if (addPortNumberToName) - { - String portName = port.getName(); - - if (portName.equals("")) - portName = ((port.getType() == MidiDeviceInfo.PortInfo.TYPE_OUTPUT) ? "Out " : "In ") - + Integer.toString(portIndexToUseInName); - - return getHumanReadableDeviceName() + " " + portName; - } - - return getHumanReadableDeviceName(); - } - - public String getHumanReadableNameForPort (int portType, int androidPortID, int portIndexToUseInName) - { - MidiDeviceInfo.PortInfo[] ports = info.getPorts(); - - int numTotalPorts = 0; - - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) - { - numTotalPorts++; - } - } - - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) - { - if (port.getPortNumber() == androidPortID) - return getHumanReadableNameForPort (port, portIndexToUseInName, (numTotalPorts > 1)); - } - } - - return "Unknown"; - } - - public String getHumanReadableDeviceName() - { - Bundle bundle = info.getProperties(); - return bundle.getString (MidiDeviceInfo.PROPERTY_NAME , "Unknown device"); - } - - public void checkIfDeviceCanBeClosed() - { - if (juceOpenedPorts.size() == 0) - { - // never close bluetooth LE devices, otherwise they unpair and we have - // no idea how many ports they have. - // Only remove bluetooth devices when we specifically unpair - if (bluetoothAddress.equals ("")) - { - try - { - handle.close(); - handle = null; - } - catch (IOException e) - { - Log.d ("JUCE", "PhysicalMidiDevice::checkIfDeviceCanBeClosed: IOException = " + e.toString()); - } - } - } - } - - public void removePort (int portIdx, boolean isInput) - { - MidiPortID portID = new MidiPortID (portIdx, isInput); - JuceMidiPort port = juceOpenedPorts.get (portID); - - if (port != null) - { - juceOpenedPorts.remove (portID); - checkIfDeviceCanBeClosed(); - return; - } - - // tried to remove a port that was never added - assert false; - } - - public JuceMidiPort openPort (int portIdx, boolean isInput, long host) - { - open(); - - if (handle == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: handle = null, device not open"); - return null; - } - - // make sure that the port is not already open - if (findPortForIdx (portIdx, isInput) != null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openInputPort: port already open, not opening again!"); - return null; - } - - JuceMidiPort retval = null; - - if (isInput) - { - MidiOutputPort androidPort = handle.openOutputPort (portIdx); - - if (androidPort == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openOutputPort (portIdx = " - + Integer.toString (portIdx) + ") failed!"); - return null; - } - - retval = new JuceMidiInputPort (this, host, androidPort); - } - else - { - MidiInputPort androidPort = handle.openInputPort (portIdx); - - if (androidPort == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openInputPort (portIdx = " - + Integer.toString (portIdx) + ") failed!"); - return null; - } - - retval = new JuceMidiOutputPort (this, androidPort); - } - - juceOpenedPorts.put (new MidiPortID (portIdx, isInput), retval); - return retval; - } - - private JuceMidiPort findPortForIdx (int idx, boolean isInput) - { - return juceOpenedPorts.get (new MidiPortID (idx, isInput)); - } - - // opens the device - private synchronized void open() - { - if (handle != null) - return; - - Object waitForCreation = new Object(); - MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); - thread.start(); - - synchronized(waitForCreation) - { - while (thread.handler == null) - { - try - { - waitForCreation.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "wait was interrupted but we don't care"); - } - } - } - - Object waitForDevice = new Object(); - - MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); - - synchronized (waitForDevice) - { - midiManager.openDevice (info, openCallback, thread.handler); - - while (openCallback.isWaiting) - { - try - { - waitForDevice.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "wait was interrupted but we don't care"); - } - } - } - - handle = openCallback.theDevice; - } - - private MidiDeviceInfo info; - private Hashtable juceOpenedPorts; - public MidiDevice handle; - public String bluetoothAddress; - private MidiManager midiManager; } //============================================================================== - public class MidiDeviceManager extends MidiManager.DeviceCallback + public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener { - public class MidiPortPath - { - public PhysicalMidiDevice midiDevice; - public int androidMidiPortID; - public int portIndexToUseInName; - } - - public class JuceDeviceList - { - public ArrayList inPorts = new ArrayList(); - public ArrayList outPorts = new ArrayList(); - } - - // We need to keep a thread local copy of the devices - // which we returned the last time - // getJuceAndroidMidiIn/OutputDevices() was called - private final ThreadLocal lastDevicesReturned = - new ThreadLocal() - { - @Override protected JuceDeviceList initialValue() - { - return new JuceDeviceList(); - } - }; public MidiDeviceManager() { - physicalMidiDevices = new ArrayList(); manager = (MidiManager) getSystemService (MIDI_SERVICE); if (manager == null) @@ -863,188 +498,319 @@ public class JuceDemoPlugin extends Activity return; } - manager.registerDeviceCallback (this, null); + openPorts = new HashMap> (); + midiDevices = new ArrayList(); MidiDeviceInfo[] foundDevices = manager.getDevices(); - for (MidiDeviceInfo info : foundDevices) - physicalMidiDevices.add (PhysicalMidiDevice.fromMidiDeviceInfo (info, manager)); + onDeviceAdded (info); + + manager.registerDeviceCallback (this, null); } - // specifically add a device to the list - public void addDeviceToList (PhysicalMidiDevice device) + protected void finalize() throws Throwable { - physicalMidiDevices.add (device); - } + manager.unregisterDeviceCallback (this); - public void unpairBluetoothDevice (String address) - { - for (int i = 0; i < physicalMidiDevices.size(); ++i) - { - PhysicalMidiDevice device = physicalMidiDevices.get(i); + for (MidiPortPath key : openPorts.keySet()) + openPorts.get (key).get().close(); - if (device.bluetoothAddress.equals (address)) - { - physicalMidiDevices.remove (i); - device.unpair(); - return; - } - } - } + openPorts = null; - public boolean isBluetoothDevicePaired (String address) - { - for (int i = 0; i < physicalMidiDevices.size(); ++i) - { - PhysicalMidiDevice device = physicalMidiDevices.get(i); + for (MidiDevice device : midiDevices) + device.close(); - if (device.bluetoothAddress.equals (address)) - return true; - } - - return false; + super.finalize(); } public String[] getJuceAndroidMidiInputDevices() { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); } public String[] getJuceAndroidMidiOutputDevices() { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); } private String[] getJuceAndroidMidiDevices (int portType) { - ArrayList listOfReturnedDevices = new ArrayList(); - List deviceNames = new ArrayList(); - - for (PhysicalMidiDevice physicalMidiDevice : physicalMidiDevices) + // only update the list when JUCE asks for a new list + synchronized (MidiDeviceManager.class) { - int portIdx = 0; - MidiDeviceInfo.PortInfo[] ports = physicalMidiDevice.getPorts(); + deviceInfos = getDeviceInfos(); + } - int numberOfPorts = 0; - for (MidiDeviceInfo.PortInfo port : ports) + ArrayList portNames = new ArrayList(); + + 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) { - if (port.getType() == portType) - { - numberOfPorts++; - } - } + // ports must be opened exclusively! + if (openPorts.containsKey (portInfo)) + return null; - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) + MidiDevice device = getMidiDeviceForId (portInfo.deviceId); + if (device != null) { - MidiPortPath path = new MidiPortPath(); - path.midiDevice = physicalMidiDevice; - path.androidMidiPortID = port.getPortNumber(); - path.portIndexToUseInName = ++portIdx; - listOfReturnedDevices.add (path); + JuceMidiPort juceMidiPort = null; - deviceNames.add (physicalMidiDevice.getHumanReadableNameForPort (port, - path.portIndexToUseInName, - (numberOfPorts > 1))); + 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)); + + return juceMidiPort; + } } } } - String[] deviceNamesArray = new String[deviceNames.size()]; - - if (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT) - { - lastDevicesReturned.get().inPorts.clear(); - lastDevicesReturned.get().inPorts.addAll (listOfReturnedDevices); - } - else - { - lastDevicesReturned.get().outPorts.clear(); - lastDevicesReturned.get().outPorts.addAll (listOfReturnedDevices); - } - - return deviceNames.toArray(deviceNamesArray); + return null; } public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) { - ArrayList lastDevices = lastDevicesReturned.get().inPorts; - - if (index >= lastDevices.size() || index < 0) - return null; - - MidiPortPath path = lastDevices.get (index); - return path.midiDevice.openPort (path.androidMidiPortID, true, host); + return openMidiPortWithJuceIndex (index, host, true); } public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().outPorts; + return openMidiPortWithJuceIndex (index, 0, false); + } - if (index >= lastDevices.size() || index < 0) - return null; + public boolean isBluetoothDevicePaired (String address) + { + return (findMidiDeviceForBluetoothAddress (address) != null); + } - MidiPortPath path = lastDevices.get (index); - return path.midiDevice.openPort (path.androidMidiPortID, false, 0); + public boolean pairBluetoothDevice (BluetoothDevice btDevice) + { + manager.openBluetoothDevice(btDevice, this, null); + return true; + } + + public void unpairBluetoothDevice (String address) + { + synchronized (MidiDeviceManager.class) + { + MidiDevice midiDevice = findMidiDeviceForBluetoothAddress (address); + if (midiDevice != null) + { + onDeviceRemoved (midiDevice.getInfo()); + + try { + midiDevice.close(); + } + catch (IOException exception) + { + Log.d ("JUCE", "IOException while closing midi device"); + } + } + } + } + + private MidiDevice findMidiDeviceForBluetoothAddress (String address) + { + for (MidiDevice midiDevice : midiDevices) + { + MidiDeviceInfo info = midiDevice.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; + } + + public void removePort (MidiPortPath path) + { + openPorts.remove (path); } public String getInputPortNameForJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().inPorts; + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); + if (portInfo != null) + return getPortName (portInfo); - if (index >= lastDevices.size() || index < 0) - return ""; - - MidiPortPath path = lastDevices.get (index); - - return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_INPUT, - path.androidMidiPortID, - path.portIndexToUseInName); + return ""; } public String getOutputPortNameForJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().outPorts; + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); + if (portInfo != null) + return getPortName (portInfo); - if (index >= lastDevices.size() || index < 0) - return ""; - - MidiPortPath path = lastDevices.get (index); - - return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, - path.androidMidiPortID, - path.portIndexToUseInName); + return ""; } public void onDeviceAdded (MidiDeviceInfo info) { - PhysicalMidiDevice device = PhysicalMidiDevice.fromMidiDeviceInfo (info, manager); + // only add standard midi devices + if (info.getType() == info.TYPE_BLUETOOTH) + return; - // Do not add bluetooth devices as they are already added by the native bluetooth dialog - if (info.getType() != MidiDeviceInfo.TYPE_BLUETOOTH) - physicalMidiDevices.add (device); + manager.openDevice (info, this, null); } public void onDeviceRemoved (MidiDeviceInfo info) { - for (int i = 0; i < physicalMidiDevices.size(); ++i) + synchronized (MidiDeviceManager.class) { - if (physicalMidiDevices.get(i).info.getId() == info.getId()) - { - physicalMidiDevices.remove (i); - return; + MidiDevice device = getMidiDeviceForId (info.getId()); + + // 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 (device != null) + midiDevices.remove (device); } - // Don't assert here as this may be called again after a bluetooth device is unpaired } public void onDeviceStatusChanged (MidiDeviceStatus status) { } - private ArrayList physicalMidiDevices; + @Override + public void onDeviceOpened (MidiDevice theDevice) + { + synchronized (MidiDeviceManager.class) + { + // make sure it's not already there + if (! midiDevices.contains(theDevice)) + midiDevices.add (theDevice); + } + } + + 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 (MidiDevice midiDevice : midiDevices) + infos[idx++] = midiDevice.getInfo(); + + return infos; + } + } + + private MidiDevice getMidiDeviceForId (int deviceId) + { + synchronized (MidiDeviceManager.class) + { + for (MidiDevice midiDevice : midiDevices) + if (midiDevice.getInfo().getId() == deviceId) + return midiDevice; + } + + return null; + } + private MidiManager manager; + private ArrayList midiDevices; + private MidiDeviceInfo[] deviceInfos; + private HashMap> openPorts; } public MidiDeviceManager getAndroidMidiDeviceManager() diff --git a/extras/AudioPerformanceTest/Builds/Android/app/src/main/java/com/juce/audioperformancetest/AudioPerformanceTest.java b/extras/AudioPerformanceTest/Builds/Android/app/src/main/java/com/juce/audioperformancetest/AudioPerformanceTest.java index c40afed921..6007d14340 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/src/main/java/com/juce/audioperformancetest/AudioPerformanceTest.java +++ b/extras/AudioPerformanceTest/Builds/Android/app/src/main/java/com/juce/audioperformancetest/AudioPerformanceTest.java @@ -55,6 +55,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; +import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import java.io.*; @@ -160,38 +161,6 @@ public class AudioPerformanceTest extends Activity } //============================================================================== - public static class MidiPortID extends Object - { - public MidiPortID (int index, boolean direction) - { - androidIndex = index; - isInput = direction; - } - - public int androidIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer (androidIndex); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortID other = (MidiPortID) obj; - return (androidIndex == other.androidIndex && isInput == other.isInput); - } - } - public interface JuceMidiPort { boolean isInputPort(); @@ -201,7 +170,6 @@ public class AudioPerformanceTest extends Activity void stop(); void close(); - MidiPortID getPortId(); // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count); @@ -279,17 +247,7 @@ public class AudioPerformanceTest extends Activity return false; } - MidiManager mm = (MidiManager) getSystemService (MIDI_SERVICE); - - PhysicalMidiDevice midiDevice = PhysicalMidiDevice.fromBluetoothLeDevice (btDevice, mm); - - if (midiDevice != null) - { - getAndroidMidiDeviceManager().addDeviceToList (midiDevice); - return true; - } - - return false; + return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice); } public void unpairBluetoothMidiDevice (String address) @@ -341,11 +299,20 @@ public class AudioPerformanceTest extends Activity { private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp); - public JuceMidiInputPort (PhysicalMidiDevice device, long host, MidiOutputPort midiPort) + public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse) { - parent = device; - juceHost = host; - port = midiPort; + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + juceHost = hostToUse; + isConnected = false; + } + + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); } @Override @@ -357,36 +324,40 @@ public class AudioPerformanceTest extends Activity @Override public void start() { - port.connect (this); + if (owner != null && androidPort != null && ! isConnected) { + androidPort.connect(this); + isConnected = true; + } } @Override public void stop() { - port.disconnect (this); + if (owner != null && androidPort != null && isConnected) { + androidPort.disconnect(this); + isConnected = false; + } } @Override public void close() { - stop(); - - try - { - port.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiInputPort::close: IOException = " + e.toString()); + if (androidPort != null) { + try { + androidPort.close(); + } catch (IOException exception) { + Log.d("JUCE", "IO Exception while closing port"); + } } - if (parent != null) - { - parent.removePort (port.getPortNumber(), true); - parent = null; - } + 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) @@ -394,27 +365,35 @@ public class AudioPerformanceTest extends Activity } @Override - public MidiPortID getPortId() - { - return new MidiPortID (port.getPortNumber(), true); - } + public void onFlush() + {} @Override public void sendMidi (byte[] msg, int offset, int count) { } - private PhysicalMidiDevice parent = null; - private long juceHost = 0; - private MidiOutputPort port; + MidiDeviceManager owner; + MidiOutputPort androidPort; + MidiPortPath portPath; + long juceHost; + boolean isConnected; } public static class JuceMidiOutputPort implements JuceMidiPort { - public JuceMidiOutputPort (PhysicalMidiDevice device, MidiInputPort midiPort) + public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse) { - parent = device; - port = midiPort; + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + } + + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); } @Override @@ -436,425 +415,81 @@ public class AudioPerformanceTest extends Activity @Override public void sendMidi (byte[] msg, int offset, int count) { - try + if (androidPort != null) { - port.send(msg, offset, count); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiOutputPort::sendMidi: IOException = " + e.toString()); + try { + androidPort.send(msg, offset, count); + } catch (IOException exception) + { + Log.d ("JUCE", "send midi had IO exception"); + } } } @Override public void close() { - try - { - port.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiOutputPort::close: IOException = " + e.toString()); + if (androidPort != null) { + try { + androidPort.close(); + } catch (IOException exception) { + Log.d("JUCE", "IO Exception while closing port"); + } } - if (parent != null) - { - parent.removePort (port.getPortNumber(), false); - parent = null; - } + if (owner != null) + owner.removePort (portPath); + + owner = null; + androidPort = null; } - - @Override - public MidiPortID getPortId() - { - return new MidiPortID (port.getPortNumber(), false); - } - - private PhysicalMidiDevice parent = null; - private MidiInputPort port; + MidiDeviceManager owner; + MidiInputPort androidPort; + MidiPortPath portPath; } - public static class PhysicalMidiDevice + private static class MidiPortPath extends Object { - private static class MidiDeviceThread extends Thread + public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex) { - public Handler handler = null; - public Object sync = null; + deviceId = deviceIdToUse; + isInput = direction; + portIndex = androidIndex; - public MidiDeviceThread (Object syncrhonization) - { - sync = syncrhonization; - } - - public void run() - { - Looper.prepare(); - - synchronized (sync) - { - handler = new Handler(); - sync.notifyAll(); - } - - Looper.loop(); - } } - private static class MidiDeviceOpenCallback implements MidiManager.OnDeviceOpenedListener + public int deviceId; + public int portIndex; + public boolean isInput; + + @Override + public int hashCode() { - public Object sync = null; - public boolean isWaiting = true; - public android.media.midi.MidiDevice theDevice = null; - - public MidiDeviceOpenCallback (Object waiter) - { - sync = waiter; - } - - public void onDeviceOpened (MidiDevice device) - { - synchronized (sync) - { - theDevice = device; - isWaiting = false; - sync.notifyAll(); - } - } + Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127)); + return i.hashCode() * (isInput ? -1 : 1); } - public static PhysicalMidiDevice fromBluetoothLeDevice (BluetoothDevice bluetoothDevice, MidiManager mm) + @Override + public boolean equals (Object obj) { - Object waitForCreation = new Object(); - MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); - thread.start(); + if (obj == null) + return false; - synchronized (waitForCreation) - { - while (thread.handler == null) - { - try - { - waitForCreation.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "Wait was interrupted but we don't care"); - } - } - } + if (getClass() != obj.getClass()) + return false; - Object waitForDevice = new Object(); - - MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); - - synchronized (waitForDevice) - { - mm.openBluetoothDevice (bluetoothDevice, openCallback, thread.handler); - - while (openCallback.isWaiting) - { - try - { - waitForDevice.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "Wait was interrupted but we don't care"); - } - } - } - - if (openCallback.theDevice == null) - { - Log.d ("JUCE", "openBluetoothDevice failed"); - return null; - } - - PhysicalMidiDevice device = new PhysicalMidiDevice(); - - device.handle = openCallback.theDevice; - device.info = device.handle.getInfo(); - device.bluetoothAddress = bluetoothDevice.getAddress(); - device.midiManager = mm; - - return device; + MidiPortPath other = (MidiPortPath) obj; + return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId); } - - public void unpair() - { - if (! bluetoothAddress.equals ("") && handle != null) - { - JuceMidiPort ports[] = new JuceMidiPort[0]; - ports = juceOpenedPorts.values().toArray(ports); - - for (int i = 0; i < ports.length; ++i) - ports[i].close(); - - juceOpenedPorts.clear(); - - try - { - handle.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "handle.close(): IOException = " + e.toString()); - } - - handle = null; - } - } - - public static PhysicalMidiDevice fromMidiDeviceInfo (MidiDeviceInfo info, MidiManager mm) - { - PhysicalMidiDevice device = new PhysicalMidiDevice(); - device.info = info; - device.midiManager = mm; - return device; - } - - public PhysicalMidiDevice() - { - bluetoothAddress = ""; - juceOpenedPorts = new Hashtable(); - handle = null; - } - - public MidiDeviceInfo.PortInfo[] getPorts() - { - return info.getPorts(); - } - - public String getHumanReadableNameForPort (MidiDeviceInfo.PortInfo port, int portIndexToUseInName, boolean addPortNumberToName) - { - if (addPortNumberToName) - { - String portName = port.getName(); - - if (portName.equals("")) - portName = ((port.getType() == MidiDeviceInfo.PortInfo.TYPE_OUTPUT) ? "Out " : "In ") - + Integer.toString(portIndexToUseInName); - - return getHumanReadableDeviceName() + " " + portName; - } - - return getHumanReadableDeviceName(); - } - - public String getHumanReadableNameForPort (int portType, int androidPortID, int portIndexToUseInName) - { - MidiDeviceInfo.PortInfo[] ports = info.getPorts(); - - int numTotalPorts = 0; - - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) - { - numTotalPorts++; - } - } - - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) - { - if (port.getPortNumber() == androidPortID) - return getHumanReadableNameForPort (port, portIndexToUseInName, (numTotalPorts > 1)); - } - } - - return "Unknown"; - } - - public String getHumanReadableDeviceName() - { - Bundle bundle = info.getProperties(); - return bundle.getString (MidiDeviceInfo.PROPERTY_NAME , "Unknown device"); - } - - public void checkIfDeviceCanBeClosed() - { - if (juceOpenedPorts.size() == 0) - { - // never close bluetooth LE devices, otherwise they unpair and we have - // no idea how many ports they have. - // Only remove bluetooth devices when we specifically unpair - if (bluetoothAddress.equals ("")) - { - try - { - handle.close(); - handle = null; - } - catch (IOException e) - { - Log.d ("JUCE", "PhysicalMidiDevice::checkIfDeviceCanBeClosed: IOException = " + e.toString()); - } - } - } - } - - public void removePort (int portIdx, boolean isInput) - { - MidiPortID portID = new MidiPortID (portIdx, isInput); - JuceMidiPort port = juceOpenedPorts.get (portID); - - if (port != null) - { - juceOpenedPorts.remove (portID); - checkIfDeviceCanBeClosed(); - return; - } - - // tried to remove a port that was never added - assert false; - } - - public JuceMidiPort openPort (int portIdx, boolean isInput, long host) - { - open(); - - if (handle == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: handle = null, device not open"); - return null; - } - - // make sure that the port is not already open - if (findPortForIdx (portIdx, isInput) != null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openInputPort: port already open, not opening again!"); - return null; - } - - JuceMidiPort retval = null; - - if (isInput) - { - MidiOutputPort androidPort = handle.openOutputPort (portIdx); - - if (androidPort == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openOutputPort (portIdx = " - + Integer.toString (portIdx) + ") failed!"); - return null; - } - - retval = new JuceMidiInputPort (this, host, androidPort); - } - else - { - MidiInputPort androidPort = handle.openInputPort (portIdx); - - if (androidPort == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openInputPort (portIdx = " - + Integer.toString (portIdx) + ") failed!"); - return null; - } - - retval = new JuceMidiOutputPort (this, androidPort); - } - - juceOpenedPorts.put (new MidiPortID (portIdx, isInput), retval); - return retval; - } - - private JuceMidiPort findPortForIdx (int idx, boolean isInput) - { - return juceOpenedPorts.get (new MidiPortID (idx, isInput)); - } - - // opens the device - private synchronized void open() - { - if (handle != null) - return; - - Object waitForCreation = new Object(); - MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); - thread.start(); - - synchronized(waitForCreation) - { - while (thread.handler == null) - { - try - { - waitForCreation.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "wait was interrupted but we don't care"); - } - } - } - - Object waitForDevice = new Object(); - - MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); - - synchronized (waitForDevice) - { - midiManager.openDevice (info, openCallback, thread.handler); - - while (openCallback.isWaiting) - { - try - { - waitForDevice.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "wait was interrupted but we don't care"); - } - } - } - - handle = openCallback.theDevice; - } - - private MidiDeviceInfo info; - private Hashtable juceOpenedPorts; - public MidiDevice handle; - public String bluetoothAddress; - private MidiManager midiManager; } //============================================================================== - public class MidiDeviceManager extends MidiManager.DeviceCallback + public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener { - public class MidiPortPath - { - public PhysicalMidiDevice midiDevice; - public int androidMidiPortID; - public int portIndexToUseInName; - } - - public class JuceDeviceList - { - public ArrayList inPorts = new ArrayList(); - public ArrayList outPorts = new ArrayList(); - } - - // We need to keep a thread local copy of the devices - // which we returned the last time - // getJuceAndroidMidiIn/OutputDevices() was called - private final ThreadLocal lastDevicesReturned = - new ThreadLocal() - { - @Override protected JuceDeviceList initialValue() - { - return new JuceDeviceList(); - } - }; public MidiDeviceManager() { - physicalMidiDevices = new ArrayList(); manager = (MidiManager) getSystemService (MIDI_SERVICE); if (manager == null) @@ -863,188 +498,319 @@ public class AudioPerformanceTest extends Activity return; } - manager.registerDeviceCallback (this, null); + openPorts = new HashMap> (); + midiDevices = new ArrayList(); MidiDeviceInfo[] foundDevices = manager.getDevices(); - for (MidiDeviceInfo info : foundDevices) - physicalMidiDevices.add (PhysicalMidiDevice.fromMidiDeviceInfo (info, manager)); + onDeviceAdded (info); + + manager.registerDeviceCallback (this, null); } - // specifically add a device to the list - public void addDeviceToList (PhysicalMidiDevice device) + protected void finalize() throws Throwable { - physicalMidiDevices.add (device); - } + manager.unregisterDeviceCallback (this); - public void unpairBluetoothDevice (String address) - { - for (int i = 0; i < physicalMidiDevices.size(); ++i) - { - PhysicalMidiDevice device = physicalMidiDevices.get(i); + for (MidiPortPath key : openPorts.keySet()) + openPorts.get (key).get().close(); - if (device.bluetoothAddress.equals (address)) - { - physicalMidiDevices.remove (i); - device.unpair(); - return; - } - } - } + openPorts = null; - public boolean isBluetoothDevicePaired (String address) - { - for (int i = 0; i < physicalMidiDevices.size(); ++i) - { - PhysicalMidiDevice device = physicalMidiDevices.get(i); + for (MidiDevice device : midiDevices) + device.close(); - if (device.bluetoothAddress.equals (address)) - return true; - } - - return false; + super.finalize(); } public String[] getJuceAndroidMidiInputDevices() { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); } public String[] getJuceAndroidMidiOutputDevices() { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); } private String[] getJuceAndroidMidiDevices (int portType) { - ArrayList listOfReturnedDevices = new ArrayList(); - List deviceNames = new ArrayList(); - - for (PhysicalMidiDevice physicalMidiDevice : physicalMidiDevices) + // only update the list when JUCE asks for a new list + synchronized (MidiDeviceManager.class) { - int portIdx = 0; - MidiDeviceInfo.PortInfo[] ports = physicalMidiDevice.getPorts(); + deviceInfos = getDeviceInfos(); + } - int numberOfPorts = 0; - for (MidiDeviceInfo.PortInfo port : ports) + ArrayList portNames = new ArrayList(); + + 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) { - if (port.getType() == portType) - { - numberOfPorts++; - } - } + // ports must be opened exclusively! + if (openPorts.containsKey (portInfo)) + return null; - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) + MidiDevice device = getMidiDeviceForId (portInfo.deviceId); + if (device != null) { - MidiPortPath path = new MidiPortPath(); - path.midiDevice = physicalMidiDevice; - path.androidMidiPortID = port.getPortNumber(); - path.portIndexToUseInName = ++portIdx; - listOfReturnedDevices.add (path); + JuceMidiPort juceMidiPort = null; - deviceNames.add (physicalMidiDevice.getHumanReadableNameForPort (port, - path.portIndexToUseInName, - (numberOfPorts > 1))); + 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)); + + return juceMidiPort; + } } } } - String[] deviceNamesArray = new String[deviceNames.size()]; - - if (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT) - { - lastDevicesReturned.get().inPorts.clear(); - lastDevicesReturned.get().inPorts.addAll (listOfReturnedDevices); - } - else - { - lastDevicesReturned.get().outPorts.clear(); - lastDevicesReturned.get().outPorts.addAll (listOfReturnedDevices); - } - - return deviceNames.toArray(deviceNamesArray); + return null; } public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) { - ArrayList lastDevices = lastDevicesReturned.get().inPorts; - - if (index >= lastDevices.size() || index < 0) - return null; - - MidiPortPath path = lastDevices.get (index); - return path.midiDevice.openPort (path.androidMidiPortID, true, host); + return openMidiPortWithJuceIndex (index, host, true); } public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().outPorts; + return openMidiPortWithJuceIndex (index, 0, false); + } - if (index >= lastDevices.size() || index < 0) - return null; + public boolean isBluetoothDevicePaired (String address) + { + return (findMidiDeviceForBluetoothAddress (address) != null); + } - MidiPortPath path = lastDevices.get (index); - return path.midiDevice.openPort (path.androidMidiPortID, false, 0); + public boolean pairBluetoothDevice (BluetoothDevice btDevice) + { + manager.openBluetoothDevice(btDevice, this, null); + return true; + } + + public void unpairBluetoothDevice (String address) + { + synchronized (MidiDeviceManager.class) + { + MidiDevice midiDevice = findMidiDeviceForBluetoothAddress (address); + if (midiDevice != null) + { + onDeviceRemoved (midiDevice.getInfo()); + + try { + midiDevice.close(); + } + catch (IOException exception) + { + Log.d ("JUCE", "IOException while closing midi device"); + } + } + } + } + + private MidiDevice findMidiDeviceForBluetoothAddress (String address) + { + for (MidiDevice midiDevice : midiDevices) + { + MidiDeviceInfo info = midiDevice.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; + } + + public void removePort (MidiPortPath path) + { + openPorts.remove (path); } public String getInputPortNameForJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().inPorts; + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); + if (portInfo != null) + return getPortName (portInfo); - if (index >= lastDevices.size() || index < 0) - return ""; - - MidiPortPath path = lastDevices.get (index); - - return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_INPUT, - path.androidMidiPortID, - path.portIndexToUseInName); + return ""; } public String getOutputPortNameForJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().outPorts; + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); + if (portInfo != null) + return getPortName (portInfo); - if (index >= lastDevices.size() || index < 0) - return ""; - - MidiPortPath path = lastDevices.get (index); - - return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, - path.androidMidiPortID, - path.portIndexToUseInName); + return ""; } public void onDeviceAdded (MidiDeviceInfo info) { - PhysicalMidiDevice device = PhysicalMidiDevice.fromMidiDeviceInfo (info, manager); + // only add standard midi devices + if (info.getType() == info.TYPE_BLUETOOTH) + return; - // Do not add bluetooth devices as they are already added by the native bluetooth dialog - if (info.getType() != MidiDeviceInfo.TYPE_BLUETOOTH) - physicalMidiDevices.add (device); + manager.openDevice (info, this, null); } public void onDeviceRemoved (MidiDeviceInfo info) { - for (int i = 0; i < physicalMidiDevices.size(); ++i) + synchronized (MidiDeviceManager.class) { - if (physicalMidiDevices.get(i).info.getId() == info.getId()) - { - physicalMidiDevices.remove (i); - return; + MidiDevice device = getMidiDeviceForId (info.getId()); + + // 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 (device != null) + midiDevices.remove (device); } - // Don't assert here as this may be called again after a bluetooth device is unpaired } public void onDeviceStatusChanged (MidiDeviceStatus status) { } - private ArrayList physicalMidiDevices; + @Override + public void onDeviceOpened (MidiDevice theDevice) + { + synchronized (MidiDeviceManager.class) + { + // make sure it's not already there + if (! midiDevices.contains(theDevice)) + midiDevices.add (theDevice); + } + } + + 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 (MidiDevice midiDevice : midiDevices) + infos[idx++] = midiDevice.getInfo(); + + return infos; + } + } + + private MidiDevice getMidiDeviceForId (int deviceId) + { + synchronized (MidiDeviceManager.class) + { + for (MidiDevice midiDevice : midiDevices) + if (midiDevice.getInfo().getId() == deviceId) + return midiDevice; + } + + return null; + } + private MidiManager manager; + private ArrayList midiDevices; + private MidiDeviceInfo[] deviceInfos; + private HashMap> openPorts; } public MidiDeviceManager getAndroidMidiDeviceManager() diff --git a/modules/juce_core/native/java/AndroidMidi.java b/modules/juce_core/native/java/AndroidMidi.java index 61110a332d..911d9ba47f 100644 --- a/modules/juce_core/native/java/AndroidMidi.java +++ b/modules/juce_core/native/java/AndroidMidi.java @@ -69,17 +69,7 @@ return false; } - MidiManager mm = (MidiManager) getSystemService (MIDI_SERVICE); - - PhysicalMidiDevice midiDevice = PhysicalMidiDevice.fromBluetoothLeDevice (btDevice, mm); - - if (midiDevice != null) - { - getAndroidMidiDeviceManager().addDeviceToList (midiDevice); - return true; - } - - return false; + return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice); } public void unpairBluetoothMidiDevice (String address) @@ -131,11 +121,20 @@ { private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp); - public JuceMidiInputPort (PhysicalMidiDevice device, long host, MidiOutputPort midiPort) + public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse) { - parent = device; - juceHost = host; - port = midiPort; + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + juceHost = hostToUse; + isConnected = false; + } + + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); } @Override @@ -147,36 +146,40 @@ @Override public void start() { - port.connect (this); + if (owner != null && androidPort != null && ! isConnected) { + androidPort.connect(this); + isConnected = true; + } } @Override public void stop() { - port.disconnect (this); + if (owner != null && androidPort != null && isConnected) { + androidPort.disconnect(this); + isConnected = false; + } } @Override public void close() { - stop(); - - try - { - port.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiInputPort::close: IOException = " + e.toString()); + if (androidPort != null) { + try { + androidPort.close(); + } catch (IOException exception) { + Log.d("JUCE", "IO Exception while closing port"); + } } - if (parent != null) - { - parent.removePort (port.getPortNumber(), true); - parent = null; - } + 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) @@ -184,27 +187,35 @@ } @Override - public MidiPortID getPortId() - { - return new MidiPortID (port.getPortNumber(), true); - } + public void onFlush() + {} @Override public void sendMidi (byte[] msg, int offset, int count) { } - private PhysicalMidiDevice parent = null; - private long juceHost = 0; - private MidiOutputPort port; + MidiDeviceManager owner; + MidiOutputPort androidPort; + MidiPortPath portPath; + long juceHost; + boolean isConnected; } public static class JuceMidiOutputPort implements JuceMidiPort { - public JuceMidiOutputPort (PhysicalMidiDevice device, MidiInputPort midiPort) + public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse) { - parent = device; - port = midiPort; + owner = mm; + androidPort = actualPort; + portPath = portPathToUse; + } + + @Override + protected void finalize() throws Throwable + { + close(); + super.finalize(); } @Override @@ -226,425 +237,81 @@ @Override public void sendMidi (byte[] msg, int offset, int count) { - try + if (androidPort != null) { - port.send(msg, offset, count); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiOutputPort::sendMidi: IOException = " + e.toString()); + try { + androidPort.send(msg, offset, count); + } catch (IOException exception) + { + Log.d ("JUCE", "send midi had IO exception"); + } } } @Override public void close() { - try - { - port.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "JuceMidiOutputPort::close: IOException = " + e.toString()); + if (androidPort != null) { + try { + androidPort.close(); + } catch (IOException exception) { + Log.d("JUCE", "IO Exception while closing port"); + } } - if (parent != null) - { - parent.removePort (port.getPortNumber(), false); - parent = null; - } + if (owner != null) + owner.removePort (portPath); + + owner = null; + androidPort = null; } - - @Override - public MidiPortID getPortId() - { - return new MidiPortID (port.getPortNumber(), false); - } - - private PhysicalMidiDevice parent = null; - private MidiInputPort port; + MidiDeviceManager owner; + MidiInputPort androidPort; + MidiPortPath portPath; } - public static class PhysicalMidiDevice + private static class MidiPortPath extends Object { - private static class MidiDeviceThread extends Thread + public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex) { - public Handler handler = null; - public Object sync = null; + deviceId = deviceIdToUse; + isInput = direction; + portIndex = androidIndex; - public MidiDeviceThread (Object syncrhonization) - { - sync = syncrhonization; - } - - public void run() - { - Looper.prepare(); - - synchronized (sync) - { - handler = new Handler(); - sync.notifyAll(); - } - - Looper.loop(); - } } - private static class MidiDeviceOpenCallback implements MidiManager.OnDeviceOpenedListener + public int deviceId; + public int portIndex; + public boolean isInput; + + @Override + public int hashCode() { - public Object sync = null; - public boolean isWaiting = true; - public android.media.midi.MidiDevice theDevice = null; - - public MidiDeviceOpenCallback (Object waiter) - { - sync = waiter; - } - - public void onDeviceOpened (MidiDevice device) - { - synchronized (sync) - { - theDevice = device; - isWaiting = false; - sync.notifyAll(); - } - } + Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127)); + return i.hashCode() * (isInput ? -1 : 1); } - public static PhysicalMidiDevice fromBluetoothLeDevice (BluetoothDevice bluetoothDevice, MidiManager mm) + @Override + public boolean equals (Object obj) { - Object waitForCreation = new Object(); - MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); - thread.start(); + if (obj == null) + return false; - synchronized (waitForCreation) - { - while (thread.handler == null) - { - try - { - waitForCreation.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "Wait was interrupted but we don't care"); - } - } - } + if (getClass() != obj.getClass()) + return false; - Object waitForDevice = new Object(); - - MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); - - synchronized (waitForDevice) - { - mm.openBluetoothDevice (bluetoothDevice, openCallback, thread.handler); - - while (openCallback.isWaiting) - { - try - { - waitForDevice.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "Wait was interrupted but we don't care"); - } - } - } - - if (openCallback.theDevice == null) - { - Log.d ("JUCE", "openBluetoothDevice failed"); - return null; - } - - PhysicalMidiDevice device = new PhysicalMidiDevice(); - - device.handle = openCallback.theDevice; - device.info = device.handle.getInfo(); - device.bluetoothAddress = bluetoothDevice.getAddress(); - device.midiManager = mm; - - return device; + MidiPortPath other = (MidiPortPath) obj; + return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId); } - - public void unpair() - { - if (! bluetoothAddress.equals ("") && handle != null) - { - JuceMidiPort ports[] = new JuceMidiPort[0]; - ports = juceOpenedPorts.values().toArray(ports); - - for (int i = 0; i < ports.length; ++i) - ports[i].close(); - - juceOpenedPorts.clear(); - - try - { - handle.close(); - } - catch (IOException e) - { - Log.d ("JUCE", "handle.close(): IOException = " + e.toString()); - } - - handle = null; - } - } - - public static PhysicalMidiDevice fromMidiDeviceInfo (MidiDeviceInfo info, MidiManager mm) - { - PhysicalMidiDevice device = new PhysicalMidiDevice(); - device.info = info; - device.midiManager = mm; - return device; - } - - public PhysicalMidiDevice() - { - bluetoothAddress = ""; - juceOpenedPorts = new Hashtable(); - handle = null; - } - - public MidiDeviceInfo.PortInfo[] getPorts() - { - return info.getPorts(); - } - - public String getHumanReadableNameForPort (MidiDeviceInfo.PortInfo port, int portIndexToUseInName, boolean addPortNumberToName) - { - if (addPortNumberToName) - { - String portName = port.getName(); - - if (portName.equals("")) - portName = ((port.getType() == MidiDeviceInfo.PortInfo.TYPE_OUTPUT) ? "Out " : "In ") - + Integer.toString(portIndexToUseInName); - - return getHumanReadableDeviceName() + " " + portName; - } - - return getHumanReadableDeviceName(); - } - - public String getHumanReadableNameForPort (int portType, int androidPortID, int portIndexToUseInName) - { - MidiDeviceInfo.PortInfo[] ports = info.getPorts(); - - int numTotalPorts = 0; - - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) - { - numTotalPorts++; - } - } - - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) - { - if (port.getPortNumber() == androidPortID) - return getHumanReadableNameForPort (port, portIndexToUseInName, (numTotalPorts > 1)); - } - } - - return "Unknown"; - } - - public String getHumanReadableDeviceName() - { - Bundle bundle = info.getProperties(); - return bundle.getString (MidiDeviceInfo.PROPERTY_NAME , "Unknown device"); - } - - public void checkIfDeviceCanBeClosed() - { - if (juceOpenedPorts.size() == 0) - { - // never close bluetooth LE devices, otherwise they unpair and we have - // no idea how many ports they have. - // Only remove bluetooth devices when we specifically unpair - if (bluetoothAddress.equals ("")) - { - try - { - handle.close(); - handle = null; - } - catch (IOException e) - { - Log.d ("JUCE", "PhysicalMidiDevice::checkIfDeviceCanBeClosed: IOException = " + e.toString()); - } - } - } - } - - public void removePort (int portIdx, boolean isInput) - { - MidiPortID portID = new MidiPortID (portIdx, isInput); - JuceMidiPort port = juceOpenedPorts.get (portID); - - if (port != null) - { - juceOpenedPorts.remove (portID); - checkIfDeviceCanBeClosed(); - return; - } - - // tried to remove a port that was never added - assert false; - } - - public JuceMidiPort openPort (int portIdx, boolean isInput, long host) - { - open(); - - if (handle == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: handle = null, device not open"); - return null; - } - - // make sure that the port is not already open - if (findPortForIdx (portIdx, isInput) != null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openInputPort: port already open, not opening again!"); - return null; - } - - JuceMidiPort retval = null; - - if (isInput) - { - MidiOutputPort androidPort = handle.openOutputPort (portIdx); - - if (androidPort == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openOutputPort (portIdx = " - + Integer.toString (portIdx) + ") failed!"); - return null; - } - - retval = new JuceMidiInputPort (this, host, androidPort); - } - else - { - MidiInputPort androidPort = handle.openInputPort (portIdx); - - if (androidPort == null) - { - Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openInputPort (portIdx = " - + Integer.toString (portIdx) + ") failed!"); - return null; - } - - retval = new JuceMidiOutputPort (this, androidPort); - } - - juceOpenedPorts.put (new MidiPortID (portIdx, isInput), retval); - return retval; - } - - private JuceMidiPort findPortForIdx (int idx, boolean isInput) - { - return juceOpenedPorts.get (new MidiPortID (idx, isInput)); - } - - // opens the device - private synchronized void open() - { - if (handle != null) - return; - - Object waitForCreation = new Object(); - MidiDeviceThread thread = new MidiDeviceThread (waitForCreation); - thread.start(); - - synchronized(waitForCreation) - { - while (thread.handler == null) - { - try - { - waitForCreation.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "wait was interrupted but we don't care"); - } - } - } - - Object waitForDevice = new Object(); - - MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice); - - synchronized (waitForDevice) - { - midiManager.openDevice (info, openCallback, thread.handler); - - while (openCallback.isWaiting) - { - try - { - waitForDevice.wait(); - } - catch (InterruptedException e) - { - Log.d ("JUCE", "wait was interrupted but we don't care"); - } - } - } - - handle = openCallback.theDevice; - } - - private MidiDeviceInfo info; - private Hashtable juceOpenedPorts; - public MidiDevice handle; - public String bluetoothAddress; - private MidiManager midiManager; } //============================================================================== - public class MidiDeviceManager extends MidiManager.DeviceCallback + public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener { - public class MidiPortPath - { - public PhysicalMidiDevice midiDevice; - public int androidMidiPortID; - public int portIndexToUseInName; - } - - public class JuceDeviceList - { - public ArrayList inPorts = new ArrayList(); - public ArrayList outPorts = new ArrayList(); - } - - // We need to keep a thread local copy of the devices - // which we returned the last time - // getJuceAndroidMidiIn/OutputDevices() was called - private final ThreadLocal lastDevicesReturned = - new ThreadLocal() - { - @Override protected JuceDeviceList initialValue() - { - return new JuceDeviceList(); - } - }; public MidiDeviceManager() { - physicalMidiDevices = new ArrayList(); manager = (MidiManager) getSystemService (MIDI_SERVICE); if (manager == null) @@ -653,188 +320,319 @@ return; } - manager.registerDeviceCallback (this, null); + openPorts = new HashMap> (); + midiDevices = new ArrayList(); MidiDeviceInfo[] foundDevices = manager.getDevices(); - for (MidiDeviceInfo info : foundDevices) - physicalMidiDevices.add (PhysicalMidiDevice.fromMidiDeviceInfo (info, manager)); + onDeviceAdded (info); + + manager.registerDeviceCallback (this, null); } - // specifically add a device to the list - public void addDeviceToList (PhysicalMidiDevice device) + protected void finalize() throws Throwable { - physicalMidiDevices.add (device); - } + manager.unregisterDeviceCallback (this); - public void unpairBluetoothDevice (String address) - { - for (int i = 0; i < physicalMidiDevices.size(); ++i) - { - PhysicalMidiDevice device = physicalMidiDevices.get(i); + for (MidiPortPath key : openPorts.keySet()) + openPorts.get (key).get().close(); - if (device.bluetoothAddress.equals (address)) - { - physicalMidiDevices.remove (i); - device.unpair(); - return; - } - } - } + openPorts = null; - public boolean isBluetoothDevicePaired (String address) - { - for (int i = 0; i < physicalMidiDevices.size(); ++i) - { - PhysicalMidiDevice device = physicalMidiDevices.get(i); + for (MidiDevice device : midiDevices) + device.close(); - if (device.bluetoothAddress.equals (address)) - return true; - } - - return false; + super.finalize(); } public String[] getJuceAndroidMidiInputDevices() { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); } public String[] getJuceAndroidMidiOutputDevices() { - return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT); + return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT); } private String[] getJuceAndroidMidiDevices (int portType) { - ArrayList listOfReturnedDevices = new ArrayList(); - List deviceNames = new ArrayList(); - - for (PhysicalMidiDevice physicalMidiDevice : physicalMidiDevices) + // only update the list when JUCE asks for a new list + synchronized (MidiDeviceManager.class) { - int portIdx = 0; - MidiDeviceInfo.PortInfo[] ports = physicalMidiDevice.getPorts(); + deviceInfos = getDeviceInfos(); + } - int numberOfPorts = 0; - for (MidiDeviceInfo.PortInfo port : ports) + ArrayList portNames = new ArrayList(); + + 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) { - if (port.getType() == portType) - { - numberOfPorts++; - } - } + // ports must be opened exclusively! + if (openPorts.containsKey (portInfo)) + return null; - for (MidiDeviceInfo.PortInfo port : ports) - { - if (port.getType() == portType) + MidiDevice device = getMidiDeviceForId (portInfo.deviceId); + if (device != null) { - MidiPortPath path = new MidiPortPath(); - path.midiDevice = physicalMidiDevice; - path.androidMidiPortID = port.getPortNumber(); - path.portIndexToUseInName = ++portIdx; - listOfReturnedDevices.add (path); + JuceMidiPort juceMidiPort = null; - deviceNames.add (physicalMidiDevice.getHumanReadableNameForPort (port, - path.portIndexToUseInName, - (numberOfPorts > 1))); + 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)); + + return juceMidiPort; + } } } } - String[] deviceNamesArray = new String[deviceNames.size()]; - - if (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT) - { - lastDevicesReturned.get().inPorts.clear(); - lastDevicesReturned.get().inPorts.addAll (listOfReturnedDevices); - } - else - { - lastDevicesReturned.get().outPorts.clear(); - lastDevicesReturned.get().outPorts.addAll (listOfReturnedDevices); - } - - return deviceNames.toArray(deviceNamesArray); + return null; } public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host) { - ArrayList lastDevices = lastDevicesReturned.get().inPorts; - - if (index >= lastDevices.size() || index < 0) - return null; - - MidiPortPath path = lastDevices.get (index); - return path.midiDevice.openPort (path.androidMidiPortID, true, host); + return openMidiPortWithJuceIndex (index, host, true); } public JuceMidiPort openMidiOutputPortWithJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().outPorts; + return openMidiPortWithJuceIndex (index, 0, false); + } - if (index >= lastDevices.size() || index < 0) - return null; + public boolean isBluetoothDevicePaired (String address) + { + return (findMidiDeviceForBluetoothAddress (address) != null); + } - MidiPortPath path = lastDevices.get (index); - return path.midiDevice.openPort (path.androidMidiPortID, false, 0); + public boolean pairBluetoothDevice (BluetoothDevice btDevice) + { + manager.openBluetoothDevice(btDevice, this, null); + return true; + } + + public void unpairBluetoothDevice (String address) + { + synchronized (MidiDeviceManager.class) + { + MidiDevice midiDevice = findMidiDeviceForBluetoothAddress (address); + if (midiDevice != null) + { + onDeviceRemoved (midiDevice.getInfo()); + + try { + midiDevice.close(); + } + catch (IOException exception) + { + Log.d ("JUCE", "IOException while closing midi device"); + } + } + } + } + + private MidiDevice findMidiDeviceForBluetoothAddress (String address) + { + for (MidiDevice midiDevice : midiDevices) + { + MidiDeviceInfo info = midiDevice.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; + } + + public void removePort (MidiPortPath path) + { + openPorts.remove (path); } public String getInputPortNameForJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().inPorts; + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index); + if (portInfo != null) + return getPortName (portInfo); - if (index >= lastDevices.size() || index < 0) - return ""; - - MidiPortPath path = lastDevices.get (index); - - return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_INPUT, - path.androidMidiPortID, - path.portIndexToUseInName); + return ""; } public String getOutputPortNameForJuceIndex (int index) { - ArrayList lastDevices = lastDevicesReturned.get().outPorts; + MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index); + if (portInfo != null) + return getPortName (portInfo); - if (index >= lastDevices.size() || index < 0) - return ""; - - MidiPortPath path = lastDevices.get (index); - - return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, - path.androidMidiPortID, - path.portIndexToUseInName); + return ""; } public void onDeviceAdded (MidiDeviceInfo info) { - PhysicalMidiDevice device = PhysicalMidiDevice.fromMidiDeviceInfo (info, manager); + // only add standard midi devices + if (info.getType() == info.TYPE_BLUETOOTH) + return; - // Do not add bluetooth devices as they are already added by the native bluetooth dialog - if (info.getType() != MidiDeviceInfo.TYPE_BLUETOOTH) - physicalMidiDevices.add (device); + manager.openDevice (info, this, null); } public void onDeviceRemoved (MidiDeviceInfo info) { - for (int i = 0; i < physicalMidiDevices.size(); ++i) + synchronized (MidiDeviceManager.class) { - if (physicalMidiDevices.get(i).info.getId() == info.getId()) - { - physicalMidiDevices.remove (i); - return; + MidiDevice device = getMidiDeviceForId (info.getId()); + + // 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 (device != null) + midiDevices.remove (device); } - // Don't assert here as this may be called again after a bluetooth device is unpaired } public void onDeviceStatusChanged (MidiDeviceStatus status) { } - private ArrayList physicalMidiDevices; + @Override + public void onDeviceOpened (MidiDevice theDevice) + { + synchronized (MidiDeviceManager.class) + { + // make sure it's not already there + if (! midiDevices.contains(theDevice)) + midiDevices.add (theDevice); + } + } + + 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 (MidiDevice midiDevice : midiDevices) + infos[idx++] = midiDevice.getInfo(); + + return infos; + } + } + + private MidiDevice getMidiDeviceForId (int deviceId) + { + synchronized (MidiDeviceManager.class) + { + for (MidiDevice midiDevice : midiDevices) + if (midiDevice.getInfo().getId() == deviceId) + return midiDevice; + } + + return null; + } + private MidiManager manager; + private ArrayList midiDevices; + private MidiDeviceInfo[] deviceInfos; + private HashMap> openPorts; } public MidiDeviceManager getAndroidMidiDeviceManager() diff --git a/modules/juce_core/native/java/JuceAppActivity.java b/modules/juce_core/native/java/JuceAppActivity.java index 6b34b00025..669f4230b7 100644 --- a/modules/juce_core/native/java/JuceAppActivity.java +++ b/modules/juce_core/native/java/JuceAppActivity.java @@ -55,6 +55,7 @@ import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; import java.lang.Runnable; +import java.lang.ref.WeakReference; import java.lang.reflect.*; import java.util.*; import java.io.*; @@ -146,38 +147,6 @@ public class JuceAppActivity extends Activity $$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the Projucer! //============================================================================== - public static class MidiPortID extends Object - { - public MidiPortID (int index, boolean direction) - { - androidIndex = index; - isInput = direction; - } - - public int androidIndex; - public boolean isInput; - - @Override - public int hashCode() - { - Integer i = new Integer (androidIndex); - return i.hashCode() * (isInput ? -1 : 1); - } - - @Override - public boolean equals (Object obj) - { - if (obj == null) - return false; - - if (getClass() != obj.getClass()) - return false; - - MidiPortID other = (MidiPortID) obj; - return (androidIndex == other.androidIndex && isInput == other.isInput); - } - } - public interface JuceMidiPort { boolean isInputPort(); @@ -187,7 +156,6 @@ public class JuceAppActivity extends Activity void stop(); void close(); - MidiPortID getPortId(); // send will do nothing on an input port void sendMidi (byte[] msg, int offset, int count);