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.

2070 lines
70KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. package com.juce.jucedemo;
  18. import android.app.Activity;
  19. import android.app.AlertDialog;
  20. import android.content.DialogInterface;
  21. import android.content.Context;
  22. import android.content.Intent;
  23. import android.content.res.Configuration;
  24. import android.content.pm.PackageInfo;
  25. import android.content.pm.PackageManager;
  26. import android.net.Uri;
  27. import android.os.Bundle;
  28. import android.os.Looper;
  29. import android.os.Handler;
  30. import android.os.ParcelUuid;
  31. import android.os.Environment;
  32. import android.view.*;
  33. import android.view.inputmethod.BaseInputConnection;
  34. import android.view.inputmethod.EditorInfo;
  35. import android.view.inputmethod.InputConnection;
  36. import android.view.inputmethod.InputMethodManager;
  37. import android.graphics.*;
  38. import android.text.ClipboardManager;
  39. import android.text.InputType;
  40. import android.util.DisplayMetrics;
  41. import android.util.Log;
  42. import java.lang.Runnable;
  43. import java.util.*;
  44. import java.io.*;
  45. import java.net.URL;
  46. import java.net.HttpURLConnection;
  47. import android.media.AudioManager;
  48. import android.media.MediaScannerConnection;
  49. import android.media.MediaScannerConnection.MediaScannerConnectionClient;
  50. import android.support.v4.content.ContextCompat;
  51. import android.support.v4.app.ActivityCompat;
  52. import android.Manifest;
  53. import android.media.midi.*;
  54. import android.bluetooth.*;
  55. import android.bluetooth.le.*;
  56. //==============================================================================
  57. public class JuceDemo extends Activity
  58. {
  59. //==============================================================================
  60. static
  61. {
  62. System.loadLibrary ("juce_jni");
  63. }
  64. //==============================================================================
  65. public boolean isPermissionDeclaredInManifest (int permissionID)
  66. {
  67. String permissionToCheck = getAndroidPermissionName(permissionID);
  68. try
  69. {
  70. PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
  71. if (info.requestedPermissions != null)
  72. for (String permission : info.requestedPermissions)
  73. if (permission.equals (permissionToCheck))
  74. return true;
  75. }
  76. catch (PackageManager.NameNotFoundException e)
  77. {
  78. Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());
  79. }
  80. Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);
  81. return false;
  82. }
  83. //==============================================================================
  84. // these have to match the values of enum PermissionID in C++ class RuntimePermissions:
  85. private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1;
  86. private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
  87. private static String getAndroidPermissionName (int permissionID)
  88. {
  89. switch (permissionID)
  90. {
  91. case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO;
  92. case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION;
  93. }
  94. // unknown permission ID!
  95. assert false;
  96. return new String();
  97. }
  98. public boolean isPermissionGranted (int permissionID)
  99. {
  100. return ContextCompat.checkSelfPermission (this, getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;
  101. }
  102. private Map<Integer, Long> permissionCallbackPtrMap;
  103. public void requestRuntimePermission (int permissionID, long ptrToCallback)
  104. {
  105. String permissionName = getAndroidPermissionName (permissionID);
  106. if (ContextCompat.checkSelfPermission (this, permissionName) != PackageManager.PERMISSION_GRANTED)
  107. {
  108. // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously
  109. permissionCallbackPtrMap.put (permissionID, ptrToCallback);
  110. ActivityCompat.requestPermissions (this, new String[]{permissionName}, permissionID);
  111. }
  112. else
  113. {
  114. // permissions were already granted before, we can call callback directly
  115. androidRuntimePermissionsCallback (true, ptrToCallback);
  116. }
  117. }
  118. private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);
  119. @Override
  120. public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults)
  121. {
  122. boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);
  123. if (! permissionsGranted)
  124. Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID));
  125. Long ptrToCallback = permissionCallbackPtrMap.get (permissionID);
  126. permissionCallbackPtrMap.remove (permissionID);
  127. androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback);
  128. }
  129. //==============================================================================
  130. public static class MidiPortID extends Object
  131. {
  132. public MidiPortID (int index, boolean direction)
  133. {
  134. androidIndex = index;
  135. isInput = direction;
  136. }
  137. public int androidIndex;
  138. public boolean isInput;
  139. @Override
  140. public int hashCode()
  141. {
  142. Integer i = new Integer (androidIndex);
  143. return i.hashCode() * (isInput ? -1 : 1);
  144. }
  145. @Override
  146. public boolean equals (Object obj)
  147. {
  148. if (obj == null)
  149. return false;
  150. if (getClass() != obj.getClass())
  151. return false;
  152. MidiPortID other = (MidiPortID) obj;
  153. return (androidIndex == other.androidIndex && isInput == other.isInput);
  154. }
  155. }
  156. public interface JuceMidiPort
  157. {
  158. boolean isInputPort();
  159. // start, stop does nothing on an output port
  160. void start();
  161. void stop();
  162. void close();
  163. MidiPortID getPortId();
  164. // send will do nothing on an input port
  165. void sendMidi (byte[] msg, int offset, int count);
  166. }
  167. //==============================================================================
  168. //==============================================================================
  169. public class BluetoothManager extends ScanCallback
  170. {
  171. BluetoothManager()
  172. {
  173. ScanFilter.Builder scanFilterBuilder = new ScanFilter.Builder();
  174. scanFilterBuilder.setServiceUuid (ParcelUuid.fromString (bluetoothLEMidiServiceUUID));
  175. ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder();
  176. scanSettingsBuilder.setCallbackType (ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
  177. .setScanMode (ScanSettings.SCAN_MODE_LOW_POWER)
  178. .setScanMode (ScanSettings.MATCH_MODE_STICKY);
  179. BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  180. if (bluetoothAdapter == null)
  181. {
  182. Log.d ("JUCE", "BluetoothManager error: could not get default Bluetooth adapter");
  183. return;
  184. }
  185. BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
  186. if (bluetoothLeScanner == null)
  187. {
  188. Log.d ("JUCE", "BluetoothManager error: could not get Bluetooth LE scanner");
  189. return;
  190. }
  191. bluetoothLeScanner.startScan (Arrays.asList (scanFilterBuilder.build()),
  192. scanSettingsBuilder.build(),
  193. this);
  194. }
  195. public String[] getMidiBluetoothAddresses()
  196. {
  197. return bluetoothMidiDevices.toArray (new String[bluetoothMidiDevices.size()]);
  198. }
  199. public String getHumanReadableStringForBluetoothAddress (String address)
  200. {
  201. BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
  202. return btDevice.getName();
  203. }
  204. public boolean isBluetoothDevicePaired (String address)
  205. {
  206. return getAndroidMidiDeviceManager().isBluetoothDevicePaired (address);
  207. }
  208. public boolean pairBluetoothMidiDevice(String address)
  209. {
  210. BluetoothDevice btDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice (address);
  211. if (btDevice == null)
  212. {
  213. Log.d ("JUCE", "failed to create buletooth device from address");
  214. return false;
  215. }
  216. MidiManager mm = (MidiManager) getSystemService (MIDI_SERVICE);
  217. PhysicalMidiDevice midiDevice = PhysicalMidiDevice.fromBluetoothLeDevice (btDevice, mm);
  218. if (midiDevice != null)
  219. {
  220. getAndroidMidiDeviceManager().addDeviceToList (midiDevice);
  221. return true;
  222. }
  223. return false;
  224. }
  225. public void unpairBluetoothMidiDevice (String address)
  226. {
  227. getAndroidMidiDeviceManager().unpairBluetoothDevice (address);
  228. }
  229. public void onScanFailed (int errorCode)
  230. {
  231. }
  232. public void onScanResult (int callbackType, ScanResult result)
  233. {
  234. if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
  235. || callbackType == ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
  236. {
  237. BluetoothDevice device = result.getDevice();
  238. if (device != null)
  239. bluetoothMidiDevices.add (device.getAddress());
  240. }
  241. if (callbackType == ScanSettings.CALLBACK_TYPE_MATCH_LOST)
  242. {
  243. Log.d ("JUCE", "ScanSettings.CALLBACK_TYPE_MATCH_LOST");
  244. BluetoothDevice device = result.getDevice();
  245. if (device != null)
  246. {
  247. bluetoothMidiDevices.remove (device.getAddress());
  248. unpairBluetoothMidiDevice (device.getAddress());
  249. }
  250. }
  251. }
  252. public void onBatchScanResults (List<ScanResult> results)
  253. {
  254. for (ScanResult result : results)
  255. onScanResult (ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
  256. }
  257. private BluetoothLeScanner scanner;
  258. private static final String bluetoothLEMidiServiceUUID = "03B80E5A-EDE8-4B33-A751-6CE34EC4C700";
  259. private HashSet<String> bluetoothMidiDevices = new HashSet<String>();
  260. }
  261. public static class JuceMidiInputPort extends MidiReceiver implements JuceMidiPort
  262. {
  263. private native void handleReceive (long host, byte[] msg, int offset, int count, long timestamp);
  264. public JuceMidiInputPort (PhysicalMidiDevice device, long host, MidiOutputPort midiPort)
  265. {
  266. parent = device;
  267. juceHost = host;
  268. port = midiPort;
  269. }
  270. @Override
  271. public boolean isInputPort()
  272. {
  273. return true;
  274. }
  275. @Override
  276. public void start()
  277. {
  278. port.connect (this);
  279. }
  280. @Override
  281. public void stop()
  282. {
  283. port.disconnect (this);
  284. }
  285. @Override
  286. public void close()
  287. {
  288. stop();
  289. try
  290. {
  291. port.close();
  292. }
  293. catch (IOException e)
  294. {
  295. Log.d ("JUCE", "JuceMidiInputPort::close: IOException = " + e.toString());
  296. }
  297. if (parent != null)
  298. {
  299. parent.removePort (port.getPortNumber(), true);
  300. parent = null;
  301. }
  302. }
  303. public void onSend (byte[] msg, int offset, int count, long timestamp)
  304. {
  305. if (count > 0)
  306. handleReceive (juceHost, msg, offset, count, timestamp);
  307. }
  308. @Override
  309. public MidiPortID getPortId()
  310. {
  311. return new MidiPortID (port.getPortNumber(), true);
  312. }
  313. @Override
  314. public void sendMidi (byte[] msg, int offset, int count)
  315. {
  316. }
  317. private PhysicalMidiDevice parent = null;
  318. private long juceHost = 0;
  319. private MidiOutputPort port;
  320. }
  321. public static class JuceMidiOutputPort implements JuceMidiPort
  322. {
  323. public JuceMidiOutputPort (PhysicalMidiDevice device, MidiInputPort midiPort)
  324. {
  325. parent = device;
  326. port = midiPort;
  327. }
  328. @Override
  329. public boolean isInputPort()
  330. {
  331. return false;
  332. }
  333. @Override
  334. public void start()
  335. {
  336. }
  337. @Override
  338. public void stop()
  339. {
  340. }
  341. @Override
  342. public void sendMidi (byte[] msg, int offset, int count)
  343. {
  344. try
  345. {
  346. port.send(msg, offset, count);
  347. }
  348. catch (IOException e)
  349. {
  350. Log.d ("JUCE", "JuceMidiOutputPort::sendMidi: IOException = " + e.toString());
  351. }
  352. }
  353. @Override
  354. public void close()
  355. {
  356. try
  357. {
  358. port.close();
  359. }
  360. catch (IOException e)
  361. {
  362. Log.d ("JUCE", "JuceMidiOutputPort::close: IOException = " + e.toString());
  363. }
  364. if (parent != null)
  365. {
  366. parent.removePort (port.getPortNumber(), false);
  367. parent = null;
  368. }
  369. }
  370. @Override
  371. public MidiPortID getPortId()
  372. {
  373. return new MidiPortID (port.getPortNumber(), false);
  374. }
  375. private PhysicalMidiDevice parent = null;
  376. private MidiInputPort port;
  377. }
  378. public static class PhysicalMidiDevice
  379. {
  380. private static class MidiDeviceThread extends Thread
  381. {
  382. public Handler handler = null;
  383. public Object sync = null;
  384. public MidiDeviceThread (Object syncrhonization)
  385. {
  386. sync = syncrhonization;
  387. }
  388. public void run()
  389. {
  390. Looper.prepare();
  391. synchronized (sync)
  392. {
  393. handler = new Handler();
  394. sync.notifyAll();
  395. }
  396. Looper.loop();
  397. }
  398. }
  399. private static class MidiDeviceOpenCallback implements MidiManager.OnDeviceOpenedListener
  400. {
  401. public Object sync = null;
  402. public boolean isWaiting = true;
  403. public android.media.midi.MidiDevice theDevice = null;
  404. public MidiDeviceOpenCallback (Object waiter)
  405. {
  406. sync = waiter;
  407. }
  408. public void onDeviceOpened (MidiDevice device)
  409. {
  410. synchronized (sync)
  411. {
  412. theDevice = device;
  413. isWaiting = false;
  414. sync.notifyAll();
  415. }
  416. }
  417. }
  418. public static PhysicalMidiDevice fromBluetoothLeDevice (BluetoothDevice bluetoothDevice, MidiManager mm)
  419. {
  420. Object waitForCreation = new Object();
  421. MidiDeviceThread thread = new MidiDeviceThread (waitForCreation);
  422. thread.start();
  423. synchronized (waitForCreation)
  424. {
  425. while (thread.handler == null)
  426. {
  427. try
  428. {
  429. waitForCreation.wait();
  430. }
  431. catch (InterruptedException e)
  432. {
  433. Log.d ("JUCE", "Wait was interrupted but we don't care");
  434. }
  435. }
  436. }
  437. Object waitForDevice = new Object();
  438. MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice);
  439. synchronized (waitForDevice)
  440. {
  441. mm.openBluetoothDevice (bluetoothDevice, openCallback, thread.handler);
  442. while (openCallback.isWaiting)
  443. {
  444. try
  445. {
  446. waitForDevice.wait();
  447. }
  448. catch (InterruptedException e)
  449. {
  450. Log.d ("JUCE", "Wait was interrupted but we don't care");
  451. }
  452. }
  453. }
  454. if (openCallback.theDevice == null)
  455. {
  456. Log.d ("JUCE", "openBluetoothDevice failed");
  457. return null;
  458. }
  459. PhysicalMidiDevice device = new PhysicalMidiDevice();
  460. device.handle = openCallback.theDevice;
  461. device.info = device.handle.getInfo();
  462. device.bluetoothAddress = bluetoothDevice.getAddress();
  463. device.midiManager = mm;
  464. return device;
  465. }
  466. public void unpair()
  467. {
  468. if (! bluetoothAddress.equals ("") && handle != null)
  469. {
  470. JuceMidiPort ports[] = new JuceMidiPort[0];
  471. ports = juceOpenedPorts.values().toArray(ports);
  472. for (int i = 0; i < ports.length; ++i)
  473. ports[i].close();
  474. juceOpenedPorts.clear();
  475. try
  476. {
  477. handle.close();
  478. }
  479. catch (IOException e)
  480. {
  481. Log.d ("JUCE", "handle.close(): IOException = " + e.toString());
  482. }
  483. handle = null;
  484. }
  485. }
  486. public static PhysicalMidiDevice fromMidiDeviceInfo (MidiDeviceInfo info, MidiManager mm)
  487. {
  488. PhysicalMidiDevice device = new PhysicalMidiDevice();
  489. device.info = info;
  490. device.midiManager = mm;
  491. return device;
  492. }
  493. public PhysicalMidiDevice()
  494. {
  495. bluetoothAddress = "";
  496. juceOpenedPorts = new Hashtable<MidiPortID, JuceMidiPort>();
  497. handle = null;
  498. }
  499. public MidiDeviceInfo.PortInfo[] getPorts()
  500. {
  501. return info.getPorts();
  502. }
  503. public String getHumanReadableNameForPort (MidiDeviceInfo.PortInfo port, int portIndexToUseInName)
  504. {
  505. String portName = port.getName();
  506. if (portName.equals (""))
  507. portName = ((port.getType() == MidiDeviceInfo.PortInfo.TYPE_OUTPUT) ? "Out " : "In ")
  508. + Integer.toString (portIndexToUseInName);
  509. return getHumanReadableDeviceName() + " " + portName;
  510. }
  511. public String getHumanReadableNameForPort (int portType, int androidPortID, int portIndexToUseInName)
  512. {
  513. MidiDeviceInfo.PortInfo[] ports = info.getPorts();
  514. for (MidiDeviceInfo.PortInfo port : ports)
  515. {
  516. if (port.getType() == portType)
  517. {
  518. if (port.getPortNumber() == androidPortID)
  519. return getHumanReadableNameForPort (port, portIndexToUseInName);
  520. }
  521. }
  522. return "Unknown";
  523. }
  524. public String getHumanReadableDeviceName()
  525. {
  526. Bundle bundle = info.getProperties();
  527. return bundle.getString (MidiDeviceInfo.PROPERTY_NAME , "Unknown device");
  528. }
  529. public void checkIfDeviceCanBeClosed()
  530. {
  531. if (juceOpenedPorts.size() == 0)
  532. {
  533. // never close bluetooth LE devices, otherwise they unpair and we have
  534. // no idea how many ports they have.
  535. // Only remove bluetooth devices when we specifically unpair
  536. if (bluetoothAddress.equals (""))
  537. {
  538. try
  539. {
  540. handle.close();
  541. handle = null;
  542. }
  543. catch (IOException e)
  544. {
  545. Log.d ("JUCE", "PhysicalMidiDevice::checkIfDeviceCanBeClosed: IOException = " + e.toString());
  546. }
  547. }
  548. }
  549. }
  550. public void removePort (int portIdx, boolean isInput)
  551. {
  552. MidiPortID portID = new MidiPortID (portIdx, isInput);
  553. JuceMidiPort port = juceOpenedPorts.get (portID);
  554. if (port != null)
  555. {
  556. juceOpenedPorts.remove (portID);
  557. checkIfDeviceCanBeClosed();
  558. return;
  559. }
  560. // tried to remove a port that was never added
  561. assert false;
  562. }
  563. public JuceMidiPort openPort (int portIdx, boolean isInput, long host)
  564. {
  565. open();
  566. if (handle == null)
  567. {
  568. Log.d ("JUCE", "PhysicalMidiDevice::openPort: handle = null, device not open");
  569. return null;
  570. }
  571. // make sure that the port is not already open
  572. if (findPortForIdx (portIdx, isInput) != null)
  573. {
  574. Log.d ("JUCE", "PhysicalMidiDevice::openInputPort: port already open, not opening again!");
  575. return null;
  576. }
  577. JuceMidiPort retval = null;
  578. if (isInput)
  579. {
  580. MidiOutputPort androidPort = handle.openOutputPort (portIdx);
  581. if (androidPort == null)
  582. {
  583. Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openOutputPort (portIdx = "
  584. + Integer.toString (portIdx) + ") failed!");
  585. return null;
  586. }
  587. retval = new JuceMidiInputPort (this, host, androidPort);
  588. }
  589. else
  590. {
  591. MidiInputPort androidPort = handle.openInputPort (portIdx);
  592. if (androidPort == null)
  593. {
  594. Log.d ("JUCE", "PhysicalMidiDevice::openPort: MidiDevice::openInputPort (portIdx = "
  595. + Integer.toString (portIdx) + ") failed!");
  596. return null;
  597. }
  598. retval = new JuceMidiOutputPort (this, androidPort);
  599. }
  600. juceOpenedPorts.put (new MidiPortID (portIdx, isInput), retval);
  601. return retval;
  602. }
  603. private JuceMidiPort findPortForIdx (int idx, boolean isInput)
  604. {
  605. return juceOpenedPorts.get (new MidiPortID (idx, isInput));
  606. }
  607. // opens the device
  608. private synchronized void open()
  609. {
  610. if (handle != null)
  611. return;
  612. Object waitForCreation = new Object();
  613. MidiDeviceThread thread = new MidiDeviceThread (waitForCreation);
  614. thread.start();
  615. synchronized(waitForCreation)
  616. {
  617. while (thread.handler == null)
  618. {
  619. try
  620. {
  621. waitForCreation.wait();
  622. }
  623. catch (InterruptedException e)
  624. {
  625. Log.d ("JUCE", "wait was interrupted but we don't care");
  626. }
  627. }
  628. }
  629. Object waitForDevice = new Object();
  630. MidiDeviceOpenCallback openCallback = new MidiDeviceOpenCallback (waitForDevice);
  631. synchronized (waitForDevice)
  632. {
  633. midiManager.openDevice (info, openCallback, thread.handler);
  634. while (openCallback.isWaiting)
  635. {
  636. try
  637. {
  638. waitForDevice.wait();
  639. }
  640. catch (InterruptedException e)
  641. {
  642. Log.d ("JUCE", "wait was interrupted but we don't care");
  643. }
  644. }
  645. }
  646. handle = openCallback.theDevice;
  647. }
  648. private MidiDeviceInfo info;
  649. private Hashtable<MidiPortID, JuceMidiPort> juceOpenedPorts;
  650. public MidiDevice handle;
  651. public String bluetoothAddress;
  652. private MidiManager midiManager;
  653. }
  654. //==============================================================================
  655. public class MidiDeviceManager extends MidiManager.DeviceCallback
  656. {
  657. public class MidiPortPath
  658. {
  659. public PhysicalMidiDevice midiDevice;
  660. public int androidMidiPortID;
  661. public int portIndexToUseInName;
  662. }
  663. public class JuceDeviceList
  664. {
  665. public ArrayList<MidiPortPath> inPorts = new ArrayList<MidiPortPath>();
  666. public ArrayList<MidiPortPath> outPorts = new ArrayList<MidiPortPath>();
  667. }
  668. // We need to keep a thread local copy of the devices
  669. // which we returned the last time
  670. // getJuceAndroidMidiIn/OutputDevices() was called
  671. private final ThreadLocal<JuceDeviceList> lastDevicesReturned =
  672. new ThreadLocal<JuceDeviceList>()
  673. {
  674. @Override protected JuceDeviceList initialValue()
  675. {
  676. return new JuceDeviceList();
  677. }
  678. };
  679. public MidiDeviceManager()
  680. {
  681. physicalMidiDevices = new ArrayList<PhysicalMidiDevice>();
  682. manager = (MidiManager) getSystemService (MIDI_SERVICE);
  683. if (manager == null)
  684. {
  685. Log.d ("JUCE", "MidiDeviceManager error: could not get MidiManager system service");
  686. return;
  687. }
  688. manager.registerDeviceCallback (this, null);
  689. MidiDeviceInfo[] foundDevices = manager.getDevices();
  690. for (MidiDeviceInfo info : foundDevices)
  691. physicalMidiDevices.add (PhysicalMidiDevice.fromMidiDeviceInfo (info, manager));
  692. }
  693. // specifically add a device to the list
  694. public void addDeviceToList (PhysicalMidiDevice device)
  695. {
  696. physicalMidiDevices.add (device);
  697. }
  698. public void unpairBluetoothDevice (String address)
  699. {
  700. for (int i = 0; i < physicalMidiDevices.size(); ++i)
  701. {
  702. PhysicalMidiDevice device = physicalMidiDevices.get(i);
  703. if (device.bluetoothAddress.equals (address))
  704. {
  705. physicalMidiDevices.remove (i);
  706. device.unpair();
  707. return;
  708. }
  709. }
  710. }
  711. public boolean isBluetoothDevicePaired (String address)
  712. {
  713. for (int i = 0; i < physicalMidiDevices.size(); ++i)
  714. {
  715. PhysicalMidiDevice device = physicalMidiDevices.get(i);
  716. if (device.bluetoothAddress.equals (address))
  717. return true;
  718. }
  719. return false;
  720. }
  721. public String[] getJuceAndroidMidiInputDevices()
  722. {
  723. return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_INPUT);
  724. }
  725. public String[] getJuceAndroidMidiOutputDevices()
  726. {
  727. return getJuceAndroidMidiDevices (MidiDeviceInfo.PortInfo.TYPE_OUTPUT);
  728. }
  729. private String[] getJuceAndroidMidiDevices (int portType)
  730. {
  731. ArrayList<MidiPortPath> listOfReturnedDevices = new ArrayList<MidiPortPath>();
  732. List<String> deviceNames = new ArrayList<String>();
  733. for (PhysicalMidiDevice physicalMidiDevice : physicalMidiDevices)
  734. {
  735. int portIdx = 0;
  736. MidiDeviceInfo.PortInfo[] ports = physicalMidiDevice.getPorts();
  737. for (MidiDeviceInfo.PortInfo port : ports)
  738. {
  739. if (port.getType() == portType)
  740. {
  741. MidiPortPath path = new MidiPortPath();
  742. path.midiDevice = physicalMidiDevice;
  743. path.androidMidiPortID = port.getPortNumber();
  744. path.portIndexToUseInName = ++portIdx;
  745. listOfReturnedDevices.add (path);
  746. deviceNames.add (physicalMidiDevice.getHumanReadableNameForPort (port,
  747. path.portIndexToUseInName));
  748. }
  749. }
  750. }
  751. String[] deviceNamesArray = new String[deviceNames.size()];
  752. if (portType == MidiDeviceInfo.PortInfo.TYPE_INPUT)
  753. {
  754. lastDevicesReturned.get().inPorts.clear();
  755. lastDevicesReturned.get().inPorts.addAll (listOfReturnedDevices);
  756. }
  757. else
  758. {
  759. lastDevicesReturned.get().outPorts.clear();
  760. lastDevicesReturned.get().outPorts.addAll (listOfReturnedDevices);
  761. }
  762. return deviceNames.toArray(deviceNamesArray);
  763. }
  764. public JuceMidiPort openMidiInputPortWithJuceIndex (int index, long host)
  765. {
  766. ArrayList<MidiPortPath> lastDevices = lastDevicesReturned.get().inPorts;
  767. if (index >= lastDevices.size() || index < 0)
  768. return null;
  769. MidiPortPath path = lastDevices.get (index);
  770. return path.midiDevice.openPort (path.androidMidiPortID, true, host);
  771. }
  772. public JuceMidiPort openMidiOutputPortWithJuceIndex (int index)
  773. {
  774. ArrayList<MidiPortPath> lastDevices = lastDevicesReturned.get().outPorts;
  775. if (index >= lastDevices.size() || index < 0)
  776. return null;
  777. MidiPortPath path = lastDevices.get (index);
  778. return path.midiDevice.openPort (path.androidMidiPortID, false, 0);
  779. }
  780. public String getInputPortNameForJuceIndex (int index)
  781. {
  782. ArrayList<MidiPortPath> lastDevices = lastDevicesReturned.get().inPorts;
  783. if (index >= lastDevices.size() || index < 0)
  784. return "";
  785. MidiPortPath path = lastDevices.get (index);
  786. return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_INPUT,
  787. path.androidMidiPortID,
  788. path.portIndexToUseInName);
  789. }
  790. public String getOutputPortNameForJuceIndex (int index)
  791. {
  792. ArrayList<MidiPortPath> lastDevices = lastDevicesReturned.get().outPorts;
  793. if (index >= lastDevices.size() || index < 0)
  794. return "";
  795. MidiPortPath path = lastDevices.get (index);
  796. return path.midiDevice.getHumanReadableNameForPort (MidiDeviceInfo.PortInfo.TYPE_OUTPUT,
  797. path.androidMidiPortID,
  798. path.portIndexToUseInName);
  799. }
  800. public void onDeviceAdded (MidiDeviceInfo info)
  801. {
  802. PhysicalMidiDevice device = PhysicalMidiDevice.fromMidiDeviceInfo (info, manager);
  803. // Do not add bluetooth devices as they are already added by the native bluetooth dialog
  804. if (info.getType() != MidiDeviceInfo.TYPE_BLUETOOTH)
  805. physicalMidiDevices.add (device);
  806. }
  807. public void onDeviceRemoved (MidiDeviceInfo info)
  808. {
  809. for (int i = 0; i < physicalMidiDevices.size(); ++i)
  810. {
  811. if (physicalMidiDevices.get(i).info.getId() == info.getId())
  812. {
  813. physicalMidiDevices.remove (i);
  814. return;
  815. }
  816. }
  817. // Don't assert here as this may be called again after a bluetooth device is unpaired
  818. }
  819. public void onDeviceStatusChanged (MidiDeviceStatus status)
  820. {
  821. }
  822. private ArrayList<PhysicalMidiDevice> physicalMidiDevices;
  823. private MidiManager manager;
  824. }
  825. public MidiDeviceManager getAndroidMidiDeviceManager()
  826. {
  827. if (getSystemService (MIDI_SERVICE) == null)
  828. return null;
  829. synchronized (JuceDemo.class)
  830. {
  831. if (midiDeviceManager == null)
  832. midiDeviceManager = new MidiDeviceManager();
  833. }
  834. return midiDeviceManager;
  835. }
  836. public BluetoothManager getAndroidBluetoothManager()
  837. {
  838. BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
  839. if (adapter == null)
  840. return null;
  841. if (adapter.getBluetoothLeScanner() == null)
  842. return null;
  843. synchronized (JuceDemo.class)
  844. {
  845. if (bluetoothManager == null)
  846. bluetoothManager = new BluetoothManager();
  847. }
  848. return bluetoothManager;
  849. }
  850. //==============================================================================
  851. @Override
  852. public void onCreate (Bundle savedInstanceState)
  853. {
  854. super.onCreate (savedInstanceState);
  855. isScreenSaverEnabled = true;
  856. hideActionBar();
  857. viewHolder = new ViewHolder (this);
  858. setContentView (viewHolder);
  859. setVolumeControlStream (AudioManager.STREAM_MUSIC);
  860. permissionCallbackPtrMap = new HashMap<Integer, Long>();
  861. }
  862. @Override
  863. protected void onDestroy()
  864. {
  865. quitApp();
  866. super.onDestroy();
  867. clearDataCache();
  868. }
  869. @Override
  870. protected void onPause()
  871. {
  872. suspendApp();
  873. super.onPause();
  874. }
  875. @Override
  876. protected void onResume()
  877. {
  878. super.onResume();
  879. resumeApp();
  880. }
  881. @Override
  882. public void onConfigurationChanged (Configuration cfg)
  883. {
  884. super.onConfigurationChanged (cfg);
  885. setContentView (viewHolder);
  886. }
  887. private void callAppLauncher()
  888. {
  889. launchApp (getApplicationInfo().publicSourceDir,
  890. getApplicationInfo().dataDir);
  891. }
  892. private void hideActionBar()
  893. {
  894. // get "getActionBar" method
  895. java.lang.reflect.Method getActionBarMethod = null;
  896. try
  897. {
  898. getActionBarMethod = this.getClass().getMethod ("getActionBar");
  899. }
  900. catch (SecurityException e) { return; }
  901. catch (NoSuchMethodException e) { return; }
  902. if (getActionBarMethod == null) return;
  903. // invoke "getActionBar" method
  904. Object actionBar = null;
  905. try
  906. {
  907. actionBar = getActionBarMethod.invoke (this);
  908. }
  909. catch (java.lang.IllegalArgumentException e) { return; }
  910. catch (java.lang.IllegalAccessException e) { return; }
  911. catch (java.lang.reflect.InvocationTargetException e) { return; }
  912. if (actionBar == null) return;
  913. // get "hide" method
  914. java.lang.reflect.Method actionBarHideMethod = null;
  915. try
  916. {
  917. actionBarHideMethod = actionBar.getClass().getMethod ("hide");
  918. }
  919. catch (SecurityException e) { return; }
  920. catch (NoSuchMethodException e) { return; }
  921. if (actionBarHideMethod == null) return;
  922. // invoke "hide" method
  923. try
  924. {
  925. actionBarHideMethod.invoke (actionBar);
  926. }
  927. catch (java.lang.IllegalArgumentException e) {}
  928. catch (java.lang.IllegalAccessException e) {}
  929. catch (java.lang.reflect.InvocationTargetException e) {}
  930. }
  931. //==============================================================================
  932. private native void launchApp (String appFile, String appDataDir);
  933. private native void quitApp();
  934. private native void suspendApp();
  935. private native void resumeApp();
  936. private native void setScreenSize (int screenWidth, int screenHeight, int dpi);
  937. //==============================================================================
  938. public native void deliverMessage (long value);
  939. private android.os.Handler messageHandler = new android.os.Handler();
  940. public final void postMessage (long value)
  941. {
  942. messageHandler.post (new MessageCallback (value));
  943. }
  944. private final class MessageCallback implements Runnable
  945. {
  946. public MessageCallback (long value_) { value = value_; }
  947. public final void run() { deliverMessage (value); }
  948. private long value;
  949. }
  950. //==============================================================================
  951. private ViewHolder viewHolder;
  952. private MidiDeviceManager midiDeviceManager = null;
  953. private BluetoothManager bluetoothManager = null;
  954. private boolean isScreenSaverEnabled;
  955. private java.util.Timer keepAliveTimer;
  956. public final ComponentPeerView createNewView (boolean opaque, long host)
  957. {
  958. ComponentPeerView v = new ComponentPeerView (this, opaque, host);
  959. viewHolder.addView (v);
  960. return v;
  961. }
  962. public final void deleteView (ComponentPeerView view)
  963. {
  964. ViewGroup group = (ViewGroup) (view.getParent());
  965. if (group != null)
  966. group.removeView (view);
  967. }
  968. public final void deleteNativeSurfaceView (NativeSurfaceView view)
  969. {
  970. ViewGroup group = (ViewGroup) (view.getParent());
  971. if (group != null)
  972. group.removeView (view);
  973. }
  974. final class ViewHolder extends ViewGroup
  975. {
  976. public ViewHolder (Context context)
  977. {
  978. super (context);
  979. setDescendantFocusability (ViewGroup.FOCUS_AFTER_DESCENDANTS);
  980. setFocusable (false);
  981. }
  982. protected final void onLayout (boolean changed, int left, int top, int right, int bottom)
  983. {
  984. setScreenSize (getWidth(), getHeight(), getDPI());
  985. if (isFirstResize)
  986. {
  987. isFirstResize = false;
  988. callAppLauncher();
  989. }
  990. }
  991. private final int getDPI()
  992. {
  993. DisplayMetrics metrics = new DisplayMetrics();
  994. getWindowManager().getDefaultDisplay().getMetrics (metrics);
  995. return metrics.densityDpi;
  996. }
  997. private boolean isFirstResize = true;
  998. }
  999. public final void excludeClipRegion (android.graphics.Canvas canvas, float left, float top, float right, float bottom)
  1000. {
  1001. canvas.clipRect (left, top, right, bottom, android.graphics.Region.Op.DIFFERENCE);
  1002. }
  1003. //==============================================================================
  1004. public final void setScreenSaver (boolean enabled)
  1005. {
  1006. if (isScreenSaverEnabled != enabled)
  1007. {
  1008. isScreenSaverEnabled = enabled;
  1009. if (keepAliveTimer != null)
  1010. {
  1011. keepAliveTimer.cancel();
  1012. keepAliveTimer = null;
  1013. }
  1014. if (enabled)
  1015. {
  1016. getWindow().clearFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  1017. }
  1018. else
  1019. {
  1020. getWindow().addFlags (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  1021. // If no user input is received after about 3 seconds, the OS will lower the
  1022. // task's priority, so this timer forces it to be kept active.
  1023. keepAliveTimer = new java.util.Timer();
  1024. keepAliveTimer.scheduleAtFixedRate (new TimerTask()
  1025. {
  1026. @Override
  1027. public void run()
  1028. {
  1029. android.app.Instrumentation instrumentation = new android.app.Instrumentation();
  1030. try
  1031. {
  1032. instrumentation.sendKeyDownUpSync (KeyEvent.KEYCODE_UNKNOWN);
  1033. }
  1034. catch (Exception e)
  1035. {
  1036. }
  1037. }
  1038. }, 2000, 2000);
  1039. }
  1040. }
  1041. }
  1042. public final boolean getScreenSaver()
  1043. {
  1044. return isScreenSaverEnabled;
  1045. }
  1046. //==============================================================================
  1047. public final String getClipboardContent()
  1048. {
  1049. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  1050. return clipboard.getText().toString();
  1051. }
  1052. public final void setClipboardContent (String newText)
  1053. {
  1054. ClipboardManager clipboard = (ClipboardManager) getSystemService (CLIPBOARD_SERVICE);
  1055. clipboard.setText (newText);
  1056. }
  1057. //==============================================================================
  1058. public final void showMessageBox (String title, String message, final long callback)
  1059. {
  1060. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  1061. builder.setTitle (title)
  1062. .setMessage (message)
  1063. .setCancelable (true)
  1064. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  1065. {
  1066. public void onClick (DialogInterface dialog, int id)
  1067. {
  1068. dialog.cancel();
  1069. JuceDemo.this.alertDismissed (callback, 0);
  1070. }
  1071. });
  1072. builder.create().show();
  1073. }
  1074. public final void showOkCancelBox (String title, String message, final long callback)
  1075. {
  1076. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  1077. builder.setTitle (title)
  1078. .setMessage (message)
  1079. .setCancelable (true)
  1080. .setPositiveButton ("OK", new DialogInterface.OnClickListener()
  1081. {
  1082. public void onClick (DialogInterface dialog, int id)
  1083. {
  1084. dialog.cancel();
  1085. JuceDemo.this.alertDismissed (callback, 1);
  1086. }
  1087. })
  1088. .setNegativeButton ("Cancel", new DialogInterface.OnClickListener()
  1089. {
  1090. public void onClick (DialogInterface dialog, int id)
  1091. {
  1092. dialog.cancel();
  1093. JuceDemo.this.alertDismissed (callback, 0);
  1094. }
  1095. });
  1096. builder.create().show();
  1097. }
  1098. public final void showYesNoCancelBox (String title, String message, final long callback)
  1099. {
  1100. AlertDialog.Builder builder = new AlertDialog.Builder (this);
  1101. builder.setTitle (title)
  1102. .setMessage (message)
  1103. .setCancelable (true)
  1104. .setPositiveButton ("Yes", new DialogInterface.OnClickListener()
  1105. {
  1106. public void onClick (DialogInterface dialog, int id)
  1107. {
  1108. dialog.cancel();
  1109. JuceDemo.this.alertDismissed (callback, 1);
  1110. }
  1111. })
  1112. .setNegativeButton ("No", new DialogInterface.OnClickListener()
  1113. {
  1114. public void onClick (DialogInterface dialog, int id)
  1115. {
  1116. dialog.cancel();
  1117. JuceDemo.this.alertDismissed (callback, 2);
  1118. }
  1119. })
  1120. .setNeutralButton ("Cancel", new DialogInterface.OnClickListener()
  1121. {
  1122. public void onClick (DialogInterface dialog, int id)
  1123. {
  1124. dialog.cancel();
  1125. JuceDemo.this.alertDismissed (callback, 0);
  1126. }
  1127. });
  1128. builder.create().show();
  1129. }
  1130. public native void alertDismissed (long callback, int id);
  1131. //==============================================================================
  1132. public final class ComponentPeerView extends ViewGroup
  1133. implements View.OnFocusChangeListener
  1134. {
  1135. public ComponentPeerView (Context context, boolean opaque_, long host)
  1136. {
  1137. super (context);
  1138. this.host = host;
  1139. setWillNotDraw (false);
  1140. opaque = opaque_;
  1141. setFocusable (true);
  1142. setFocusableInTouchMode (true);
  1143. setOnFocusChangeListener (this);
  1144. requestFocus();
  1145. // swap red and blue colours to match internal opengl texture format
  1146. ColorMatrix colorMatrix = new ColorMatrix();
  1147. float[] colorTransform = { 0, 0, 1.0f, 0, 0,
  1148. 0, 1.0f, 0, 0, 0,
  1149. 1.0f, 0, 0, 0, 0,
  1150. 0, 0, 0, 1.0f, 0 };
  1151. colorMatrix.set (colorTransform);
  1152. paint.setColorFilter (new ColorMatrixColorFilter (colorMatrix));
  1153. }
  1154. //==============================================================================
  1155. private native void handlePaint (long host, Canvas canvas, Paint paint);
  1156. @Override
  1157. public void onDraw (Canvas canvas)
  1158. {
  1159. handlePaint (host, canvas, paint);
  1160. }
  1161. @Override
  1162. public boolean isOpaque()
  1163. {
  1164. return opaque;
  1165. }
  1166. private boolean opaque;
  1167. private long host;
  1168. private Paint paint = new Paint();
  1169. //==============================================================================
  1170. private native void handleMouseDown (long host, int index, float x, float y, long time);
  1171. private native void handleMouseDrag (long host, int index, float x, float y, long time);
  1172. private native void handleMouseUp (long host, int index, float x, float y, long time);
  1173. @Override
  1174. public boolean onTouchEvent (MotionEvent event)
  1175. {
  1176. int action = event.getAction();
  1177. long time = event.getEventTime();
  1178. switch (action & MotionEvent.ACTION_MASK)
  1179. {
  1180. case MotionEvent.ACTION_DOWN:
  1181. handleMouseDown (host, event.getPointerId(0), event.getX(), event.getY(), time);
  1182. return true;
  1183. case MotionEvent.ACTION_CANCEL:
  1184. case MotionEvent.ACTION_UP:
  1185. handleMouseUp (host, event.getPointerId(0), event.getX(), event.getY(), time);
  1186. return true;
  1187. case MotionEvent.ACTION_MOVE:
  1188. {
  1189. int n = event.getPointerCount();
  1190. for (int i = 0; i < n; ++i)
  1191. handleMouseDrag (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  1192. return true;
  1193. }
  1194. case MotionEvent.ACTION_POINTER_UP:
  1195. {
  1196. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  1197. handleMouseUp (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  1198. return true;
  1199. }
  1200. case MotionEvent.ACTION_POINTER_DOWN:
  1201. {
  1202. int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  1203. handleMouseDown (host, event.getPointerId(i), event.getX(i), event.getY(i), time);
  1204. return true;
  1205. }
  1206. default:
  1207. break;
  1208. }
  1209. return false;
  1210. }
  1211. //==============================================================================
  1212. private native void handleKeyDown (long host, int keycode, int textchar);
  1213. private native void handleKeyUp (long host, int keycode, int textchar);
  1214. public void showKeyboard (String type)
  1215. {
  1216. InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE);
  1217. if (imm != null)
  1218. {
  1219. if (type.length() > 0)
  1220. {
  1221. imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT);
  1222. imm.setInputMethod (getWindowToken(), type);
  1223. }
  1224. else
  1225. {
  1226. imm.hideSoftInputFromWindow (getWindowToken(), 0);
  1227. }
  1228. }
  1229. }
  1230. @Override
  1231. public boolean onKeyDown (int keyCode, KeyEvent event)
  1232. {
  1233. switch (keyCode)
  1234. {
  1235. case KeyEvent.KEYCODE_VOLUME_UP:
  1236. case KeyEvent.KEYCODE_VOLUME_DOWN:
  1237. return super.onKeyDown (keyCode, event);
  1238. default: break;
  1239. }
  1240. handleKeyDown (host, keyCode, event.getUnicodeChar());
  1241. return true;
  1242. }
  1243. @Override
  1244. public boolean onKeyUp (int keyCode, KeyEvent event)
  1245. {
  1246. handleKeyUp (host, keyCode, event.getUnicodeChar());
  1247. return true;
  1248. }
  1249. @Override
  1250. public boolean onKeyMultiple (int keyCode, int count, KeyEvent event)
  1251. {
  1252. if (keyCode != KeyEvent.KEYCODE_UNKNOWN || event.getAction() != KeyEvent.ACTION_MULTIPLE)
  1253. return super.onKeyMultiple (keyCode, count, event);
  1254. if (event.getCharacters() != null)
  1255. {
  1256. int utf8Char = event.getCharacters().codePointAt (0);
  1257. handleKeyDown (host, utf8Char, utf8Char);
  1258. return true;
  1259. }
  1260. return false;
  1261. }
  1262. // this is here to make keyboard entry work on a Galaxy Tab2 10.1
  1263. @Override
  1264. public InputConnection onCreateInputConnection (EditorInfo outAttrs)
  1265. {
  1266. outAttrs.actionLabel = "";
  1267. outAttrs.hintText = "";
  1268. outAttrs.initialCapsMode = 0;
  1269. outAttrs.initialSelEnd = outAttrs.initialSelStart = -1;
  1270. outAttrs.label = "";
  1271. outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI;
  1272. outAttrs.inputType = InputType.TYPE_NULL;
  1273. return new BaseInputConnection (this, false);
  1274. }
  1275. //==============================================================================
  1276. @Override
  1277. protected void onSizeChanged (int w, int h, int oldw, int oldh)
  1278. {
  1279. super.onSizeChanged (w, h, oldw, oldh);
  1280. viewSizeChanged (host);
  1281. }
  1282. @Override
  1283. protected void onLayout (boolean changed, int left, int top, int right, int bottom)
  1284. {
  1285. for (int i = getChildCount(); --i >= 0;)
  1286. requestTransparentRegion (getChildAt (i));
  1287. }
  1288. private native void viewSizeChanged (long host);
  1289. @Override
  1290. public void onFocusChange (View v, boolean hasFocus)
  1291. {
  1292. if (v == this)
  1293. focusChanged (host, hasFocus);
  1294. }
  1295. private native void focusChanged (long host, boolean hasFocus);
  1296. public void setViewName (String newName) {}
  1297. public boolean isVisible() { return getVisibility() == VISIBLE; }
  1298. public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); }
  1299. public boolean containsPoint (int x, int y)
  1300. {
  1301. return true; //xxx needs to check overlapping views
  1302. }
  1303. }
  1304. //==============================================================================
  1305. public static class NativeSurfaceView extends SurfaceView
  1306. implements SurfaceHolder.Callback
  1307. {
  1308. private long nativeContext = 0;
  1309. NativeSurfaceView (Context context, long nativeContextPtr)
  1310. {
  1311. super (context);
  1312. nativeContext = nativeContextPtr;
  1313. }
  1314. public Surface getNativeSurface()
  1315. {
  1316. Surface retval = null;
  1317. SurfaceHolder holder = getHolder();
  1318. if (holder != null)
  1319. retval = holder.getSurface();
  1320. return retval;
  1321. }
  1322. //==============================================================================
  1323. @Override
  1324. public void surfaceChanged (SurfaceHolder holder, int format, int width, int height)
  1325. {
  1326. surfaceChangedNative (nativeContext, holder, format, width, height);
  1327. }
  1328. @Override
  1329. public void surfaceCreated (SurfaceHolder holder)
  1330. {
  1331. surfaceCreatedNative (nativeContext, holder);
  1332. }
  1333. @Override
  1334. public void surfaceDestroyed (SurfaceHolder holder)
  1335. {
  1336. surfaceDestroyedNative (nativeContext, holder);
  1337. }
  1338. @Override
  1339. protected void dispatchDraw (Canvas canvas)
  1340. {
  1341. super.dispatchDraw (canvas);
  1342. dispatchDrawNative (nativeContext, canvas);
  1343. }
  1344. //==============================================================================
  1345. @Override
  1346. protected void onAttachedToWindow ()
  1347. {
  1348. super.onAttachedToWindow();
  1349. getHolder().addCallback (this);
  1350. }
  1351. @Override
  1352. protected void onDetachedFromWindow ()
  1353. {
  1354. super.onDetachedFromWindow();
  1355. getHolder().removeCallback (this);
  1356. }
  1357. //==============================================================================
  1358. private native void dispatchDrawNative (long nativeContextPtr, Canvas canvas);
  1359. private native void surfaceCreatedNative (long nativeContextptr, SurfaceHolder holder);
  1360. private native void surfaceDestroyedNative (long nativeContextptr, SurfaceHolder holder);
  1361. private native void surfaceChangedNative (long nativeContextptr, SurfaceHolder holder,
  1362. int format, int width, int height);
  1363. }
  1364. public NativeSurfaceView createNativeSurfaceView (long nativeSurfacePtr)
  1365. {
  1366. return new NativeSurfaceView (this, nativeSurfacePtr);
  1367. }
  1368. //==============================================================================
  1369. public final int[] renderGlyph (char glyph, Paint paint, android.graphics.Matrix matrix, Rect bounds)
  1370. {
  1371. Path p = new Path();
  1372. paint.getTextPath (String.valueOf (glyph), 0, 1, 0.0f, 0.0f, p);
  1373. RectF boundsF = new RectF();
  1374. p.computeBounds (boundsF, true);
  1375. matrix.mapRect (boundsF);
  1376. boundsF.roundOut (bounds);
  1377. bounds.left--;
  1378. bounds.right++;
  1379. final int w = bounds.width();
  1380. final int h = Math.max (1, bounds.height());
  1381. Bitmap bm = Bitmap.createBitmap (w, h, Bitmap.Config.ARGB_8888);
  1382. Canvas c = new Canvas (bm);
  1383. matrix.postTranslate (-bounds.left, -bounds.top);
  1384. c.setMatrix (matrix);
  1385. c.drawPath (p, paint);
  1386. final int sizeNeeded = w * h;
  1387. if (cachedRenderArray.length < sizeNeeded)
  1388. cachedRenderArray = new int [sizeNeeded];
  1389. bm.getPixels (cachedRenderArray, 0, w, 0, 0, w, h);
  1390. bm.recycle();
  1391. return cachedRenderArray;
  1392. }
  1393. private int[] cachedRenderArray = new int [256];
  1394. //==============================================================================
  1395. public static class HTTPStream
  1396. {
  1397. public HTTPStream (HttpURLConnection connection_,
  1398. int[] statusCode, StringBuffer responseHeaders) throws IOException
  1399. {
  1400. connection = connection_;
  1401. try
  1402. {
  1403. inputStream = new BufferedInputStream (connection.getInputStream());
  1404. }
  1405. catch (IOException e)
  1406. {
  1407. if (connection.getResponseCode() < 400)
  1408. throw e;
  1409. }
  1410. finally
  1411. {
  1412. statusCode[0] = connection.getResponseCode();
  1413. }
  1414. if (statusCode[0] >= 400)
  1415. inputStream = connection.getErrorStream();
  1416. else
  1417. inputStream = connection.getInputStream();
  1418. for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet())
  1419. if (entry.getKey() != null && entry.getValue() != null)
  1420. responseHeaders.append (entry.getKey() + ": "
  1421. + android.text.TextUtils.join (",", entry.getValue()) + "\n");
  1422. }
  1423. public final void release()
  1424. {
  1425. try
  1426. {
  1427. inputStream.close();
  1428. }
  1429. catch (IOException e)
  1430. {}
  1431. connection.disconnect();
  1432. }
  1433. public final int read (byte[] buffer, int numBytes)
  1434. {
  1435. int num = 0;
  1436. try
  1437. {
  1438. num = inputStream.read (buffer, 0, numBytes);
  1439. }
  1440. catch (IOException e)
  1441. {}
  1442. if (num > 0)
  1443. position += num;
  1444. return num;
  1445. }
  1446. public final long getPosition() { return position; }
  1447. public final long getTotalLength() { return -1; }
  1448. public final boolean isExhausted() { return false; }
  1449. public final boolean setPosition (long newPos) { return false; }
  1450. private HttpURLConnection connection;
  1451. private InputStream inputStream;
  1452. private long position;
  1453. }
  1454. public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData,
  1455. String headers, int timeOutMs, int[] statusCode,
  1456. StringBuffer responseHeaders, int numRedirectsToFollow,
  1457. String httpRequestCmd)
  1458. {
  1459. // timeout parameter of zero for HttpUrlConnection is a blocking connect (negative value for juce::URL)
  1460. if (timeOutMs < 0)
  1461. timeOutMs = 0;
  1462. else if (timeOutMs == 0)
  1463. timeOutMs = 30000;
  1464. // headers - if not empty, this string is appended onto the headers that are used for the request. It must therefore be a valid set of HTML header directives, separated by newlines.
  1465. // So convert headers string to an array, with an element for each line
  1466. String headerLines[] = headers.split("\\n");
  1467. for (;;)
  1468. {
  1469. try
  1470. {
  1471. HttpURLConnection connection = (HttpURLConnection) (new URL(address).openConnection());
  1472. if (connection != null)
  1473. {
  1474. try
  1475. {
  1476. connection.setInstanceFollowRedirects (false);
  1477. connection.setConnectTimeout (timeOutMs);
  1478. connection.setReadTimeout (timeOutMs);
  1479. // Set request headers
  1480. for (int i = 0; i < headerLines.length; ++i)
  1481. {
  1482. int pos = headerLines[i].indexOf (":");
  1483. if (pos > 0 && pos < headerLines[i].length())
  1484. {
  1485. String field = headerLines[i].substring (0, pos);
  1486. String value = headerLines[i].substring (pos + 1);
  1487. if (value.length() > 0)
  1488. connection.setRequestProperty (field, value);
  1489. }
  1490. }
  1491. connection.setRequestMethod (httpRequestCmd);
  1492. if (isPost)
  1493. {
  1494. connection.setDoOutput (true);
  1495. if (postData != null)
  1496. {
  1497. OutputStream out = connection.getOutputStream();
  1498. out.write(postData);
  1499. out.flush();
  1500. }
  1501. }
  1502. HTTPStream httpStream = new HTTPStream (connection, statusCode, responseHeaders);
  1503. // Process redirect & continue as necessary
  1504. int status = statusCode[0];
  1505. if (--numRedirectsToFollow >= 0
  1506. && (status == 301 || status == 302 || status == 303 || status == 307))
  1507. {
  1508. // Assumes only one occurrence of "Location"
  1509. int pos1 = responseHeaders.indexOf ("Location:") + 10;
  1510. int pos2 = responseHeaders.indexOf ("\n", pos1);
  1511. if (pos2 > pos1)
  1512. {
  1513. String newLocation = responseHeaders.substring(pos1, pos2);
  1514. // Handle newLocation whether it's absolute or relative
  1515. URL baseUrl = new URL (address);
  1516. URL newUrl = new URL (baseUrl, newLocation);
  1517. String transformedNewLocation = newUrl.toString();
  1518. if (transformedNewLocation != address)
  1519. {
  1520. address = transformedNewLocation;
  1521. // Clear responseHeaders before next iteration
  1522. responseHeaders.delete (0, responseHeaders.length());
  1523. continue;
  1524. }
  1525. }
  1526. }
  1527. return httpStream;
  1528. }
  1529. catch (Throwable e)
  1530. {
  1531. connection.disconnect();
  1532. }
  1533. }
  1534. }
  1535. catch (Throwable e) {}
  1536. return null;
  1537. }
  1538. }
  1539. public final void launchURL (String url)
  1540. {
  1541. startActivity (new Intent (Intent.ACTION_VIEW, Uri.parse (url)));
  1542. }
  1543. public static final String getLocaleValue (boolean isRegion)
  1544. {
  1545. java.util.Locale locale = java.util.Locale.getDefault();
  1546. return isRegion ? locale.getDisplayCountry (java.util.Locale.US)
  1547. : locale.getDisplayLanguage (java.util.Locale.US);
  1548. }
  1549. private static final String getFileLocation (String type)
  1550. {
  1551. return Environment.getExternalStoragePublicDirectory (type).getAbsolutePath();
  1552. }
  1553. public static final String getDocumentsFolder() { return Environment.getDataDirectory().getAbsolutePath(); }
  1554. public static final String getPicturesFolder() { return getFileLocation (Environment.DIRECTORY_PICTURES); }
  1555. public static final String getMusicFolder() { return getFileLocation (Environment.DIRECTORY_MUSIC); }
  1556. public static final String getMoviesFolder() { return getFileLocation (Environment.DIRECTORY_MOVIES); }
  1557. public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); }
  1558. //==============================================================================
  1559. private final class SingleMediaScanner implements MediaScannerConnectionClient
  1560. {
  1561. public SingleMediaScanner (Context context, String filename)
  1562. {
  1563. file = filename;
  1564. msc = new MediaScannerConnection (context, this);
  1565. msc.connect();
  1566. }
  1567. @Override
  1568. public void onMediaScannerConnected()
  1569. {
  1570. msc.scanFile (file, null);
  1571. }
  1572. @Override
  1573. public void onScanCompleted (String path, Uri uri)
  1574. {
  1575. msc.disconnect();
  1576. }
  1577. private MediaScannerConnection msc;
  1578. private String file;
  1579. }
  1580. public final void scanFile (String filename)
  1581. {
  1582. new SingleMediaScanner (this, filename);
  1583. }
  1584. public final Typeface getTypeFaceFromAsset (String assetName)
  1585. {
  1586. try
  1587. {
  1588. return Typeface.createFromAsset (this.getResources().getAssets(), assetName);
  1589. }
  1590. catch (Throwable e) {}
  1591. return null;
  1592. }
  1593. final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
  1594. public static String bytesToHex (byte[] bytes)
  1595. {
  1596. char[] hexChars = new char[bytes.length * 2];
  1597. for (int j = 0; j < bytes.length; ++j)
  1598. {
  1599. int v = bytes[j] & 0xff;
  1600. hexChars[j * 2] = hexArray[v >>> 4];
  1601. hexChars[j * 2 + 1] = hexArray[v & 0x0f];
  1602. }
  1603. return new String (hexChars);
  1604. }
  1605. final private java.util.Map dataCache = new java.util.HashMap();
  1606. synchronized private final File getDataCacheFile (byte[] data)
  1607. {
  1608. try
  1609. {
  1610. java.security.MessageDigest digest = java.security.MessageDigest.getInstance ("MD5");
  1611. digest.update (data);
  1612. String key = bytesToHex (digest.digest());
  1613. if (dataCache.containsKey (key))
  1614. return (File) dataCache.get (key);
  1615. File f = new File (this.getCacheDir(), "bindata_" + key);
  1616. f.delete();
  1617. FileOutputStream os = new FileOutputStream (f);
  1618. os.write (data, 0, data.length);
  1619. dataCache.put (key, f);
  1620. return f;
  1621. }
  1622. catch (Throwable e) {}
  1623. return null;
  1624. }
  1625. private final void clearDataCache()
  1626. {
  1627. java.util.Iterator it = dataCache.values().iterator();
  1628. while (it.hasNext())
  1629. {
  1630. File f = (File) it.next();
  1631. f.delete();
  1632. }
  1633. }
  1634. public final Typeface getTypeFaceFromByteArray (byte[] data)
  1635. {
  1636. try
  1637. {
  1638. File f = getDataCacheFile (data);
  1639. if (f != null)
  1640. return Typeface.createFromFile (f);
  1641. }
  1642. catch (Exception e)
  1643. {
  1644. Log.e ("JUCE", e.toString());
  1645. }
  1646. return null;
  1647. }
  1648. public final int getAndroidSDKVersion()
  1649. {
  1650. return android.os.Build.VERSION.SDK_INT;
  1651. }
  1652. public final String audioManagerGetProperty (String property)
  1653. {
  1654. Object obj = getSystemService (AUDIO_SERVICE);
  1655. if (obj == null)
  1656. return null;
  1657. java.lang.reflect.Method method;
  1658. try
  1659. {
  1660. method = obj.getClass().getMethod ("getProperty", String.class);
  1661. }
  1662. catch (SecurityException e) { return null; }
  1663. catch (NoSuchMethodException e) { return null; }
  1664. if (method == null)
  1665. return null;
  1666. try
  1667. {
  1668. return (String) method.invoke (obj, property);
  1669. }
  1670. catch (java.lang.IllegalArgumentException e) {}
  1671. catch (java.lang.IllegalAccessException e) {}
  1672. catch (java.lang.reflect.InvocationTargetException e) {}
  1673. return null;
  1674. }
  1675. public final int setCurrentThreadPriority (int priority)
  1676. {
  1677. android.os.Process.setThreadPriority (android.os.Process.myTid(), priority);
  1678. return android.os.Process.getThreadPriority (android.os.Process.myTid());
  1679. }
  1680. public final boolean hasSystemFeature (String property)
  1681. {
  1682. return getPackageManager().hasSystemFeature (property);
  1683. }
  1684. private static class JuceThread extends Thread
  1685. {
  1686. public JuceThread (long host, String threadName, long threadStackSize)
  1687. {
  1688. super (null, null, threadName, threadStackSize);
  1689. _this = host;
  1690. }
  1691. public void run()
  1692. {
  1693. runThread(_this);
  1694. }
  1695. private native void runThread (long host);
  1696. private long _this;
  1697. }
  1698. public final Thread createNewThread(long host, String threadName, long threadStackSize)
  1699. {
  1700. return new JuceThread(host, threadName, threadStackSize);
  1701. }
  1702. }