| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE library.
 -    Copyright (c) 2017 - ROLI Ltd.
 - 
 -    JUCE is an open source library subject to commercial or open-source
 -    licensing.
 - 
 -    By using JUCE, you agree to the terms of both the JUCE 5 End-User License
 -    Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
 -    27th April 2017).
 - 
 -    End User License Agreement: www.juce.com/juce-5-licence
 -    Privacy Policy: www.juce.com/juce-5-privacy-policy
 - 
 -    Or: You may also use this code under the terms of the GPL v3 (see
 -    www.gnu.org/licenses).
 - 
 -    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
 -    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
 -    DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - namespace juce
 - {
 - 
 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 -  STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/roli/juce/JuceMidiSupport$BluetoothManager;")
 - 
 - DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidJuceMidiSupport, "com/roli/juce/JuceMidiSupport", 23)
 - #undef JNI_CLASS_MEMBERS
 - 
 - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
 -  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 (getBluetoothDeviceStatus, "getBluetoothDeviceStatus", "(Ljava/lang/String;)I") \
 -  METHOD (startStopScan, "startStopScan", "(Z)V")
 - 
 - DECLARE_JNI_CLASS_WITH_MIN_SDK (AndroidBluetoothManager, "com/roli/juce/JuceMidiSupport$BluetoothManager", 23)
 - #undef JNI_CLASS_MEMBERS
 - 
 - //==============================================================================
 - struct AndroidBluetoothMidiInterface
 - {
 -     static void startStopScan (bool startScanning)
 -     {
 -         JNIEnv* env = getEnv();
 -         LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
 - 
 -         if (btManager.get() != nullptr)
 -             env->CallVoidMethod (btManager.get(), AndroidBluetoothManager.startStopScan, (jboolean) (startScanning ? 1 : 0));
 -     }
 - 
 -     static StringArray getBluetoothMidiDevicesNearby()
 -     {
 -         StringArray retval;
 - 
 -         JNIEnv* env = getEnv();
 - 
 -         LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
 - 
 -         // if this is null then bluetooth is not enabled
 -         if (btManager.get() == nullptr)
 -             return {};
 - 
 -         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 (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
 -         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 (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
 - 
 -         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 (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
 - 
 -         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);
 -     }
 - 
 -     //==============================================================================
 -     enum PairStatus
 -     {
 -         unpaired = 0,
 -         paired = 1,
 -         pairing = 2
 -     };
 - 
 -     static PairStatus isBluetoothDevicePaired (const String& address)
 -     {
 -         JNIEnv* env = getEnv();
 - 
 -         LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
 - 
 -         if (btManager.get() == nullptr)
 -             return unpaired;
 - 
 -         return static_cast<PairStatus> (env->CallIntMethod (btManager.get(), AndroidBluetoothManager.getBluetoothDeviceStatus,
 -                                                             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);
 -         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) 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,
 -                         Rectangle<float> (xmargin, ymargin, deviceNameWidth - (2.0f * xmargin), height - (2.0f * ymargin)),
 -                         Justification::topLeft, true);
 - 
 -             g.setColour (getDeviceStatusFontColour (device.connectionStatus));
 -             g.drawText (statusString,
 -                         Rectangle<float> (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;
 -             switch (AndroidBluetoothMidiInterface::isBluetoothDevicePaired (*address))
 -             {
 -                 case AndroidBluetoothMidiInterface::pairing:
 -                     status = AndroidBluetoothMidiDevice::connecting;
 -                     break;
 -                 case AndroidBluetoothMidiInterface::paired:
 -                     status = AndroidBluetoothMidiDevice::connected;
 -                     break;
 -                 case AndroidBluetoothMidiInterface::unpaired:
 -                 default:
 -                     status = 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,
 -                                   const Rectangle<int>& boundsToUse)
 -         : bounds (boundsToUse)
 -     {
 -         std::unique_ptr<ModalComponentManager::Callback> exitCallback (exitCallbackToUse);
 - 
 -         AndroidBluetoothMidiInterface::startStopScan (true);
 - 
 -         setAlwaysOnTop (true);
 -         setVisible (true);
 -         addToDesktop (ComponentPeer::windowHasDropShadow);
 - 
 -         if (bounds.isEmpty())
 -             setBounds (0, 0, getParentWidth(), getParentHeight());
 -         else
 -             setBounds (bounds);
 - 
 -         toFront (true);
 -         setOpaque (! bounds.isEmpty());
 - 
 -         addAndMakeVisible (bluetoothDevicesList);
 -         enterModalState (true, exitCallback.release(), true);
 -     }
 - 
 -     ~BluetoothMidiSelectorOverlay() override
 -     {
 -         AndroidBluetoothMidiInterface::startStopScan (false);
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         g.fillAll (bounds.isEmpty() ? Colours::black.withAlpha (0.6f) : Colours::black);
 - 
 -         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:
 -     Rectangle<int> bounds;
 - 
 -     void update()
 -     {
 -         if (bounds.isEmpty())
 -             setBounds (0, 0, getParentWidth(), getParentHeight());
 -         else
 -             setBounds (bounds);
 - 
 -         bluetoothDevicesList.setBounds (getOverlayBounds().withTrimmedTop (40));
 -     }
 - 
 -     Rectangle<int> getOverlayBounds() const noexcept
 -     {
 -         if (bounds.isEmpty())
 -         {
 -             const int pw = getParentWidth();
 -             const int ph = getParentHeight();
 - 
 -             return Rectangle<int> (pw, ph).withSizeKeepingCentre (jmin (400, pw - 14),
 -                                                                   jmin (300, ph - 40));
 -         }
 - 
 -         return bounds.withZeroOrigin();
 -     }
 - 
 -     AndroidBluetoothMidiDevicesListBox bluetoothDevicesList;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BluetoothMidiSelectorOverlay)
 - };
 - 
 - //==============================================================================
 - bool BluetoothMidiDevicePairingDialogue::open (ModalComponentManager::Callback* exitCallbackPtr,
 -                                                Rectangle<int>* btBounds)
 - {
 -     std::unique_ptr<ModalComponentManager::Callback> exitCallback (exitCallbackPtr);
 - 
 -     if (getAndroidSDKVersion() < 23)
 -         return false;
 - 
 -     auto boundsToUse = (btBounds != nullptr ? *btBounds : Rectangle<int> {});
 - 
 -     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;
 -     }
 - 
 -     new BluetoothMidiSelectorOverlay (exitCallback.release(), boundsToUse);
 -     return true;
 - }
 - 
 - bool BluetoothMidiDevicePairingDialogue::isAvailable()
 - {
 -     if (getAndroidSDKVersion() < 23)
 -         return false;
 - 
 -     auto* env = getEnv();
 - 
 -     LocalRef<jobject> btManager (env->CallStaticObjectMethod (AndroidJuceMidiSupport, AndroidJuceMidiSupport.getAndroidBluetoothManager, getAppContext().get()));
 -     return btManager != nullptr;
 - }
 - 
 - } // namespace juce
 
 
  |