summaryrefslogtreecommitdiffstats
path: root/src/bluetooth/qbluetoothlocaldevice_android.cpp
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@digia.com>2014-02-10 15:37:17 +0100
committerThe Qt Project <gerrit-noreply@qt-project.org>2014-02-13 09:47:27 +0100
commit173d16efb54ccc152f19afb9b1c2a87915fb414b (patch)
treef07ce85ba2cb973e3c08f3ed84252d92ee1c16de /src/bluetooth/qbluetoothlocaldevice_android.cpp
parentdd75b1f776695006ca96fd43f995c3ba0549b968 (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.cpp443
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