|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2015 - ROLI Ltd.
-
- Permission is granted to use this software under the terms of either:
- a) the GPL v2 (or any later version)
- b) the Affero GPL v3
-
- Details of these licenses can be found at: www.gnu.org/licenses
-
- JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- ------------------------------------------------------------------------------
-
- To release a closed-source product which uses JUCE, commercial licenses are
- available: visit www.juce.com for more information.
-
- ==============================================================================
- */
-
- //==============================================================================
- #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \
- METHOD (getMidiBluetoothAddresses, "getMidiBluetoothAddresses", "()[Ljava/lang/String;") \
- METHOD (pairBluetoothMidiDevice, "pairBluetoothMidiDevice", "(Ljava/lang/String;)Z") \
- METHOD (unpairBluetoothMidiDevice, "unpairBluetoothMidiDevice", "(Ljava/lang/String;)V") \
- METHOD (getHumanReadableStringForBluetoothAddress, "getHumanReadableStringForBluetoothAddress", "(Ljava/lang/String;)Ljava/lang/String;") \
- METHOD (isBluetoothDevicePaired, "isBluetoothDevicePaired", "(Ljava/lang/String;)Z")
-
- DECLARE_JNI_CLASS (AndroidBluetoothManager, JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager");
- #undef JNI_CLASS_MEMBERS
-
- //==============================================================================
- struct AndroidBluetoothMidiInterface
- {
- static StringArray getBluetoothMidiDevicesNearby()
- {
- StringArray retval;
-
- JNIEnv* env = getEnv();
-
- LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
-
- // if this is null then bluetooth is not enabled
- if (btManager.get() == nullptr)
- return StringArray();
-
- jobjectArray jDevices = (jobjectArray) env->CallObjectMethod (btManager.get(),
- AndroidBluetoothManager.getMidiBluetoothAddresses);
- LocalRef<jobjectArray> devices (jDevices);
-
- const int count = env->GetArrayLength (devices.get());
-
- for (int i = 0; i < count; ++i)
- {
- LocalRef<jstring> string ((jstring) env->GetObjectArrayElement (devices.get(), i));
- retval.add (juceString (string));
- }
-
- return retval;
- }
-
- //==============================================================================
- static bool pairBluetoothMidiDevice (const String& bluetoothAddress)
- {
- JNIEnv* env = getEnv();
-
- LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
- if (btManager.get() == nullptr)
- return false;
-
- jboolean result = env->CallBooleanMethod (btManager.get(), AndroidBluetoothManager.pairBluetoothMidiDevice,
- javaString (bluetoothAddress).get());
-
- return result;
- }
-
- static void unpairBluetoothMidiDevice (const String& bluetoothAddress)
- {
- JNIEnv* env = getEnv();
-
- LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
-
- if (btManager.get() != nullptr)
- env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.unpairBluetoothMidiDevice,
- javaString (bluetoothAddress).get());
- }
-
- //==============================================================================
- static String getHumanReadableStringForBluetoothAddress (const String& address)
- {
- JNIEnv* env = getEnv();
-
- LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
-
- if (btManager.get() == nullptr)
- return address;
-
- LocalRef<jstring> string ((jstring) env->CallObjectMethod (btManager.get(),
- AndroidBluetoothManager.getHumanReadableStringForBluetoothAddress,
- javaString (address).get()));
-
-
- if (string.get() == nullptr)
- return address;
-
- return juceString (string);
- }
-
- //==============================================================================
- static bool isBluetoothDevicePaired (const String& address)
- {
- JNIEnv* env = getEnv();
-
- LocalRef<jobject> btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
-
- if (btManager.get() == nullptr)
- return false;
-
- return env->CallBooleanMethod (btManager.get(), AndroidBluetoothManager.isBluetoothDevicePaired,
- javaString (address).get());
- }
- };
-
- //==============================================================================
- struct AndroidBluetoothMidiDevice
- {
- enum ConnectionStatus
- {
- offline,
- connected,
- disconnected,
- connecting,
- disconnecting
- };
-
- AndroidBluetoothMidiDevice (String deviceName, String address, ConnectionStatus status)
- : name (deviceName), bluetoothAddress (address), connectionStatus (status)
- {
- // can't create a device without a valid name and bluetooth address!
- jassert (! name.isEmpty());
- jassert (! bluetoothAddress.isEmpty());
- }
-
- bool operator== (const AndroidBluetoothMidiDevice& other) const noexcept
- {
- return bluetoothAddress == other.bluetoothAddress;
- }
-
- bool operator!= (const AndroidBluetoothMidiDevice& other) const noexcept
- {
- return ! operator== (other);
- }
-
- const String name, bluetoothAddress;
- ConnectionStatus connectionStatus;
- };
-
- //==============================================================================
- class AndroidBluetoothMidiDevicesListBox : public ListBox,
- private ListBoxModel,
- private Timer
- {
- public:
- //==============================================================================
- AndroidBluetoothMidiDevicesListBox()
- : timerPeriodInMs (1000)
- {
- setRowHeight (40);
- setModel (this);
- setOutlineThickness (1);
- updateDeviceList();
- startTimer (timerPeriodInMs);
- }
-
- void pairDeviceThreadFinished() // callback from PairDeviceThread
- {
- updateDeviceList();
- startTimer (timerPeriodInMs);
- }
-
- private:
- //==============================================================================
- typedef AndroidBluetoothMidiDevice::ConnectionStatus DeviceStatus;
-
- int getNumRows() override
- {
- return devices.size();
- }
-
- void paintListBoxItem (int rowNumber, Graphics& g,
- int width, int height, bool rowIsSelected) override
- {
- if (isPositiveAndBelow (rowNumber, devices.size()))
- {
- const AndroidBluetoothMidiDevice& device = devices.getReference (rowNumber);
- const String statusString (getDeviceStatusString (device.connectionStatus));
-
- g.fillAll (Colours::white);
-
- const float xmargin = 3.0f;
- const float ymargin = 3.0f;
- const float fontHeight = 0.4f * height;
- const float deviceNameWidth = 0.6f * width;
-
- g.setFont (fontHeight);
-
- g.setColour (getDeviceNameFontColour (device.connectionStatus));
- g.drawText (device.name,
- xmargin, ymargin,
- deviceNameWidth - (2.0f * xmargin), height - (2.0f * ymargin),
- Justification::topLeft, true);
-
- g.setColour (getDeviceStatusFontColour (device.connectionStatus));
- g.drawText (statusString,
- deviceNameWidth + xmargin, ymargin,
- width - deviceNameWidth - (2.0f * xmargin), height - (2.0f * ymargin),
- Justification::topRight, true);
-
- g.setColour (Colours::grey);
- g.drawHorizontalLine (height - 1, xmargin, width);
- }
- }
-
- //==============================================================================
- static Colour getDeviceNameFontColour (DeviceStatus deviceStatus) noexcept
- {
- if (deviceStatus == AndroidBluetoothMidiDevice::offline)
- return Colours::grey;
-
- return Colours::black;
- }
-
- static Colour getDeviceStatusFontColour (DeviceStatus deviceStatus) noexcept
- {
- if (deviceStatus == AndroidBluetoothMidiDevice::offline
- || deviceStatus == AndroidBluetoothMidiDevice::connecting
- || deviceStatus == AndroidBluetoothMidiDevice::disconnecting)
- return Colours::grey;
-
- if (deviceStatus == AndroidBluetoothMidiDevice::connected)
- return Colours::green;
-
- return Colours::black;
- }
-
- static String getDeviceStatusString (DeviceStatus deviceStatus) noexcept
- {
- if (deviceStatus == AndroidBluetoothMidiDevice::offline) return "Offline";
- if (deviceStatus == AndroidBluetoothMidiDevice::connected) return "Connected";
- if (deviceStatus == AndroidBluetoothMidiDevice::disconnected) return "Not connected";
- if (deviceStatus == AndroidBluetoothMidiDevice::connecting) return "Connecting...";
- if (deviceStatus == AndroidBluetoothMidiDevice::disconnecting) return "Disconnecting...";
-
- // unknown device state!
- jassertfalse;
- return "Status unknown";
- }
-
- //==============================================================================
- void listBoxItemClicked (int row, const MouseEvent&) override
- {
- const AndroidBluetoothMidiDevice& device = devices.getReference (row);
-
- if (device.connectionStatus == AndroidBluetoothMidiDevice::disconnected)
- disconnectedDeviceClicked (row);
-
- else if (device.connectionStatus == AndroidBluetoothMidiDevice::connected)
- connectedDeviceClicked (row);
- }
-
- void timerCallback() override
- {
- updateDeviceList();
- }
-
- //==============================================================================
- struct PairDeviceThread : public Thread,
- private AsyncUpdater
- {
- PairDeviceThread (const String& bluetoothAddressOfDeviceToPair,
- AndroidBluetoothMidiDevicesListBox& ownerListBox)
- : Thread ("JUCE Bluetooth MIDI Device Pairing Thread"),
- bluetoothAddress (bluetoothAddressOfDeviceToPair),
- owner (&ownerListBox)
- {
- startThread();
- }
-
- void run() override
- {
- AndroidBluetoothMidiInterface::pairBluetoothMidiDevice (bluetoothAddress);
- triggerAsyncUpdate();
- }
-
- void handleAsyncUpdate() override
- {
- if (owner != nullptr)
- owner->pairDeviceThreadFinished();
-
- delete this;
- }
-
- private:
- String bluetoothAddress;
- Component::SafePointer<AndroidBluetoothMidiDevicesListBox> owner;
- };
-
- //==============================================================================
- void disconnectedDeviceClicked (int row)
- {
- stopTimer();
-
- AndroidBluetoothMidiDevice& device = devices.getReference (row);
- device.connectionStatus = AndroidBluetoothMidiDevice::connecting;
- updateContent();
- repaint();
-
- new PairDeviceThread (device.bluetoothAddress, *this);
- }
-
- void connectedDeviceClicked (int row)
- {
- AndroidBluetoothMidiDevice& device = devices.getReference (row);
- device.connectionStatus = AndroidBluetoothMidiDevice::disconnecting;
- updateContent();
- repaint();
- AndroidBluetoothMidiInterface::unpairBluetoothMidiDevice (device.bluetoothAddress);
- }
-
- //==============================================================================
- void updateDeviceList()
- {
- StringArray bluetoothAddresses = AndroidBluetoothMidiInterface::getBluetoothMidiDevicesNearby();
-
- Array<AndroidBluetoothMidiDevice> newDevices;
-
- for (String* address = bluetoothAddresses.begin();
- address != bluetoothAddresses.end(); ++address)
- {
- String name = AndroidBluetoothMidiInterface::getHumanReadableStringForBluetoothAddress (*address);
- DeviceStatus status = AndroidBluetoothMidiInterface::isBluetoothDevicePaired (*address)
- ? AndroidBluetoothMidiDevice::connected
- : AndroidBluetoothMidiDevice::disconnected;
-
- newDevices.add (AndroidBluetoothMidiDevice (name, *address, status));
- }
-
- devices.swapWith (newDevices);
- updateContent();
- repaint();
- }
-
- Array<AndroidBluetoothMidiDevice> devices;
- const int timerPeriodInMs;
- };
-
- //==============================================================================
- class BluetoothMidiSelectorOverlay : public Component
- {
- public:
- BluetoothMidiSelectorOverlay (ModalComponentManager::Callback* exitCallbackToUse)
- {
- ScopedPointer<ModalComponentManager::Callback> exitCallback (exitCallbackToUse);
-
- setAlwaysOnTop (true);
- setVisible (true);
- addToDesktop (ComponentPeer::windowHasDropShadow);
- setBounds (0, 0, getParentWidth(), getParentHeight());
- toFront (true);
-
- addAndMakeVisible (bluetoothDevicesList);
- enterModalState (true, exitCallback.release(), true);
- }
-
- void paint (Graphics& g) override
- {
- g.fillAll (Colours::black.withAlpha (0.6f));
-
- g.setColour (Colour (0xffdfdfdf));
- Rectangle<int> overlayBounds = getOverlayBounds();
- g.fillRect (overlayBounds);
-
- g.setColour (Colours::black);
- g.setFont (16);
- g.drawText ("Bluetooth MIDI Devices",
- overlayBounds.removeFromTop (20).reduced (3, 3),
- Justification::topLeft, true);
-
- overlayBounds.removeFromTop (2);
-
- g.setFont (12);
- g.drawText ("tap to connect/disconnect",
- overlayBounds.removeFromTop (18).reduced (3, 3),
- Justification::topLeft, true);
- }
-
- void inputAttemptWhenModal() override { exitModalState (0); }
- void mouseDrag (const MouseEvent&) override {}
- void mouseDown (const MouseEvent&) override { exitModalState (0); }
- void resized() override { update(); }
- void parentSizeChanged() override { update(); }
-
- private:
- void update()
- {
- setBounds (0, 0, getParentWidth(), getParentHeight());
- bluetoothDevicesList.setBounds (getOverlayBounds().withTrimmedTop (40));
- }
-
- Rectangle<int> getOverlayBounds() const noexcept
- {
- const int pw = getParentWidth();
- const int ph = getParentHeight();
-
- return Rectangle<int> (pw, ph).withSizeKeepingCentre (jmin (400, pw - 14),
- jmin (300, ph - 40));
- }
-
- AndroidBluetoothMidiDevicesListBox bluetoothDevicesList;
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorOverlay)
- };
-
- //==============================================================================
- bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallbackPtr)
- {
- ScopedPointer<ModalComponentManager::Callback> exitCallback (exitCallbackPtr);
-
- if (! RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
- {
- // If you hit this assert, you probably forgot to get RuntimePermissions::bluetoothMidi.
- // This is not going to work, boo! The pairing dialogue won't be able to scan for or
- // find any devices, it will just display an empty list, so don't bother opening it.
- jassertfalse;
- return false;
- }
-
- BluetoothMidiSelectorOverlay* overlay = new BluetoothMidiSelectorOverlay (exitCallback.release());
- return true;
- }
-
- bool BluetoothMidiDevicePairingDialogue::isAvailable()
- {
- jobject btManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidBluetoothManager));
- return btManager != nullptr;
- }
|