// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only

#include <QtCore/QLoggingCategory>
#include <QtCore/QRandomGenerator>
#include <QtDBus/QDBusContext>

#include "qbluetoothlocaldevice.h"
#include "qbluetoothaddress.h"
#include "qbluetoothlocaldevice_p.h"

#include "bluez/bluez5_helper_p.h"
#include "bluez/objectmanager_p.h"
#include "bluez/properties_p.h"
#include "bluez/adapter1_bluez5_p.h"
#include "bluez/device1_bluez5_p.h"

QT_BEGIN_NAMESPACE

Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ)

QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) :
    QObject(parent),
    d_ptr(new QBluetoothLocalDevicePrivate(this))
{
    d_ptr->currentMode = hostMode();
}

QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) :
    QObject(parent),
    d_ptr(new QBluetoothLocalDevicePrivate(this, address))
{
    d_ptr->currentMode = hostMode();
}

QString QBluetoothLocalDevice::name() const
{
    if (d_ptr->adapter)
        return d_ptr->adapter->alias();

    return QString();
}

QBluetoothAddress QBluetoothLocalDevice::address() const
{
    if (d_ptr->adapter)
        return QBluetoothAddress(d_ptr->adapter->address());

    return QBluetoothAddress();
}

void QBluetoothLocalDevice::powerOn()
{
    if (d_ptr->adapter)
        d_ptr->adapter->setPowered(true);
}

void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode mode)
{
    if (!isValid())
        return;

    Q_D(QBluetoothLocalDevice);

    if (d->pendingHostModeChange != -1) {
        qCWarning(QT_BT_BLUEZ) << "setHostMode() ignored due to already pending mode change";
        return;
    }

    switch (mode) {
    case HostDiscoverableLimitedInquiry:
    case HostDiscoverable:
        if (hostMode() == HostPoweredOff) {
            // We first have to wait for BT to be powered on,
            // then we can set the host mode correctly
            d->pendingHostModeChange = static_cast<int>(HostDiscoverable);
            d->adapter->setPowered(true);
        } else {
            d->adapter->setDiscoverable(true);
        }
        break;
    case HostConnectable:
        if (hostMode() == HostPoweredOff) {
            d->pendingHostModeChange = static_cast<int>(HostConnectable);
            d->adapter->setPowered(true);
        } else {
            d->adapter->setDiscoverable(false);
        }
        break;
    case HostPoweredOff:
        d->adapter->setPowered(false);
        break;
    }
}

QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const
{
    if (d_ptr->adapter) {
        if (!d_ptr->adapter->powered())
            return HostPoweredOff;
        else if (d_ptr->adapter->discoverable())
            return HostDiscoverable;
        else if (d_ptr->adapter->powered())
            return HostConnectable;
    }

    return HostPoweredOff;
}

QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const
{
    return d_ptr->connectedDevices();
}

QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices()
{
    QList<QBluetoothHostInfo> localDevices;

    initializeBluez5();
    OrgFreedesktopDBusObjectManagerInterface manager(
            QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus());
    QDBusPendingReply<ManagedObjectList> reply = manager.GetManagedObjects();
    reply.waitForFinished();
    if (reply.isError())
        return localDevices;

    ManagedObjectList managedObjectList = reply.value();
    for (ManagedObjectList::const_iterator it = managedObjectList.constBegin();
         it != managedObjectList.constEnd(); ++it) {
        const InterfaceList &ifaceList = it.value();

        for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd();
             ++jt) {
            const QString &iface = jt.key();
            const QVariantMap &ifaceValues = jt.value();

            if (iface == QStringLiteral("org.bluez.Adapter1")) {
                QBluetoothHostInfo hostInfo;
                const QString temp = ifaceValues.value(QStringLiteral("Address")).toString();

                hostInfo.setAddress(QBluetoothAddress(temp));
                if (hostInfo.address().isNull())
                    continue;
                hostInfo.setName(ifaceValues.value(QStringLiteral("Name")).toString());
                localDevices.append(hostInfo);
            }
        }
    }
    return localDevices;
}

void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing)
{
    if (!isValid() || address.isNull()) {
        QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
                                  Q_ARG(QBluetoothLocalDevice::Error,
                                        QBluetoothLocalDevice::PairingError));
        return;
    }

    const Pairing current_pairing = pairingStatus(address);
    if (current_pairing == pairing) {
        if (d_ptr->adapter) {
            // A possibly running discovery or pending pairing request should be canceled
            if (d_ptr->pairingDiscoveryTimer && d_ptr->pairingDiscoveryTimer->isActive()) {
                d_ptr->pairingDiscoveryTimer->stop();
            }

            if (d_ptr->pairingTarget) {
                qCDebug(QT_BT_BLUEZ) << "Cancelling pending pairing request to" << d_ptr->pairingTarget->address();
                QDBusPendingReply<> cancelReply = d_ptr->pairingTarget->CancelPairing();
                cancelReply.waitForFinished();
                delete d_ptr->pairingTarget;
                d_ptr->pairingTarget = nullptr;
            }
        }
        QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection,
                                  Q_ARG(QBluetoothAddress, address),
                                  Q_ARG(QBluetoothLocalDevice::Pairing, pairing));
        return;
    }

    d_ptr->requestPairing(address, pairing);
}

void QBluetoothLocalDevicePrivate::requestPairing(const QBluetoothAddress &targetAddress,
                                                  QBluetoothLocalDevice::Pairing targetPairing)
{
    if (!isValid())
        return;

    //are we already discovering something? -> abort those attempts
    if (pairingDiscoveryTimer && pairingDiscoveryTimer->isActive()) {
        pairingDiscoveryTimer->stop();
        QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(adapter->path());
    }

    if (pairingTarget) {
        delete pairingTarget;
        pairingTarget = nullptr;
    }

    // pairing implies that the device was found
    // if we cannot find it we may have to turn on Discovery mode for a limited amount of time

    // check device doesn't already exist
    QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
    reply.waitForFinished();
    if (reply.isError()) {
        emit q_ptr->errorOccurred(QBluetoothLocalDevice::PairingError);
        return;
    }

    ManagedObjectList managedObjectList = reply.value();
    for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
        const QDBusObjectPath &path = it.key();
        const InterfaceList &ifaceList = it.value();

        for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
            const QString &iface = jt.key();

            if (iface == QStringLiteral("org.bluez.Device1")) {

                OrgBluezDevice1Interface device(QStringLiteral("org.bluez"),
                                                path.path(),
                                                QDBusConnection::systemBus());
                if (targetAddress == QBluetoothAddress(device.address())) {
                    qCDebug(QT_BT_BLUEZ) << "Initiating direct pair to" << targetAddress.toString();
                    //device exist -> directly work with it
                    processPairing(path.path(), targetPairing);
                    return;
                }
            }
        }
    }

    //no device matching -> turn on discovery
    QtBluezDiscoveryManager::instance()->registerDiscoveryInterest(adapter->path());

    address = targetAddress;
    pairing = targetPairing;
    if (!pairingDiscoveryTimer) {
        pairingDiscoveryTimer = new QTimer(this);
        pairingDiscoveryTimer->setSingleShot(true);
        pairingDiscoveryTimer->setInterval(20000); //20s
        connect(pairingDiscoveryTimer, &QTimer::timeout,
                this, &QBluetoothLocalDevicePrivate::pairingDiscoveryTimedOut);
    }

    qCDebug(QT_BT_BLUEZ) << "Initiating discovery for pairing on" << targetAddress.toString();
    pairingDiscoveryTimer->start();
}

/*!
 * \internal
 *
 * Found a matching device. Now we must ensure its pairing/trusted state is as desired.
 * If it has to be paired then we need another roundtrip through the event loop
 * while we wait for the user to accept the pairing dialogs.
 */
void QBluetoothLocalDevicePrivate::processPairing(const QString &objectPath,
                                                  QBluetoothLocalDevice::Pairing target)
{
    if (pairingTarget)
        delete pairingTarget;

    //stop possibly running discovery
    if (pairingDiscoveryTimer && pairingDiscoveryTimer->isActive()) {
        pairingDiscoveryTimer->stop();

        QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(adapter->path());
    }

    pairingTarget = new OrgBluezDevice1Interface(QStringLiteral("org.bluez"), objectPath,
                                                 QDBusConnection::systemBus(), this);
    const QBluetoothAddress targetAddress(pairingTarget->address());

    Q_Q(QBluetoothLocalDevice);

    switch (target) {
    case QBluetoothLocalDevice::Unpaired: {
        delete pairingTarget;
        pairingTarget = nullptr;

        QDBusPendingReply<> removeReply = adapter->RemoveDevice(QDBusObjectPath(objectPath));
        auto watcher = new QDBusPendingCallWatcher(removeReply, this);
        connect(watcher, &QDBusPendingCallWatcher::finished,
                this, [q, targetAddress](QDBusPendingCallWatcher* watcher){
            QDBusPendingReply<> reply = *watcher;
            if (reply.isError())
                emit q->errorOccurred(QBluetoothLocalDevice::PairingError);
            else
                emit q->pairingFinished(targetAddress, QBluetoothLocalDevice::Unpaired);

            watcher->deleteLater();
        });
        break;
    }
    case QBluetoothLocalDevice::Paired:
    case QBluetoothLocalDevice::AuthorizedPaired:
        pairing = target;

        if (!pairingTarget->paired()) {
            qCDebug(QT_BT_BLUEZ) << "Sending pairing request to" << pairingTarget->address();
            //initiate the pairing
            QDBusPendingReply<> pairReply = pairingTarget->Pair();
            QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pairReply, this);
            connect(watcher, &QDBusPendingCallWatcher::finished,
                    this, &QBluetoothLocalDevicePrivate::pairingCompleted);
            return;
        }

        //already paired but Trust level must be adjusted
        if (target == QBluetoothLocalDevice::AuthorizedPaired && !pairingTarget->trusted())
            pairingTarget->setTrusted(true);
        else if (target == QBluetoothLocalDevice::Paired && pairingTarget->trusted())
            pairingTarget->setTrusted(false);

        delete pairingTarget;
        pairingTarget = nullptr;

        emit q->pairingFinished(targetAddress, target);

        break;
    default:
        break;
    }
}

void QBluetoothLocalDevicePrivate::pairingDiscoveryTimedOut()
{
    qCWarning(QT_BT_BLUEZ) << "Discovery for pairing purposes failed. Cannot find parable device.";

    QtBluezDiscoveryManager::instance()->unregisterDiscoveryInterest(adapter->path());

    emit q_ptr->errorOccurred(QBluetoothLocalDevice::PairingError);
}

QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(
    const QBluetoothAddress &address) const
{
    if (address.isNull())
        return Unpaired;

    if (isValid())
    {
        QDBusPendingReply<ManagedObjectList> reply = d_ptr->manager->GetManagedObjects();
        reply.waitForFinished();
        if (reply.isError())
            return Unpaired;

        ManagedObjectList managedObjectList = reply.value();
        for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
            const QDBusObjectPath &path = it.key();
            const InterfaceList &ifaceList = it.value();

            for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
                const QString &iface = jt.key();

                if (iface == QStringLiteral("org.bluez.Device1")) {

                    OrgBluezDevice1Interface device(QStringLiteral("org.bluez"),
                                                    path.path(),
                                                    QDBusConnection::systemBus());

                    if (address == QBluetoothAddress(device.address())) {
                        if (device.trusted() && device.paired())
                            return AuthorizedPaired;
                        else if (device.paired())
                            return Paired;
                        else
                            return Unpaired;
                    }
                }
            }
        }
    }

    return Unpaired;
}

QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate(QBluetoothLocalDevice *q,
                                                           QBluetoothAddress address) :
        localAddress(address),
        pendingHostModeChange(-1),
        q_ptr(q)
{
    registerQBluetoothLocalDeviceMetaType();
    initializeBluez5();
    initializeAdapter();

    connectDeviceChanges();
}

bool objectPathIsForThisDevice(const QString &adapterPath, const QString &objectPath)
{
    return (!adapterPath.isEmpty() && objectPath.startsWith(adapterPath));
}

void QBluetoothLocalDevicePrivate::connectDeviceChanges()
{
    if (isValid()) {
        //setup property change notifications for all existing devices
        QDBusPendingReply<ManagedObjectList> reply = manager->GetManagedObjects();
        reply.waitForFinished();
        if (reply.isError())
            return;

        OrgFreedesktopDBusPropertiesInterface *monitor = nullptr;

        ManagedObjectList managedObjectList = reply.value();
        for (ManagedObjectList::const_iterator it = managedObjectList.constBegin(); it != managedObjectList.constEnd(); ++it) {
            const QDBusObjectPath &path = it.key();
            const InterfaceList &ifaceList = it.value();

            // don't track connected devices from other adapters but the current
            if (!objectPathIsForThisDevice(deviceAdapterPath, path.path()))
                continue;

            for (InterfaceList::const_iterator jt = ifaceList.constBegin(); jt != ifaceList.constEnd(); ++jt) {
                const QString &iface = jt.key();
                const QVariantMap &ifaceValues = jt.value();

                if (iface == QStringLiteral("org.bluez.Device1")) {
                    monitor = new OrgFreedesktopDBusPropertiesInterface(QStringLiteral("org.bluez"),
                                                                        path.path(),
                                                                        QDBusConnection::systemBus(), this);
                    connect(monitor, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
                            this, &QBluetoothLocalDevicePrivate::PropertiesChanged);
                    deviceChangeMonitors.insert(path.path(), monitor);

                    if (ifaceValues.value(QStringLiteral("Connected"), false).toBool()) {
                        QBluetoothAddress address(ifaceValues.value(QStringLiteral("Address")).toString());
                        connectedDevicesSet.insert(address);
                    }
                }
            }
        }
    }
}

QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate()
{
    delete adapter;
    delete adapterProperties;
    delete manager;
    delete pairingTarget;

    qDeleteAll(deviceChangeMonitors);
}

void QBluetoothLocalDevicePrivate::initializeAdapter()
{
    if (adapter)
        return;

    //get all local adapters
    if (!manager)
        manager = new OrgFreedesktopDBusObjectManagerInterface(
                                                     QStringLiteral("org.bluez"),
                                                     QStringLiteral("/"),
                                                     QDBusConnection::systemBus(), this);

    connect(manager, &OrgFreedesktopDBusObjectManagerInterface::InterfacesAdded,
            this, &QBluetoothLocalDevicePrivate::InterfacesAdded);
    connect(manager, &OrgFreedesktopDBusObjectManagerInterface::InterfacesRemoved,
            this, &QBluetoothLocalDevicePrivate::InterfacesRemoved);

    bool ok = true;
    const QString adapterPath = findAdapterForAddress(localAddress, &ok);
    if (!ok || adapterPath.isEmpty())
        return;

    deviceAdapterPath = adapterPath;
    adapter = new OrgBluezAdapter1Interface(QStringLiteral("org.bluez"), adapterPath,
                                            QDBusConnection::systemBus(), this);

    if (adapter) {
        //hook up propertiesChanged for current adapter
        adapterProperties = new OrgFreedesktopDBusPropertiesInterface(
                QStringLiteral("org.bluez"), adapter->path(),
                QDBusConnection::systemBus(), this);
        connect(adapterProperties, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
                this, &QBluetoothLocalDevicePrivate::PropertiesChanged);
    }
}

void QBluetoothLocalDevicePrivate::PropertiesChanged(const QString &interface,
                                                     const QVariantMap &changed_properties,
                                                     const QStringList &/*invalidated_properties*/,
                                                     const QDBusMessage &)
{
    //qDebug() << "Change" << interface << changed_properties;
    if (interface == QStringLiteral("org.bluez.Adapter1")) {
        //update host mode
        if (changed_properties.contains(QStringLiteral("Discoverable"))
                || changed_properties.contains(QStringLiteral("Powered"))) {

            QBluetoothLocalDevice::HostMode mode;

            if (!adapter->powered()) {
                mode = QBluetoothLocalDevice::HostPoweredOff;
            } else {
                if (adapter->discoverable())
                    mode = QBluetoothLocalDevice::HostDiscoverable;
                else
                    mode = QBluetoothLocalDevice::HostConnectable;

                if (pendingHostModeChange != -1) {

                    if (static_cast<int>(mode) != pendingHostModeChange) {
                        adapter->setDiscoverable(
                                pendingHostModeChange
                                == static_cast<int>(QBluetoothLocalDevice::HostDiscoverable));
                        pendingHostModeChange = -1;
                        return;
                    }
                    pendingHostModeChange = -1;
                }
            }

            if (mode != currentMode)
                emit q_ptr->hostModeStateChanged(mode);

            currentMode = mode;
        }
    } else if (interface == QStringLiteral("org.bluez.Device1")
               && changed_properties.contains(QStringLiteral("Connected"))) {
        // update list of connected devices
        OrgFreedesktopDBusPropertiesInterface *senderIface =
                qobject_cast<OrgFreedesktopDBusPropertiesInterface*>(sender());
        if (!senderIface)
            return;

        const QString currentPath = senderIface->path();
        bool isConnected = changed_properties.value(QStringLiteral("Connected"), false).toBool();
        OrgBluezDevice1Interface device(QStringLiteral("org.bluez"), currentPath,
                                        QDBusConnection::systemBus());
        const QBluetoothAddress changedAddress(device.address());
        bool isInSet = connectedDevicesSet.contains(changedAddress);
        if (isConnected && !isInSet) {
            connectedDevicesSet.insert(changedAddress);
            emit q_ptr->deviceConnected(changedAddress);
        } else if (!isConnected && isInSet) {
            connectedDevicesSet.remove(changedAddress);
            emit q_ptr->deviceDisconnected(changedAddress);
        }
    }
}

void QBluetoothLocalDevicePrivate::InterfacesAdded(const QDBusObjectPath &object_path, InterfaceList interfaces_and_properties)
{
    if (interfaces_and_properties.contains(QStringLiteral("org.bluez.Device1"))
        && !deviceChangeMonitors.contains(object_path.path())) {
        // a new device was added which we need to add to list of known devices

        if (objectPathIsForThisDevice(deviceAdapterPath, object_path.path())) {
            OrgFreedesktopDBusPropertiesInterface *monitor = new OrgFreedesktopDBusPropertiesInterface(
                                               QStringLiteral("org.bluez"),
                                               object_path.path(),
                                               QDBusConnection::systemBus());
            connect(monitor, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
                    this, &QBluetoothLocalDevicePrivate::PropertiesChanged);
            deviceChangeMonitors.insert(object_path.path(), monitor);

            const QVariantMap ifaceValues = interfaces_and_properties.value(QStringLiteral("org.bluez.Device1"));
            if (ifaceValues.value(QStringLiteral("Connected"), false).toBool()) {
                QBluetoothAddress address(ifaceValues.value(QStringLiteral("Address")).toString());
                connectedDevicesSet.insert(address);
                emit q_ptr->deviceConnected(address);
            }
        }
    }

    if (pairingDiscoveryTimer && pairingDiscoveryTimer->isActive()
        && interfaces_and_properties.contains(QStringLiteral("org.bluez.Device1"))) {
        //device discovery for pairing found new remote device
        OrgBluezDevice1Interface device(QStringLiteral("org.bluez"),
                                        object_path.path(), QDBusConnection::systemBus());
        if (!address.isNull() && address == QBluetoothAddress(device.address()))
            processPairing(object_path.path(), pairing);
    }
}

void QBluetoothLocalDevicePrivate::InterfacesRemoved(const QDBusObjectPath &object_path,
                                                     const QStringList &interfaces)
{
    if (deviceChangeMonitors.contains(object_path.path())
            && interfaces.contains(QLatin1String("org.bluez.Device1"))) {

        if (objectPathIsForThisDevice(deviceAdapterPath, object_path.path())) {
            //a device was removed
            delete deviceChangeMonitors.take(object_path.path());

            //the path contains the address (e.g.: /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX)
            //-> use it to update current list of connected devices
            QString addressString = object_path.path().right(17);
            addressString.replace(QStringLiteral("_"), QStringLiteral(":"));
            const QBluetoothAddress address(addressString);
            bool found = connectedDevicesSet.remove(address);
            if (found)
                emit q_ptr->deviceDisconnected(address);
        }
    }

    if (adapter && object_path.path() == adapter->path()
        && interfaces.contains(QLatin1String("org.bluez.Adapter1"))) {
        qCDebug(QT_BT_BLUEZ) << "Adapter" << adapter->path() << "was removed";
        // current adapter was removed -> invalidate the instance
        delete adapter;
        adapter = nullptr;
        manager->deleteLater();
        manager = nullptr;
        delete adapterProperties;
        adapterProperties = nullptr;

        delete pairingTarget;
        pairingTarget = nullptr;

        // turn  off connectivity monitoring
        qDeleteAll(deviceChangeMonitors);
        deviceChangeMonitors.clear();
        connectedDevicesSet.clear();
    }
}

bool QBluetoothLocalDevicePrivate::isValid() const
{
    return adapter && manager;
}

QList<QBluetoothAddress> QBluetoothLocalDevicePrivate::connectedDevices() const
{
    return connectedDevicesSet.values();
}

void QBluetoothLocalDevicePrivate::pairingCompleted(QDBusPendingCallWatcher *watcher)
{
    Q_Q(QBluetoothLocalDevice);
    QDBusPendingReply<> reply = *watcher;

    if (reply.isError()) {
        qCWarning(QT_BT_BLUEZ) << "Failed to create pairing" << reply.error().name();
        if (reply.error().name() != QStringLiteral("org.bluez.Error.AuthenticationCanceled"))
            emit q->errorOccurred(QBluetoothLocalDevice::PairingError);
        watcher->deleteLater();
        return;
    }

    if (adapter) {
        if (!pairingTarget) {
            qCWarning(QT_BT_BLUEZ) << "Pairing target expected but found null pointer.";
            emit q->errorOccurred(QBluetoothLocalDevice::PairingError);
            watcher->deleteLater();
            return;
        }

        if (!pairingTarget->paired()) {
            qCWarning(QT_BT_BLUEZ) << "Device was not paired as requested";
            emit q->errorOccurred(QBluetoothLocalDevice::PairingError);
            watcher->deleteLater();
            return;
        }

        const QBluetoothAddress targetAddress(pairingTarget->address());

        if (pairing == QBluetoothLocalDevice::AuthorizedPaired && !pairingTarget->trusted())
            pairingTarget->setTrusted(true);
        else if (pairing == QBluetoothLocalDevice::Paired && pairingTarget->trusted())
            pairingTarget->setTrusted(false);

        delete pairingTarget;
        pairingTarget = nullptr;

        emit q->pairingFinished(targetAddress, pairing);
    }

    watcher->deleteLater();
}

QT_END_NAMESPACE

#include "moc_qbluetoothlocaldevice_p.cpp"
