diff options
author | Alex Blasche <alexander.blasche@digia.com> | 2014-02-10 15:37:17 +0100 |
---|---|---|
committer | The Qt Project <gerrit-noreply@qt-project.org> | 2014-02-13 09:47:27 +0100 |
commit | 173d16efb54ccc152f19afb9b1c2a87915fb414b (patch) | |
tree | f07ce85ba2cb973e3c08f3ed84252d92ee1c16de /src/bluetooth/qbluetoothlocaldevice_android.cpp | |
parent | dd75b1f776695006ca96fd43f995c3ba0549b968 (diff) |
Port QtBluetooth to Android
This is a feature merge to dev targeting Qt 5.3.
Known issues:
-QTBUG-36754: QBluetoothServer::close() crashes
-QTBUG-36763: QBluetothTransferManager port to Android not possible
-QTBUG-36764: Improve QBluetoothLocalDevice::connectedDevices()
-QTBUG-36810: Remove direct use of Android action strings
The above issues and some other minor TODO's will be addressed
until final release time.
Task-number: QTBUG-33792
[ChangeLog][QtBluetooth][Android] QtBluetooth has been ported to
Android.
Change-Id: I31ba83e3b7d6aa68e7258b7e43235de7d1a6e68a
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
Diffstat (limited to 'src/bluetooth/qbluetoothlocaldevice_android.cpp')
-rw-r--r-- | src/bluetooth/qbluetoothlocaldevice_android.cpp | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/src/bluetooth/qbluetoothlocaldevice_android.cpp b/src/bluetooth/qbluetoothlocaldevice_android.cpp new file mode 100644 index 00000000..4e441bc2 --- /dev/null +++ b/src/bluetooth/qbluetoothlocaldevice_android.cpp @@ -0,0 +1,443 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Lauri Laanmets (Proekspert AS) <lauri.laanmets@eesti.ee> +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QLoggingCategory> +#include <QtCore/private/qjnihelpers_p.h> +#include <QtAndroidExtras/QAndroidJniEnvironment> +#include <QtAndroidExtras/QAndroidJniObject> +#include <QtBluetooth/QBluetoothLocalDevice> +#include <QtBluetooth/QBluetoothAddress> + +#include "qbluetoothlocaldevice_p.h" +#include "android/localdevicebroadcastreceiver_p.h" + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_ANDROID) + +QBluetoothLocalDevicePrivate::QBluetoothLocalDevicePrivate( + QBluetoothLocalDevice *q, const QBluetoothAddress &address) + : q_ptr(q), obj(0), pendingHostModeTransition(false) +{ + initialize(address); + + receiver = new LocalDeviceBroadcastReceiver(q_ptr); + QObject::connect(receiver, SIGNAL(hostModeStateChanged(QBluetoothLocalDevice::HostMode)), + this, SLOT(processHostModeChange(QBluetoothLocalDevice::HostMode))); + QObject::connect(receiver, SIGNAL(pairingStateChanged(QBluetoothAddress,QBluetoothLocalDevice::Pairing)), + this, SLOT(processPairingStateChanged(QBluetoothAddress,QBluetoothLocalDevice::Pairing))); + QObject::connect(receiver, SIGNAL(connectDeviceChanges(QBluetoothAddress,bool)), + this, SLOT(processConnectDeviceChanges(QBluetoothAddress,bool))); + QObject::connect(receiver, SIGNAL(pairingDisplayConfirmation(QBluetoothAddress,QString)), + this, SLOT(processDisplayConfirmation(QBluetoothAddress,QString))); +} + + +QBluetoothLocalDevicePrivate::~QBluetoothLocalDevicePrivate() +{ + delete receiver; + delete obj; +} + +QAndroidJniObject *QBluetoothLocalDevicePrivate::adapter() +{ + return obj; +} + +void QBluetoothLocalDevicePrivate::initialize(const QBluetoothAddress &address) +{ + QAndroidJniEnvironment env; + + jclass btAdapterClass = env->FindClass("android/bluetooth/BluetoothAdapter"); + if (btAdapterClass == NULL) { + qCWarning(QT_BT_ANDROID) << "Native registration unable to find class android/bluetooth/BluetoothAdapter"; + return; + } + + jmethodID getDefaultAdapterID = env->GetStaticMethodID(btAdapterClass, "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;"); + if (getDefaultAdapterID == NULL) { + qCWarning(QT_BT_ANDROID) << "Native registration unable to get method ID: getDefaultAdapter of android/bluetooth/BluetoothAdapter"; + return; + } + + + jobject btAdapterObject = env->CallStaticObjectMethod(btAdapterClass, getDefaultAdapterID); + if (btAdapterObject == NULL) { + qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth"; + env->DeleteLocalRef(btAdapterClass); + return; + } + + obj = new QAndroidJniObject(btAdapterObject); + if (!obj->isValid()) { + delete obj; + obj = 0; + } else { + if (!address.isNull()) { + const QString localAddress = obj->callObjectMethod("getAddress", "()Ljava/lang/String;").toString(); + if (localAddress != address.toString()) { + //passed address not local one -> invalid + delete obj; + obj = 0; + } + } + } + + env->DeleteLocalRef(btAdapterObject); + env->DeleteLocalRef(btAdapterClass); +} + +bool QBluetoothLocalDevicePrivate::isValid() const +{ + return obj ? true : false; +} + + +void QBluetoothLocalDevicePrivate::processHostModeChange(QBluetoothLocalDevice::HostMode newMode) +{ + if (!pendingHostModeTransition) { + //if not in transition -> pass data on + emit q_ptr->hostModeStateChanged(newMode); + return; + } + + if (isValid() && newMode == QBluetoothLocalDevice::HostPoweredOff) { + bool success = (bool) obj->callMethod<jboolean>("enable", "()Z"); + if (!success) + emit q_ptr->error(QBluetoothLocalDevice::UnknownError); + } + + pendingHostModeTransition = false; +} + +// Return -1 if address is not part of a pending pairing request +// Otherwise it returns the index of address in pendingPairings +int QBluetoothLocalDevicePrivate::pendingPairing(const QBluetoothAddress &address) +{ + for (int i = 0; i < pendingPairings.count(); i++) { + if (pendingPairings.at(i).first == address) + return i; + } + + return -1; +} + + +void QBluetoothLocalDevicePrivate::processPairingStateChanged( + const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing) +{ + int index = pendingPairing(address); + + if (index < 0) + return; //ignore unrelated pairing signals + + QPair<QBluetoothAddress, bool> entry = pendingPairings.takeAt(index); + if ((entry.second && pairing == QBluetoothLocalDevice::Paired) || + (!entry.second && pairing == QBluetoothLocalDevice::Unpaired)) { + emit q_ptr->pairingFinished(address, pairing); + } else { + emit q_ptr->error(QBluetoothLocalDevice::PairingError); + } + +} + +void QBluetoothLocalDevicePrivate::processConnectDeviceChanges(const QBluetoothAddress& address, bool isConnectEvent) +{ + int index = -1; + for (int i = 0; i < connectedDevices.count(); i++) { + if (connectedDevices.at(i) == address) { + index = i; + break; + } + } + + if (isConnectEvent) { //connect event + if (index >= 0) + return; + connectedDevices.append(address); + emit q_ptr->deviceConnected(address); + } else { //disconnect event + connectedDevices.removeAll(address); + emit q_ptr->deviceDisconnected(address); + } +} + +void QBluetoothLocalDevicePrivate::processDisplayConfirmation(const QBluetoothAddress &address, const QString &pin) +{ + //only send pairing notification for pairing requests issued by + //this QBluetoothLocalDevice instance + if (pendingPairing(address) == -1) + return; + + emit q_ptr->pairingDisplayConfirmation(address, pin); + emit q_ptr->pairingDisplayPinCode(address, pin); +} + +QBluetoothLocalDevice::QBluetoothLocalDevice(QObject *parent) +: QObject(parent), + d_ptr(new QBluetoothLocalDevicePrivate(this, QBluetoothAddress())) +{ +} + +QBluetoothLocalDevice::QBluetoothLocalDevice(const QBluetoothAddress &address, QObject *parent) +: QObject(parent), + d_ptr(new QBluetoothLocalDevicePrivate(this, address)) +{ +} + +QString QBluetoothLocalDevice::name() const +{ + if (d_ptr->adapter()) + return d_ptr->adapter()->callObjectMethod("getName", "()Ljava/lang/String;").toString(); + + return QString(); +} + +QBluetoothAddress QBluetoothLocalDevice::address() const +{ + QString result; + if (d_ptr->adapter()) + result = d_ptr->adapter()->callObjectMethod("getAddress", "()Ljava/lang/String;").toString(); + + QBluetoothAddress address(result); + return address; +} + +void QBluetoothLocalDevice::powerOn() +{ + if (hostMode() != HostPoweredOff) + return; + + if (d_ptr->adapter()) { + bool ret = (bool) d_ptr->adapter()->callMethod<jboolean>("enable", "()Z"); + if (!ret) + emit error(QBluetoothLocalDevice::UnknownError); + } +} + +void QBluetoothLocalDevice::setHostMode(QBluetoothLocalDevice::HostMode requestedMode) +{ + QBluetoothLocalDevice::HostMode mode = requestedMode; + if (requestedMode == HostDiscoverableLimitedInquiry) + mode = HostDiscoverable; + + if (mode == hostMode()) + return; + + if (mode == QBluetoothLocalDevice::HostPoweredOff) { + bool success = false; + if (d_ptr->adapter()) + success = (bool) d_ptr->adapter()->callMethod<jboolean>("disable", "()Z"); + + if (!success) + emit error(QBluetoothLocalDevice::UnknownError); + } else if (mode == QBluetoothLocalDevice::HostConnectable) { + if (hostMode() == QBluetoothLocalDevice::HostDiscoverable) { + //cannot directly go from Discoverable to Connectable + //we need to go to disabled mode and enable once disabling came through + + setHostMode(QBluetoothLocalDevice::HostPoweredOff); + d_ptr->pendingHostModeTransition = true; + } else { + QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver", "setConnectable"); + } + } else if (mode == QBluetoothLocalDevice::HostDiscoverable || + mode == QBluetoothLocalDevice::HostDiscoverableLimitedInquiry) { + QAndroidJniObject::callStaticMethod<void>("org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver", "setDiscoverable"); + } +} + +QBluetoothLocalDevice::HostMode QBluetoothLocalDevice::hostMode() const +{ + if (d_ptr->adapter()) { + jint scanMode = d_ptr->adapter()->callMethod<jint>("getScanMode"); + + switch (scanMode) { + case 20: //BluetoothAdapter.SCAN_MODE_NONE + return HostPoweredOff; + case 21: //BluetoothAdapter.SCAN_MODE_CONNECTABLE + return HostConnectable; + case 23: //BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE + return HostDiscoverable; + default: + break; + } + } + + return HostPoweredOff; +} + +QList<QBluetoothHostInfo> QBluetoothLocalDevice::allDevices() +{ + //Android only supports max of one device (so far) + QList<QBluetoothHostInfo> localDevices; + + QAndroidJniEnvironment env; + jclass btAdapterClass = env->FindClass("android/bluetooth/BluetoothAdapter"); + if (btAdapterClass == NULL) { + qCWarning(QT_BT_ANDROID) << "Native registration unable to find class android/bluetooth/BluetoothAdapter"; + return localDevices; + } + + jmethodID getDefaultAdapterID = env->GetStaticMethodID(btAdapterClass, "getDefaultAdapter", "()Landroid/bluetooth/BluetoothAdapter;"); + if (getDefaultAdapterID == NULL) { + qCWarning(QT_BT_ANDROID) << "Native registration unable to get method ID: getDefaultAdapter of android/bluetooth/BluetoothAdapter"; + env->DeleteLocalRef(btAdapterClass); + return localDevices; + } + + + jobject btAdapterObject = env->CallStaticObjectMethod(btAdapterClass, getDefaultAdapterID); + if (btAdapterObject == NULL) { + qCWarning(QT_BT_ANDROID) << "Device does not support Bluetooth"; + env->DeleteLocalRef(btAdapterClass); + return localDevices; + } + + QAndroidJniObject o(btAdapterObject); + if (o.isValid()) { + QBluetoothHostInfo info; + info.setName(o.callObjectMethod("getName", "()Ljava/lang/String;").toString()); + info.setAddress(QBluetoothAddress(o.callObjectMethod("getAddress", "()Ljava/lang/String;").toString())); + localDevices.append(info); + } + + env->DeleteLocalRef(btAdapterObject); + env->DeleteLocalRef(btAdapterClass); + + return localDevices; +} + +void QBluetoothLocalDevice::requestPairing(const QBluetoothAddress &address, Pairing pairing) +{ + if (address.isNull()) { + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QBluetoothLocalDevice::Error, + QBluetoothLocalDevice::PairingError)); + return; + } + + const Pairing previousPairing = pairingStatus(address); + Pairing newPairing = pairing; + if (pairing == AuthorizedPaired) //AuthorizedPaired same as Paired on Android + newPairing = Paired; + + if (previousPairing == newPairing) { + QMetaObject::invokeMethod(this, "pairingFinished", Qt::QueuedConnection, + Q_ARG(QBluetoothAddress, address), + Q_ARG(QBluetoothLocalDevice::Pairing, pairing)); + return; + } + + //BluetoothDevice::createBond() requires Android API 19 + if (QtAndroidPrivate::androidSdkVersion() < 19 || !d_ptr->adapter()) { + qCWarning(QT_BT_ANDROID) << "Unable to pair: requires Android API 19+"; + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QBluetoothLocalDevice::Error, + QBluetoothLocalDevice::PairingError)); + return; + } + + QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString()); + jboolean success = QAndroidJniObject::callStaticMethod<jboolean>( + "org/qtproject/qt5/android/bluetooth/QtBluetoothBroadcastReceiver", + "setPairingMode", + "(Ljava/lang/String;Z)Z", + inputString.object<jstring>(), + newPairing == Paired ? JNI_TRUE : JNI_FALSE); + + if (!success) { + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QBluetoothLocalDevice::Error, + QBluetoothLocalDevice::PairingError)); + } else { + d_ptr->pendingPairings.append(qMakePair(address, + newPairing == Paired ? true : false)); + } + +} + +QBluetoothLocalDevice::Pairing QBluetoothLocalDevice::pairingStatus(const QBluetoothAddress &address) const +{ + if (address.isNull() || !d_ptr->adapter()) + return Unpaired; + + QAndroidJniObject inputString = QAndroidJniObject::fromString(address.toString()); + QAndroidJniObject remoteDevice = + d_ptr->adapter()->callObjectMethod("getRemoteDevice", + "(Ljava/lang/String;)Landroid/bluetooth/BluetoothDevice;", + inputString.object<jstring>()); + QAndroidJniEnvironment env; + if (env->ExceptionCheck()) { + env->ExceptionClear(); + return Unpaired; + } + + jint bondState = remoteDevice.callMethod<jint>("getBondState"); + switch (bondState) { + case 12: //BluetoothDevice.BOND_BONDED + return Paired; + default: + break; + } + + return Unpaired; +} + +void QBluetoothLocalDevice::pairingConfirmation(bool confirmation) +{ + if (!d_ptr->adapter()) + return; + + bool success = d_ptr->receiver->pairingConfirmation(confirmation); + if (!success) + emit error(PairingError); + +} + +QList<QBluetoothAddress> QBluetoothLocalDevice::connectedDevices() const +{ + //TODO Support BLuetoothManager::getConnectedDevices(int) from API 18 onwards + return d_ptr->connectedDevices; +} + +QT_END_NAMESPACE |