The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

670 lines
21KB

  1. //==============================================================================
  2. public class BluetoothManager extends ScanCallback
  3. {
  4. BluetoothManager()
  5. {
  6. }
  7. public String[] getMidiBluetoothAddresses()
  8. {
  9. return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]);
  10. }
  11. public String getHumanReadableStringForBluetoothAddress (String address)
  12. {
  13. BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
  14. return btDevice.getName();
  15. }
  16. public boolean isBluetoothDevicePaired (String address)
  17. {
  18. return getAndroidMidiDeviceManager().isBluetoothDevicePaired (address);
  19. }
  20. public void startStopScan (boolean shouldStart)
  21. {
  22. BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  23. if (bluetoothAdapter == null)
  24. {
  25. Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter");
  26. return;
  27. }
  28. BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
  29. if (bluetoothLeScanner == null)
  30. {
  31. Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner");
  32. return;
  33. }
  34. if (shouldStart)
  35. {
  36. ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();
  37. scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID));
  38. ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
  39. scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
  40. .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER)
  41. .setScanMode (ScanSettings.MATCH_MODE_STICKY);
  42. bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()),
  43. scanSettingsBuilder.build(),
  44. this);
  45. }
  46. else
  47. {
  48. bluetoothLeScanner.stopScan (this);
  49. }
  50. }
  51. public boolean pairBluetoothMidiDevice(String address)
  52. {
  53. BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
  54. if (btDevice == null)
  55. {
  56. Log.d ("JUCE", "failed to create buletooth device from address");
  57. return false;
  58. }
  59. return getAndroidMidiDeviceManager().pairBluetoothDevice (btDevice);
  60. }
  61. public void unpairBluetoothMidiDevice (String address)
  62. {
  63. getAndroidMidiDeviceManager().unpairBluetoothDevice (address);
  64. }
  65. public void onScanFailed (int errorCode)
  66. {
  67. }
  68. public void onScanResult (int callbackType, ScanResult result)
  69. {
  70. if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
  71. || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
  72. {
  73. BluetoothDevice device = result.getDevice();
  74. if (device != null)
  75. bluetoothMidiDevices.add (device.getAddress());
  76. }
  77. if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST)
  78. {
  79. Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST");
  80. BluetoothDevice device = result.getDevice();
  81. if (device != null)
  82. {
  83. bluetoothMidiDevices.remove (device.getAddress());
  84. unpairBluetoothMidiDevice (device.getAddress());
  85. }
  86. }
  87. }
  88. public void onBatchScanResults (List<ScanResult> results)
  89. {
  90. for (ScanResult result : results)
  91. onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
  92. }
  93. private BluetoothLeScanner scanner;
  94. private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
  95. private HashSet<String> bluetoothMidiDevices = new HashSet<String>();
  96. }
  97. public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort
  98. {
  99. private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp);
  100. public JuceMidiInputPort (MidiDeviceManager mm, MidiOutputPort actualPort, MidiPortPath portPathToUse, long hostToUse)
  101. {
  102. owner = mm;
  103. androidPort = actualPort;
  104. portPath = portPathToUse;
  105. juceHost = hostToUse;
  106. isConnected = false;
  107. }
  108. @Override
  109. protected void finalize() throws Throwable
  110. {
  111. close();
  112. super.finalize();
  113. }
  114. @Override
  115. public boolean isInputPort()
  116. {
  117. return true;
  118. }
  119. @Override
  120. public void start()
  121. {
  122. if (owner != null && androidPort != null && ! isConnected) {
  123. androidPort.connect(this);
  124. isConnected = true;
  125. }
  126. }
  127. @Override
  128. public void stop()
  129. {
  130. if (owner != null && androidPort != null && isConnected) {
  131. androidPort.disconnect(this);
  132. isConnected = false;
  133. }
  134. }
  135. @Override
  136. public void close()
  137. {
  138. if (androidPort != null) {
  139. try {
  140. androidPort.close();
  141. } catch (IOException exception) {
  142. Log.d("JUCE", "IO Exception while closing port");
  143. }
  144. }
  145. if (owner != null)
  146. owner.removePort (portPath);
  147. owner = null;
  148. androidPort = null;
  149. }
  150. @Override
  151. public void onSend (byte[] msg, int offset, int count, long timestamp)
  152. {
  153. if (count > 0)
  154. handleReceive (juceHost, msg, offset, count, timestamp);
  155. }
  156. @Override
  157. public void onFlush()
  158. {}
  159. @Override
  160. public void sendMidi (byte[] msg, int offset, int count)
  161. {
  162. }
  163. MidiDeviceManager owner;
  164. MidiOutputPort androidPort;
  165. MidiPortPath portPath;
  166. long juceHost;
  167. boolean isConnected;
  168. }
  169. public static class JuceMidiOutputPort implements JuceMidiPort
  170. {
  171. public JuceMidiOutputPort (MidiDeviceManager mm, MidiInputPort actualPort, MidiPortPath portPathToUse)
  172. {
  173. owner = mm;
  174. androidPort = actualPort;
  175. portPath = portPathToUse;
  176. }
  177. @Override
  178. protected void finalize() throws Throwable
  179. {
  180. close();
  181. super.finalize();
  182. }
  183. @Override
  184. public boolean isInputPort()
  185. {
  186. return false;
  187. }
  188. @Override
  189. public void start()
  190. {
  191. }
  192. @Override
  193. public void stop()
  194. {
  195. }
  196. @Override
  197. public void sendMidi (byte[] msg, int offset, int count)
  198. {
  199. if (androidPort != null)
  200. {
  201. try {
  202. androidPort.send(msg, offset, count);
  203. } catch (IOException exception)
  204. {
  205. Log.d ("JUCE", "send midi had IO exception");
  206. }
  207. }
  208. }
  209. @Override
  210. public void close()
  211. {
  212. if (androidPort != null) {
  213. try {
  214. androidPort.close();
  215. } catch (IOException exception) {
  216. Log.d("JUCE", "IO Exception while closing port");
  217. }
  218. }
  219. if (owner != null)
  220. owner.removePort (portPath);
  221. owner = null;
  222. androidPort = null;
  223. }
  224. MidiDeviceManager owner;
  225. MidiInputPort androidPort;
  226. MidiPortPath portPath;
  227. }
  228. private static class MidiPortPath extends Object
  229. {
  230. public MidiPortPath (int deviceIdToUse, boolean direction, int androidIndex)
  231. {
  232. deviceId = deviceIdToUse;
  233. isInput = direction;
  234. portIndex = androidIndex;
  235. }
  236. public int deviceId;
  237. public int portIndex;
  238. public boolean isInput;
  239. @Override
  240. public int hashCode()
  241. {
  242. Integer i = new Integer ((deviceId * 128) + (portIndex < 128 ? portIndex : 127));
  243. return i.hashCode() * (isInput ? -1 : 1);
  244. }
  245. @Override
  246. public boolean equals (Object obj)
  247. {
  248. if (obj == null)
  249. return false;
  250. if (getClass() != obj.getClass())
  251. return false;
  252. MidiPortPath other = (MidiPortPath) obj;
  253. return (portIndex == other.portIndex && isInput == other.isInput && deviceId == other.deviceId);
  254. }
  255. }
  256. //==============================================================================
  257. public class MidiDeviceManager extends MidiManager.DeviceCallback implements MidiManager.OnDeviceOpenedListener
  258. {
  259. public MidiDeviceManager()
  260. {
  261. manager = (MidiManager) getSystemService (MIDI_SERVICE);
  262. if (manager == null)
  263. {
  264. Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service");
  265. return;
  266. }
  267. openPorts = new HashMap<MidiPortPath, WeakReference<JuceMidiPort>> ();
  268. midiDevices = new ArrayList<MidiDevice>();
  269. MidiDeviceInfo[] foundDevices = manager.getDevices();
  270. for (MidiDeviceInfo info : foundDevices)
  271. onDeviceAdded (info);
  272. manager.registerDeviceCallback (this, null);
  273. }
  274. protected void finalize() throws Throwable
  275. {
  276. manager.unregisterDeviceCallback (this);
  277. for (MidiPortPath key : openPorts.keySet())
  278. openPorts.get (key).get().close();
  279. openPorts = null;
  280. for (MidiDevice device : midiDevices)
  281. device.close();
  282. super.finalize();
  283. }
  284. public String[] getJuceAndroidMidiInputDevices()
  285. {
  286. return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
  287. }
  288. public String[] getJuceAndroidMidiOutputDevices()
  289. {
  290. return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT);
  291. }
  292. private String[] getJuceAndroidMidiDevices (int portType)
  293. {
  294. // only update the list when JUCE asks for a new list
  295. synchronized (MidiDeviceManager.class)
  296. {
  297. deviceInfos = getDeviceInfos();
  298. }
  299. ArrayList<String> portNames = new ArrayList<String>();
  300. int index = 0;
  301. for (MidiPortPath portInfo = getPortPathForJuceIndex (portType, index); portInfo != null; portInfo = getPortPathForJuceIndex (portType, ++index))
  302. portNames.add (getPortName (portInfo));
  303. String[] names = new String[portNames.size()];
  304. return portNames.toArray (names);
  305. }
  306. private JuceMidiPort openMidiPortWithJuceIndex (int index, long host, boolean isInput)
  307. {
  308. synchronized (MidiDeviceManager.class)
  309. {
  310. int portTypeToFind = (isInput ? MidiDeviceInfo.PortInfo.TYPE_OUTPUT : MidiDeviceInfo.PortInfo.TYPE_INPUT);
  311. MidiPortPath portInfo = getPortPathForJuceIndex (portTypeToFind, index);
  312. if (portInfo != null)
  313. {
  314. // ports must be opened exclusively!
  315. if (openPorts.containsKey (portInfo))
  316. return null;
  317. MidiDevice device = getMidiDeviceForId (portInfo.deviceId);
  318. if (device != null)
  319. {
  320. JuceMidiPort juceMidiPort = null;
  321. if (isInput)
  322. {
  323. MidiOutputPort outputPort = device.openOutputPort (portInfo.portIndex);
  324. if (outputPort != null)
  325. juceMidiPort = new JuceMidiInputPort(this, outputPort, portInfo, host);
  326. }
  327. else
  328. {
  329. MidiInputPort inputPort = device.openInputPort (portInfo.portIndex);
  330. if (inputPort != null)
  331. juceMidiPort = new JuceMidiOutputPort(this, inputPort, portInfo);
  332. }
  333. if (juceMidiPort != null) {
  334. openPorts.put(portInfo, new WeakReference<JuceMidiPort>(juceMidiPort));
  335. return juceMidiPort;
  336. }
  337. }
  338. }
  339. }
  340. return null;
  341. }
  342. public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)
  343. {
  344. return openMidiPortWithJuceIndex (index, host, true);
  345. }
  346. public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)
  347. {
  348. return openMidiPortWithJuceIndex (index, 0, false);
  349. }
  350. public boolean isBluetoothDevicePaired (String address)
  351. {
  352. return (findMidiDeviceForBluetoothAddress (address) != null);
  353. }
  354. public boolean pairBluetoothDevice (BluetoothDevice btDevice)
  355. {
  356. manager.openBluetoothDevice(btDevice, this, null);
  357. return true;
  358. }
  359. public void unpairBluetoothDevice (String address)
  360. {
  361. synchronized (MidiDeviceManager.class)
  362. {
  363. MidiDevice midiDevice = findMidiDeviceForBluetoothAddress (address);
  364. if (midiDevice != null)
  365. {
  366. onDeviceRemoved (midiDevice.getInfo());
  367. try {
  368. midiDevice.close();
  369. }
  370. catch (IOException exception)
  371. {
  372. Log.d ("JUCE", "IOException while closing midi device");
  373. }
  374. }
  375. }
  376. }
  377. private MidiDevice findMidiDeviceForBluetoothAddress (String address)
  378. {
  379. for (MidiDevice midiDevice : midiDevices)
  380. {
  381. MidiDeviceInfo info = midiDevice.getInfo();
  382. if (info.getType() == MidiDeviceInfo.TYPE_BLUETOOTH)
  383. {
  384. BluetoothDevice btDevice = (BluetoothDevice) info.getProperties().get (info.PROPERTY_BLUETOOTH_DEVICE);
  385. if (btDevice != null && btDevice.getAddress().equals (address))
  386. return midiDevice;
  387. }
  388. }
  389. return null;
  390. }
  391. public void removePort (MidiPortPath path)
  392. {
  393. openPorts.remove (path);
  394. }
  395. public String getInputPortNameForJuceIndex (int index)
  396. {
  397. MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_OUTPUT, index);
  398. if (portInfo != null)
  399. return getPortName (portInfo);
  400. return "";
  401. }
  402. public String getOutputPortNameForJuceIndex (int index)
  403. {
  404. MidiPortPath portInfo = getPortPathForJuceIndex (MidiDeviceInfo.PortInfo.TYPE_INPUT, index);
  405. if (portInfo != null)
  406. return getPortName (portInfo);
  407. return "";
  408. }
  409. public void onDeviceAdded (MidiDeviceInfo info)
  410. {
  411. // only add standard midi devices
  412. if (info.getType() == info.TYPE_BLUETOOTH)
  413. return;
  414. manager.openDevice (info, this, null);
  415. }
  416. public void onDeviceRemoved (MidiDeviceInfo info)
  417. {
  418. synchronized (MidiDeviceManager.class)
  419. {
  420. MidiDevice device = getMidiDeviceForId (info.getId());
  421. // close all ports that use this device
  422. boolean removedPort = true;
  423. while (removedPort == true) {
  424. removedPort = false;
  425. for (MidiPortPath key : openPorts.keySet()) {
  426. if (key.deviceId == info.getId()) {
  427. openPorts.get(key).get().close();
  428. removedPort = true;
  429. break;
  430. }
  431. }
  432. }
  433. if (device != null)
  434. midiDevices.remove (device);
  435. }
  436. }
  437. public void onDeviceStatusChanged (MidiDeviceStatus status)
  438. {
  439. }
  440. @Override
  441. public void onDeviceOpened (MidiDevice theDevice)
  442. {
  443. synchronized (MidiDeviceManager.class)
  444. {
  445. // make sure it's not already there
  446. if (! midiDevices.contains(theDevice))
  447. midiDevices.add (theDevice);
  448. }
  449. }
  450. public String getPortName(MidiPortPath path)
  451. {
  452. int portTypeToFind = (path.isInput ? MidiDeviceInfo.PortInfo.TYPE_INPUT : MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
  453. synchronized (MidiDeviceManager.class)
  454. {
  455. for (MidiDeviceInfo info : deviceInfos)
  456. {
  457. int localIndex = 0;
  458. if (info.getId() == path.deviceId)
  459. {
  460. for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
  461. {
  462. int portType = portInfo.getType();
  463. if (portType == portTypeToFind)
  464. {
  465. int portIndex = portInfo.getPortNumber();
  466. if (portIndex == path.portIndex)
  467. {
  468. String portName = portInfo.getName();
  469. if (portName.isEmpty())
  470. portName = (String) info.getProperties().get(info.PROPERTY_NAME);
  471. return portName;
  472. }
  473. }
  474. }
  475. }
  476. }
  477. }
  478. return "";
  479. }
  480. public MidiPortPath getPortPathForJuceIndex (int portType, int juceIndex)
  481. {
  482. int portIdx = 0;
  483. for (MidiDeviceInfo info : deviceInfos)
  484. {
  485. for (MidiDeviceInfo.PortInfo portInfo : info.getPorts())
  486. {
  487. if (portInfo.getType() == portType)
  488. {
  489. if (portIdx == juceIndex)
  490. return new MidiPortPath (info.getId(),
  491. (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT),
  492. portInfo.getPortNumber());
  493. portIdx++;
  494. }
  495. }
  496. }
  497. return null;
  498. }
  499. private MidiDeviceInfo[] getDeviceInfos()
  500. {
  501. synchronized (MidiDeviceManager.class)
  502. {
  503. MidiDeviceInfo[] infos = new MidiDeviceInfo[midiDevices.size()];
  504. int idx = 0;
  505. for (MidiDevice midiDevice : midiDevices)
  506. infos[idx++] = midiDevice.getInfo();
  507. return infos;
  508. }
  509. }
  510. private MidiDevice getMidiDeviceForId (int deviceId)
  511. {
  512. synchronized (MidiDeviceManager.class)
  513. {
  514. for (MidiDevice midiDevice : midiDevices)
  515. if (midiDevice.getInfo().getId() == deviceId)
  516. return midiDevice;
  517. }
  518. return null;
  519. }
  520. private MidiManager manager;
  521. private ArrayList<MidiDevice> midiDevices;
  522. private MidiDeviceInfo[] deviceInfos;
  523. private HashMap<MidiPortPath, WeakReference<JuceMidiPort>> openPorts;
  524. }
  525. public MidiDeviceManager getAndroidMidiDeviceManager()
  526. {
  527. if (getSystemService (MIDI_SERVICE) == null)
  528. return null;
  529. synchronized (JuceAppActivity.class)
  530. {
  531. if (midiDeviceManager == null)
  532. midiDeviceManager = new MidiDeviceManager();
  533. }
  534. return midiDeviceManager;
  535. }
  536. public BluetoothManager getAndroidBluetoothManager()
  537. {
  538. BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
  539. if (adapter == null)
  540. return null;
  541. if (adapter.getBluetoothLeScanner() == null)
  542. return null;
  543. synchronized (JuceAppActivity.class)
  544. {
  545. if (bluetoothManager == null)
  546. bluetoothManager = new BluetoothManager();
  547. }
  548. return bluetoothManager;
  549. }