/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* ** Copyright 2006, The Android Open Source Project ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. ** You may obtain a copy of the License at ** ** http://www.apache.org/licenses/LICENSE-2.0 ** ** Unless required by applicable law or agreed to in writing, software ** distributed under the License is distributed on an "AS IS" BASIS, ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ** See the License for the specific language governing permissions and ** limitations under the License. */ #include "base/basictypes.h" #include "BluetoothDBusService.h" #include "BluetoothA2dpManager.h" #include "BluetoothHfpManager.h" #include "BluetoothHidManager.h" #include "BluetoothOppManager.h" #include "BluetoothProfileController.h" #include "BluetoothReplyRunnable.h" #include "BluetoothUnixSocketConnector.h" #include "BluetoothUtils.h" #include "BluetoothUuid.h" #include #include #include "nsAutoPtr.h" #include "nsThreadUtils.h" #include "nsDebug.h" #include "nsDataHashtable.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Atomics.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/bluetooth/BluetoothTypes.h" #include "mozilla/Hal.h" #include "mozilla/ipc/UnixSocket.h" #include "mozilla/ipc/DBusUtils.h" #include "mozilla/ipc/RawDBusConnection.h" #include "mozilla/LazyIdleThread.h" #include "mozilla/Mutex.h" #include "mozilla/NullPtr.h" #include "mozilla/StaticMutex.h" #include "mozilla/unused.h" #if defined(MOZ_WIDGET_GONK) #include "cutils/properties.h" #include #endif /** * Some rules for dealing with memory in DBus: * - A DBusError only needs to be deleted if it's been set, not just * initialized. This is why LOG_AND_FREE... is called only when an error is * set, and the macro cleans up the error itself. * - A DBusMessage needs to be unrefed when it is newed explicitly. DBusMessages * from signals do not need to be unrefed, as they will be cleaned by DBus * after DBUS_HANDLER_RESULT_HANDLED is returned from the filter. */ using namespace mozilla; using namespace mozilla::ipc; USING_BLUETOOTH_NAMESPACE #define B2G_AGENT_CAPABILITIES "DisplayYesNo" #define DBUS_MANAGER_IFACE BLUEZ_DBUS_BASE_IFC ".Manager" #define DBUS_ADAPTER_IFACE BLUEZ_DBUS_BASE_IFC ".Adapter" #define DBUS_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".Device" #define DBUS_AGENT_IFACE BLUEZ_DBUS_BASE_IFC ".Agent" #define DBUS_SINK_IFACE BLUEZ_DBUS_BASE_IFC ".AudioSink" #define DBUS_CTL_IFACE BLUEZ_DBUS_BASE_IFC ".Control" #define DBUS_INPUT_IFACE BLUEZ_DBUS_BASE_IFC ".Input" #define BLUEZ_DBUS_BASE_PATH "/org/bluez" #define BLUEZ_DBUS_BASE_IFC "org.bluez" #define BLUEZ_ERROR_IFC "org.bluez.Error" #define ERR_A2DP_IS_DISCONNECTED "A2dpIsDisconnected" #define ERR_AVRCP_IS_DISCONNECTED "AvrcpIsDisconnected" /** * To not lock Bluetooth switch button on Settings UI because of any accident, * we will force disabling Bluetooth 5 seconds after the user requesting to * turn off Bluetooth. */ #define TIMEOUT_FORCE_TO_DISABLE_BT 5 #define BT_LAZY_THREAD_TIMEOUT_MS 3000 #ifdef MOZ_WIDGET_GONK class Bluedroid { struct ScopedDlHandleTraits { typedef void* type; static void* empty() { return nullptr; } static void release(void* handle) { if (!handle) { return; } int res = dlclose(handle); if (res) { BT_WARNING("Failed to close libbluedroid.so: %s", dlerror()); } } }; public: Bluedroid() : m_bt_enable(nullptr) , m_bt_disable(nullptr) , m_bt_is_enabled(nullptr) {} bool Enable() { MOZ_ASSERT(!NS_IsMainThread()); // BT thread if (!mHandle && !Init()) { return false; } else if (m_bt_is_enabled() == 1) { return true; } // 0 == success, -1 == error return !m_bt_enable(); } bool Disable() { MOZ_ASSERT(!NS_IsMainThread()); // BT thread if (!IsEnabled()) { return true; } // 0 == success, -1 == error return !m_bt_disable(); } bool IsEnabled() const { MOZ_ASSERT(!NS_IsMainThread()); // BT thread if (!mHandle) { return false; } // 1 == enabled, 0 == disabled, -1 == error return m_bt_is_enabled() > 0; } private: bool Init() { MOZ_ASSERT(!mHandle); Scoped handle(dlopen("libbluedroid.so", RTLD_LAZY)); if (!handle) { BT_WARNING("Failed to open libbluedroid.so: %s", dlerror()); return false; } int (*bt_enable)() = (int (*)())dlsym(handle, "bt_enable"); if (!bt_enable) { BT_WARNING("Failed to lookup bt_enable: %s", dlerror()); return false; } int (*bt_disable)() = (int (*)())dlsym(handle, "bt_disable"); if (!bt_disable) { BT_WARNING("Failed to lookup bt_disable: %s", dlerror()); return false; } int (*bt_is_enabled)() = (int (*)())dlsym(handle, "bt_is_enabled"); if (!bt_is_enabled) { BT_WARNING("Failed to lookup bt_is_enabled: %s", dlerror()); return false; } m_bt_enable = bt_enable; m_bt_disable = bt_disable; m_bt_is_enabled = bt_is_enabled; mHandle.reset(handle.forget()); return true; } Scoped mHandle; int (* m_bt_enable)(void); int (* m_bt_disable)(void); int (* m_bt_is_enabled)(void); }; // // BT-thread-only variables // // The variables below must only be accessed from within the BT thread. // static class Bluedroid sBluedroid; #endif // // Read-only constants // // The constants below are read-only and may be accessed from any // thread. Most of the contain DBus state or settings, so keep them // on the I/O thread if somehow possible. // typedef struct { const char* name; int type; } Properties; static const Properties sDeviceProperties[] = { {"Address", DBUS_TYPE_STRING}, {"Name", DBUS_TYPE_STRING}, {"Icon", DBUS_TYPE_STRING}, {"Class", DBUS_TYPE_UINT32}, {"UUIDs", DBUS_TYPE_ARRAY}, {"Paired", DBUS_TYPE_BOOLEAN}, {"Connected", DBUS_TYPE_BOOLEAN}, {"Trusted", DBUS_TYPE_BOOLEAN}, {"Blocked", DBUS_TYPE_BOOLEAN}, {"Alias", DBUS_TYPE_STRING}, {"Nodes", DBUS_TYPE_ARRAY}, {"Adapter", DBUS_TYPE_OBJECT_PATH}, {"LegacyPairing", DBUS_TYPE_BOOLEAN}, {"RSSI", DBUS_TYPE_INT16}, {"TX", DBUS_TYPE_UINT32}, {"Type", DBUS_TYPE_STRING}, {"Broadcaster", DBUS_TYPE_BOOLEAN}, {"Services", DBUS_TYPE_ARRAY} }; static const Properties sAdapterProperties[] = { {"Address", DBUS_TYPE_STRING}, {"Name", DBUS_TYPE_STRING}, {"Class", DBUS_TYPE_UINT32}, {"Powered", DBUS_TYPE_BOOLEAN}, {"Discoverable", DBUS_TYPE_BOOLEAN}, {"DiscoverableTimeout", DBUS_TYPE_UINT32}, {"Pairable", DBUS_TYPE_BOOLEAN}, {"PairableTimeout", DBUS_TYPE_UINT32}, {"Discovering", DBUS_TYPE_BOOLEAN}, {"Devices", DBUS_TYPE_ARRAY}, {"UUIDs", DBUS_TYPE_ARRAY}, {"Type", DBUS_TYPE_STRING} }; static const Properties sManagerProperties[] = { {"Adapters", DBUS_TYPE_ARRAY}, }; static const Properties sSinkProperties[] = { {"State", DBUS_TYPE_STRING}, {"Connected", DBUS_TYPE_BOOLEAN}, {"Playing", DBUS_TYPE_BOOLEAN} }; static const Properties sControlProperties[] = { {"Connected", DBUS_TYPE_BOOLEAN} }; static const Properties sInputProperties[] = { {"Connected", DBUS_TYPE_BOOLEAN} }; static const char* const sBluetoothDBusIfaces[] = { DBUS_MANAGER_IFACE, DBUS_ADAPTER_IFACE, DBUS_DEVICE_IFACE }; static const char* const sBluetoothDBusSignals[] = { "type='signal',interface='org.freedesktop.DBus'", "type='signal',interface='org.bluez.Adapter'", "type='signal',interface='org.bluez.Manager'", "type='signal',interface='org.bluez.Device'", "type='signal',interface='org.bluez.Input'", "type='signal',interface='org.bluez.Network'", "type='signal',interface='org.bluez.NetworkServer'", "type='signal',interface='org.bluez.HealthDevice'", "type='signal',interface='org.bluez.AudioSink'", "type='signal',interface='org.bluez.Control'" }; // Only A2DP and HID are authorized. static const BluetoothServiceClass sAuthorizedServiceClass[] = { BluetoothServiceClass::A2DP, BluetoothServiceClass::HID }; /** * The adapter name may not be ready whenever event 'AdapterAdded' is received, * so we'd like to wait for a bit. Only used on main thread. */ static const int sWaitingForAdapterNameInterval = 1000; // unit: ms // // main-thread-only variables // // The variables below must be accessed from within the main thread. // // A queue for connect/disconnect request. See Bug 913372 for details. static nsTArray > sControllerArray; // // I/O-thread-only variables // // The variables below must be accessed from within the I/O thread. // // The DBus connection to the BlueZ daemon static StaticAutoPtr sDBusConnection; // Keep the pairing requests. static unsigned int sIsPairing = 0; static nsDataHashtable* sPairingReqTable; // The object path of the adapter that should // be updated after switching Bluetooth. static nsString sAdapterPath; // // The variables below are currently accessed from within multiple // threads and should be moved to one specific thread if possible. // // TODO: Concurrency control is implemented by locking. Maybe there's // a non-blocking way to implement this. // // Disconnect all profiles before turning off Bluetooth. Please see Bug 891257 // for more details. |sStopBluetoothMonitor| protects access to this variable. static int sConnectedDeviceCount = 0; static StaticAutoPtr sStopBluetoothMonitor; // Protects against bug 969447. static StaticAutoPtr sGetPropertyMonitor; typedef void (*UnpackFunc)(DBusMessage*, DBusError*, BluetoothValue&, nsAString&); typedef bool (*FilterFunc)(const BluetoothValue&); static void DispatchToDBusThread(Task* task) { XRE_GetIOMessageLoop()->PostTask(FROM_HERE, task); } static nsresult DispatchToBtThread(nsIRunnable* aRunnable) { /* Due to the fact that the startup and shutdown of the Bluetooth * system can take an indefinite amount of time, a separate thread * is used for running blocking calls. The thread is not intended * for regular Bluetooth operations though. */ static StaticRefPtr sBluetoothThread; MOZ_ASSERT(NS_IsMainThread()); if (!sBluetoothThread) { sBluetoothThread = new LazyIdleThread(BT_LAZY_THREAD_TIMEOUT_MS, NS_LITERAL_CSTRING("BluetoothDBusService"), LazyIdleThread::ManualShutdown); ClearOnShutdown(&sBluetoothThread); } return sBluetoothThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL); } BluetoothDBusService::BluetoothDBusService() { sGetPropertyMonitor = new Monitor("BluetoothService.sGetPropertyMonitor"); sStopBluetoothMonitor = new Monitor("BluetoothService.sStopBluetoothMonitor"); } BluetoothDBusService::~BluetoothDBusService() { sStopBluetoothMonitor = nullptr; sGetPropertyMonitor = nullptr; } static bool GetConnectedDevicesFilter(const BluetoothValue& aValue) { // We don't have to filter device here return true; } static bool GetPairedDevicesFilter(const BluetoothValue& aValue) { // Check property 'Paired' and only paired device will be returned if (aValue.type() != BluetoothValue::TArrayOfBluetoothNamedValue) { BT_WARNING("Not a BluetoothNamedValue array!"); return false; } const InfallibleTArray& deviceProperties = aValue.get_ArrayOfBluetoothNamedValue(); uint32_t length = deviceProperties.Length(); for (uint32_t p = 0; p < length; ++p) { if (deviceProperties[p].name().EqualsLiteral("Paired")) { return deviceProperties[p].value().get_bool(); } } return false; } class DistributeBluetoothSignalTask : public nsRunnable { public: DistributeBluetoothSignalTask(const BluetoothSignal& aSignal) : mSignal(aSignal) { } nsresult Run() { MOZ_ASSERT(NS_IsMainThread()); BluetoothService* bs = BluetoothService::Get(); NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE); bs->DistributeSignal(mSignal); return NS_OK; } private: BluetoothSignal mSignal; }; class ControlPropertyChangedHandler : public nsRunnable { public: ControlPropertyChangedHandler(const BluetoothSignal& aSignal) : mSignal(aSignal) { } nsresult Run() { MOZ_ASSERT(NS_IsMainThread()); if (mSignal.value().type() != BluetoothValue::TArrayOfBluetoothNamedValue) { BT_WARNING("Wrong value type for ControlPropertyChangedHandler"); return NS_ERROR_FAILURE; } InfallibleTArray& arr = mSignal.value().get_ArrayOfBluetoothNamedValue(); MOZ_ASSERT(arr[0].name().EqualsLiteral("Connected")); MOZ_ASSERT(arr[0].value().type() == BluetoothValue::Tbool); bool connected = arr[0].value().get_bool(); BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE); a2dp->SetAvrcpConnected(connected); return NS_OK; } private: BluetoothSignal mSignal; }; class SinkPropertyChangedHandler : public nsRunnable { public: SinkPropertyChangedHandler(const BluetoothSignal& aSignal) : mSignal(aSignal) { } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mSignal.name().EqualsLiteral("PropertyChanged")); MOZ_ASSERT(mSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue); // Replace object path with device address nsString address = GetAddressFromObjectPath(mSignal.path()); mSignal.path() = address; BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE); a2dp->HandleSinkPropertyChanged(mSignal); return NS_OK; } private: BluetoothSignal mSignal; }; class InputPropertyChangedHandler : public nsRunnable { public: InputPropertyChangedHandler(const BluetoothSignal& aSignal) : mSignal(aSignal) { } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mSignal.name().EqualsLiteral("PropertyChanged")); MOZ_ASSERT(mSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue); // Replace object path with device address nsString address = GetAddressFromObjectPath(mSignal.path()); mSignal.path() = address; BluetoothHidManager* hid = BluetoothHidManager::Get(); NS_ENSURE_TRUE(hid, NS_ERROR_FAILURE); hid->HandleInputPropertyChanged(mSignal); return NS_OK; } private: BluetoothSignal mSignal; }; class TryFiringAdapterAddedTask : public Task { public: void Run() MOZ_OVERRIDE { MOZ_ASSERT(NS_IsMainThread()); BluetoothService* bs = BluetoothService::Get(); NS_ENSURE_TRUE_VOID(bs); #if 0 // for API_V2 bs->AdapterAddedReceived(); bs->TryFiringAdapterAdded(); #endif } }; class TryFiringAdapterAddedRunnable : public nsRunnable { public: TryFiringAdapterAddedRunnable(bool aDelay) : mDelay(aDelay) { } nsresult Run() { MOZ_ASSERT(NS_IsMainThread()); if (mDelay) { MessageLoop::current()-> PostDelayedTask(FROM_HERE, new TryFiringAdapterAddedTask(), sWaitingForAdapterNameInterval); } else { MessageLoop::current()-> PostTask(FROM_HERE, new TryFiringAdapterAddedTask()); } return NS_OK; } private: bool mDelay; }; static bool IsDBusMessageError(DBusMessage* aMsg, DBusError* aErr, nsAString& aErrorStr) { if (aErr && dbus_error_is_set(aErr)) { aErrorStr = NS_ConvertUTF8toUTF16(aErr->message); LOG_AND_FREE_DBUS_ERROR(aErr); return true; } DBusError err; dbus_error_init(&err); if (dbus_message_get_type(aMsg) == DBUS_MESSAGE_TYPE_ERROR) { const char* error_msg; if (!dbus_message_get_args(aMsg, &err, DBUS_TYPE_STRING, &error_msg, DBUS_TYPE_INVALID) || !error_msg) { if (dbus_error_is_set(&err)) { aErrorStr = NS_ConvertUTF8toUTF16(err.message); LOG_AND_FREE_DBUS_ERROR(&err); return true; } else { aErrorStr.AssignLiteral("Unknown Error"); return true; } } else { aErrorStr = NS_ConvertUTF8toUTF16(error_msg); return true; } } return false; } static void UnpackObjectPathMessage(DBusMessage* aMsg, DBusError* aErr, BluetoothValue& aValue, nsAString& aErrorStr) { DBusError err; dbus_error_init(&err); if (!IsDBusMessageError(aMsg, aErr, aErrorStr)) { MOZ_ASSERT(dbus_message_get_type(aMsg) == DBUS_MESSAGE_TYPE_METHOD_RETURN, "Got dbus callback that's not a METHOD_RETURN!"); const char* object_path; if (!dbus_message_get_args(aMsg, &err, DBUS_TYPE_OBJECT_PATH, &object_path, DBUS_TYPE_INVALID) || !object_path) { if (dbus_error_is_set(&err)) { aErrorStr = NS_ConvertUTF8toUTF16(err.message); LOG_AND_FREE_DBUS_ERROR(&err); } } else { aValue = NS_ConvertUTF8toUTF16(object_path); } } } class PrepareProfileManagersRunnable : public nsRunnable { public: nsresult Run() { BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); if (!hfp || !hfp->Listen()) { BT_WARNING("Failed to start listening for BluetoothHfpManager!"); return NS_ERROR_FAILURE; } BluetoothOppManager* opp = BluetoothOppManager::Get(); if (!opp || !opp->Listen()) { BT_WARNING("Failed to start listening for BluetoothOppManager!"); return NS_ERROR_FAILURE; } BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE); a2dp->Reset(); return NS_OK; } }; static void RunDBusCallback(DBusMessage* aMsg, void* aBluetoothReplyRunnable, UnpackFunc aFunc) { #ifdef MOZ_WIDGET_GONK // Due to the fact that we're running two dbus loops on desktop implicitly by // being gtk based, sometimes we'll get signals/reply coming in on the main // thread. There's not a lot we can do about that for the time being and it // (technically) shouldn't hurt anything. However, on gonk, die. MOZ_ASSERT(!NS_IsMainThread()); // I/O thread #endif nsRefPtr replyRunnable = dont_AddRef(static_cast< BluetoothReplyRunnable* >(aBluetoothReplyRunnable)); MOZ_ASSERT(replyRunnable, "Callback reply runnable is null!"); nsAutoString replyError; BluetoothValue v; aFunc(aMsg, nullptr, v, replyError); // Bug 941462. When blueZ replys 'I/O error', we treat it as 'internal error'. // This usually happned when the first pairing request has not yet finished, // the second pairing request issued immediately. if (replyError.EqualsLiteral("I/O error")) { replyError.AssignLiteral(ERR_INTERNAL_ERROR); } DispatchBluetoothReply(replyRunnable, v, replyError); } static void GetObjectPathCallback(DBusMessage* aMsg, void* aBluetoothReplyRunnable) { if (sIsPairing) { RunDBusCallback(aMsg, aBluetoothReplyRunnable, UnpackObjectPathMessage); sIsPairing--; } } static void UnpackVoidMessage(DBusMessage* aMsg, DBusError* aErr, BluetoothValue& aValue, nsAString& aErrorStr) { DBusError err; dbus_error_init(&err); if (!IsDBusMessageError(aMsg, aErr, aErrorStr) && dbus_message_get_type(aMsg) == DBUS_MESSAGE_TYPE_METHOD_RETURN && !dbus_message_get_args(aMsg, &err, DBUS_TYPE_INVALID)) { if (dbus_error_is_set(&err)) { aErrorStr = NS_ConvertUTF8toUTF16(err.message); LOG_AND_FREE_DBUS_ERROR(&err); } } aValue = aErrorStr.IsEmpty(); } static void GetVoidCallback(DBusMessage* aMsg, void* aBluetoothReplyRunnable) { RunDBusCallback(aMsg, aBluetoothReplyRunnable, UnpackVoidMessage); } class ReplyErrorToProfileManager : public nsRunnable { public: ReplyErrorToProfileManager(BluetoothServiceClass aServiceClass, bool aConnect, const nsAString& aErrorString) : mServiceClass(aServiceClass) , mConnect(aConnect) , mErrorString(aErrorString) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread } nsresult Run() { MOZ_ASSERT(NS_IsMainThread()); BluetoothProfileManagerBase* profile; if (mServiceClass == BluetoothServiceClass::HID) { profile = BluetoothHidManager::Get(); } else if (mServiceClass == BluetoothServiceClass::A2DP) { profile = BluetoothA2dpManager::Get(); } else { MOZ_ASSERT(false); return NS_ERROR_FAILURE; } if (mConnect) { profile->OnConnect(mErrorString); } else { profile->OnDisconnect(mErrorString); } return NS_OK; } private: BluetoothServiceClass mServiceClass; bool mConnect; nsString mErrorString; }; static void CheckDBusReply(DBusMessage* aMsg, void* aServiceClass, bool aConnect) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread NS_ENSURE_TRUE_VOID(aMsg); BluetoothValue v; nsAutoString replyError; UnpackVoidMessage(aMsg, nullptr, v, replyError); nsAutoPtr serviceClass( static_cast(aServiceClass)); if (!replyError.IsEmpty()) { NS_DispatchToMainThread( new ReplyErrorToProfileManager(*serviceClass, aConnect, replyError)); } } static void InputConnectCallback(DBusMessage* aMsg, void* aParam) { CheckDBusReply(aMsg, aParam, true); } static void InputDisconnectCallback(DBusMessage* aMsg, void* aParam) { CheckDBusReply(aMsg, aParam, false); } static void SinkConnectCallback(DBusMessage* aMsg, void* aParam) { CheckDBusReply(aMsg, aParam, true); } static void SinkDisconnectCallback(DBusMessage* aMsg, void* aParam) { CheckDBusReply(aMsg, aParam, false); } static bool HasAudioService(uint32_t aCodValue) { return ((aCodValue & 0x200000) == 0x200000); } static bool ContainsIcon(const InfallibleTArray& aProperties) { for (uint8_t i = 0; i < aProperties.Length(); i++) { if (aProperties[i].name().EqualsLiteral("Icon")) { return true; } } return false; } static bool GetProperty(DBusMessageIter aIter, const Properties* aPropertyTypes, int aPropertyTypeLen, int* aPropIndex, InfallibleTArray& aProperties) { /** * Ensure GetProperty runs in critical section otherwise * crash due to timing issue occurs when BT is enabled. * * TODO: Revise GetProperty to solve the crash */ MonitorAutoLock lock(*sGetPropertyMonitor); DBusMessageIter prop_val, array_val_iter; char* property = nullptr; uint32_t array_type; int i, expectedType, receivedType; if (dbus_message_iter_get_arg_type(&aIter) != DBUS_TYPE_STRING) { return false; } dbus_message_iter_get_basic(&aIter, &property); if (!dbus_message_iter_next(&aIter) || dbus_message_iter_get_arg_type(&aIter) != DBUS_TYPE_VARIANT) { return false; } for (i = 0; i < aPropertyTypeLen; i++) { if (!strncmp(property, aPropertyTypes[i].name, strlen(property))) { break; } } if (i == aPropertyTypeLen) { BT_LOGR("unknown property: %s", property); return false; } nsAutoString propertyName; propertyName.AssignASCII(aPropertyTypes[i].name); *aPropIndex = i; // Preprocessing dbus_message_iter_recurse(&aIter, &prop_val); expectedType = aPropertyTypes[*aPropIndex].type; receivedType = dbus_message_iter_get_arg_type(&prop_val); /** * Bug 857896. Since device property "Connected" could be a boolean value or * an 2-byte array, we need to check the value type here and convert the * first byte into a boolean manually. */ bool convert = false; if (propertyName.EqualsLiteral("Connected") && receivedType == DBUS_TYPE_ARRAY) { MOZ_ASSERT(aPropertyTypes == sDeviceProperties); convert = true; } if ((receivedType != expectedType) && !convert) { BT_WARNING("Iterator not type we expect! Property name: %s," "Property Type Expected: %d, Property Type Received: %d", NS_ConvertUTF16toUTF8(propertyName).get(), expectedType, receivedType); return false; } // Extract data BluetoothValue propertyValue; switch (receivedType) { case DBUS_TYPE_STRING: case DBUS_TYPE_OBJECT_PATH: const char* c; dbus_message_iter_get_basic(&prop_val, &c); propertyValue = NS_ConvertUTF8toUTF16(c); break; case DBUS_TYPE_UINT32: case DBUS_TYPE_INT16: uint32_t i; dbus_message_iter_get_basic(&prop_val, &i); propertyValue = i; break; case DBUS_TYPE_BOOLEAN: bool b; dbus_message_iter_get_basic(&prop_val, &b); propertyValue = b; break; case DBUS_TYPE_ARRAY: dbus_message_iter_recurse(&prop_val, &array_val_iter); array_type = dbus_message_iter_get_arg_type(&array_val_iter); if (array_type == DBUS_TYPE_OBJECT_PATH || array_type == DBUS_TYPE_STRING) { InfallibleTArray arr; do { const char* tmp; dbus_message_iter_get_basic(&array_val_iter, &tmp); nsAutoString s; s = NS_ConvertUTF8toUTF16(tmp); arr.AppendElement(s); } while (dbus_message_iter_next(&array_val_iter)); propertyValue = arr; } else if (array_type == DBUS_TYPE_BYTE) { InfallibleTArray arr; do { uint8_t tmp; dbus_message_iter_get_basic(&array_val_iter, &tmp); arr.AppendElement(tmp); } while (dbus_message_iter_next(&array_val_iter)); propertyValue = arr; } else { // This happens when the array is 0-length. Apparently we get a // DBUS_TYPE_INVALID type. propertyValue = InfallibleTArray(); } break; default: NS_NOTREACHED("Cannot find dbus message type!"); } // Postprocessing if (convert) { MOZ_ASSERT(propertyValue.type() == BluetoothValue::TArrayOfuint8_t); bool b = propertyValue.get_ArrayOfuint8_t()[0]; propertyValue = BluetoothValue(b); } else if (propertyName.EqualsLiteral("Devices")) { MOZ_ASSERT(aPropertyTypes == sAdapterProperties); MOZ_ASSERT(propertyValue.type() == BluetoothValue::TArrayOfnsString); uint32_t length = propertyValue.get_ArrayOfnsString().Length(); for (uint32_t i= 0; i < length; i++) { nsString& data = propertyValue.get_ArrayOfnsString()[i]; data = GetAddressFromObjectPath(data); } } aProperties.AppendElement(BluetoothNamedValue(propertyName, propertyValue)); return true; } static void ParseProperties(DBusMessageIter* aIter, BluetoothValue& aValue, nsAString& aErrorStr, const Properties* aPropertyTypes, const int aPropertyTypeLen) { DBusMessageIter dict_entry, dict; int prop_index = -1; MOZ_ASSERT(dbus_message_iter_get_arg_type(aIter) == DBUS_TYPE_ARRAY, "Trying to parse a property from sth. that's not an array"); dbus_message_iter_recurse(aIter, &dict); InfallibleTArray props; do { MOZ_ASSERT(dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY, "Trying to parse a property from sth. that's not an dict!"); dbus_message_iter_recurse(&dict, &dict_entry); if (!GetProperty(dict_entry, aPropertyTypes, aPropertyTypeLen, &prop_index, props)) { aErrorStr.AssignLiteral("Can't Create Property!"); BT_WARNING("Can't create property!"); return; } } while (dbus_message_iter_next(&dict)); aValue = props; } static bool UnpackPropertiesMessage(DBusMessage* aMsg, DBusError* aErr, BluetoothValue& aValue, const char* aIface) { MOZ_ASSERT(aMsg); const Properties* propertyTypes; int propertyTypesLength; nsAutoString errorStr; if (IsDBusMessageError(aMsg, aErr, errorStr) || dbus_message_get_type(aMsg) != DBUS_MESSAGE_TYPE_METHOD_RETURN) { BT_WARNING("dbus message has an error."); return false; } DBusMessageIter iter; if (!dbus_message_iter_init(aMsg, &iter)) { BT_WARNING("Cannot create dbus message iter!"); return false; } if (!strcmp(aIface, DBUS_DEVICE_IFACE)) { propertyTypes = sDeviceProperties; propertyTypesLength = ArrayLength(sDeviceProperties); } else if (!strcmp(aIface, DBUS_ADAPTER_IFACE)) { propertyTypes = sAdapterProperties; propertyTypesLength = ArrayLength(sAdapterProperties); } else if (!strcmp(aIface, DBUS_MANAGER_IFACE)) { propertyTypes = sManagerProperties; propertyTypesLength = ArrayLength(sManagerProperties); } else { return false; } ParseProperties(&iter, aValue, errorStr, propertyTypes, propertyTypesLength); return true; } static void ParsePropertyChange(DBusMessage* aMsg, BluetoothValue& aValue, nsAString& aErrorStr, const Properties* aPropertyTypes, const int aPropertyTypeLen) { DBusMessageIter iter; DBusError err; int prop_index = -1; InfallibleTArray props; dbus_error_init(&err); if (!dbus_message_iter_init(aMsg, &iter)) { BT_WARNING("Can't create iterator!"); return; } if (!GetProperty(iter, aPropertyTypes, aPropertyTypeLen, &prop_index, props)) { BT_WARNING("Can't get property!"); aErrorStr.AssignLiteral("Can't get property!"); return; } aValue = props; } class AppendDeviceNameReplyHandler: public DBusReplyHandler { public: AppendDeviceNameReplyHandler(const nsCString& aIface, const nsString& aDevicePath, const BluetoothSignal& aSignal) : mIface(aIface) , mDevicePath(aDevicePath) , mSignal(aSignal) { MOZ_ASSERT(!mIface.IsEmpty()); MOZ_ASSERT(!mDevicePath.IsEmpty()); } void Handle(DBusMessage* aReply) MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread if (!aReply || (dbus_message_get_type(aReply) == DBUS_MESSAGE_TYPE_ERROR)) { return; } // Get device properties from result of GetProperties DBusError err; dbus_error_init(&err); BluetoothValue deviceProperties; bool success = UnpackPropertiesMessage(aReply, &err, deviceProperties, mIface.get()); if (!success) { BT_WARNING("Failed to get device properties"); return; } // First we replace object path with device address. InfallibleTArray& parameters = mSignal.value().get_ArrayOfBluetoothNamedValue(); nsString address = GetAddressFromObjectPath(mDevicePath); parameters[0].name().AssignLiteral("address"); parameters[0].value() = address; // Then we append the device's name to the original signal's data. InfallibleTArray& properties = deviceProperties.get_ArrayOfBluetoothNamedValue(); uint32_t i; for (i = 0; i < properties.Length(); i++) { if (properties[i].name().EqualsLiteral("Name")) { properties[i].name().AssignLiteral("name"); parameters.AppendElement(properties[i]); break; } } MOZ_ASSERT(i != properties.Length(), "failed to get device name"); nsRefPtr task = new DistributeBluetoothSignalTask(mSignal); NS_DispatchToMainThread(task); } private: nsCString mIface; nsString mDevicePath; BluetoothSignal mSignal; }; static void AppendDeviceName(BluetoothSignal& aSignal) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); BluetoothValue v = aSignal.value(); if (v.type() != BluetoothValue::TArrayOfBluetoothNamedValue || v.get_ArrayOfBluetoothNamedValue().Length() == 0) { BT_WARNING("Invalid argument type for AppendDeviceNameRunnable"); return; } const InfallibleTArray& arr = v.get_ArrayOfBluetoothNamedValue(); // Device object path should be put in the first element if (!arr[0].name().EqualsLiteral("path") || arr[0].value().type() != BluetoothValue::TnsString) { BT_WARNING("Invalid object path for AppendDeviceNameRunnable"); return; } nsString devicePath = arr[0].value().get_nsString(); nsRefPtr handler = new AppendDeviceNameReplyHandler(nsCString(DBUS_DEVICE_IFACE), devicePath, aSignal); bool success = sDBusConnection->SendWithReply( AppendDeviceNameReplyHandler::Callback, handler.get(), 1000, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(devicePath).get(), DBUS_DEVICE_IFACE, "GetProperties", DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << handler.forget(); // picked up by callback handler } class SetPairingConfirmationTask : public Task { public: SetPairingConfirmationTask(const nsAString& aDeviceAddress, bool aConfirm, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mConfirm(aConfirm) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); nsAutoString errorStr; BluetoothValue v = true; DBusMessage *msg; if (!sPairingReqTable->Get(mDeviceAddress, &msg) && mRunnable) { BT_WARNING("%s: Couldn't get original request message.", __FUNCTION__); errorStr.AssignLiteral("Couldn't get original request message."); DispatchBluetoothReply(mRunnable, v, errorStr); return; } DBusMessage *reply; if (mConfirm) { reply = dbus_message_new_method_return(msg); } else { reply = dbus_message_new_error(msg, "org.bluez.Error.Rejected", "User rejected confirmation"); } if (!reply) { BT_WARNING("%s: Memory can't be allocated for the message.", __FUNCTION__); dbus_message_unref(msg); errorStr.AssignLiteral("Memory can't be allocated for the message."); if (mRunnable) { DispatchBluetoothReply(mRunnable, v, errorStr); } return; } bool result = sDBusConnection->Send(reply); if (!result) { errorStr.AssignLiteral("Can't send message!"); } dbus_message_unref(msg); dbus_message_unref(reply); sPairingReqTable->Remove(mDeviceAddress); if (mRunnable) { DispatchBluetoothReply(mRunnable, v, errorStr); } } private: nsString mDeviceAddress; bool mConfirm; nsRefPtr mRunnable; }; static DBusHandlerResult AgentEventFilter(DBusConnection *conn, DBusMessage *msg, void *data) { if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL) { BT_WARNING("%s: agent handler not interested (not a method call).\n", __FUNCTION__); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } DBusError err; dbus_error_init(&err); BT_LOGD("%s: %s, %s", __FUNCTION__, dbus_message_get_path(msg), dbus_message_get_member(msg)); nsString signalPath = NS_ConvertUTF8toUTF16(dbus_message_get_path(msg)); nsString signalName = NS_ConvertUTF8toUTF16(dbus_message_get_member(msg)); nsString errorStr; BluetoothValue v; InfallibleTArray parameters; bool isPairingReq = false; BluetoothSignal signal(signalName, signalPath, v); char *objectPath; // The following descriptions of each signal are retrieved from: // // http://maemo.org/api_refs/5.0/beta/bluez/agent.html // if (dbus_message_is_method_call(msg, DBUS_AGENT_IFACE, "Cancel")) { // This method gets called to indicate that the agent request failed before // a reply was returned. // Return directly DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) { errorStr.AssignLiteral("Memory can't be allocated for the message."); goto handle_error; } dbus_connection_send(conn, reply, nullptr); dbus_message_unref(reply); v = parameters; } else if (dbus_message_is_method_call(msg, DBUS_AGENT_IFACE, "Authorize")) { // This method gets called when the service daemon needs to authorize a // connection/service request. const char *uuid; if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_OBJECT_PATH, &objectPath, DBUS_TYPE_STRING, &uuid, DBUS_TYPE_INVALID)) { errorStr.AssignLiteral("Invalid arguments for Authorize() method"); goto handle_error; } NS_ConvertUTF8toUTF16 uuidStr(uuid); BluetoothServiceClass serviceClass = BluetoothUuidHelper::GetBluetoothServiceClass(uuidStr); if (serviceClass == BluetoothServiceClass::UNKNOWN) { errorStr.AssignLiteral("Failed to get service class"); goto handle_error; } DBusMessage* reply = nullptr; uint32_t i; for (i = 0; i < MOZ_ARRAY_LENGTH(sAuthorizedServiceClass); i++) { if (serviceClass == sAuthorizedServiceClass[i]) { reply = dbus_message_new_method_return(msg); break; } } // The uuid isn't authorized if (i == MOZ_ARRAY_LENGTH(sAuthorizedServiceClass)) { BT_WARNING("Uuid is not authorized."); reply = dbus_message_new_error(msg, "org.bluez.Error.Rejected", "The uuid is not authorized"); } if (!reply) { errorStr.AssignLiteral("Memory can't be allocated for the message."); goto handle_error; } dbus_connection_send(conn, reply, nullptr); dbus_message_unref(reply); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(msg, DBUS_AGENT_IFACE, "RequestConfirmation")) { // This method gets called when the service daemon needs to confirm a // passkey for an authentication. uint32_t passkey; if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_OBJECT_PATH, &objectPath, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID)) { errorStr.AssignLiteral("Invalid arguments: RequestConfirmation()"); goto handle_error; } parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("path"), NS_ConvertUTF8toUTF16(objectPath))); parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("method"), NS_LITERAL_STRING("confirmation"))); parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("passkey"), passkey)); v = parameters; isPairingReq = true; } else if (dbus_message_is_method_call(msg, DBUS_AGENT_IFACE, "RequestPinCode")) { // This method gets called when the service daemon needs to get the passkey // for an authentication. The return value should be a string of 1-16 // characters length. The string can be alphanumeric. if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_OBJECT_PATH, &objectPath, DBUS_TYPE_INVALID)) { errorStr.AssignLiteral("Invalid arguments for RequestPinCode() method"); goto handle_error; } parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("path"), NS_ConvertUTF8toUTF16(objectPath))); parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("method"), NS_LITERAL_STRING("pincode"))); v = parameters; isPairingReq = true; } else if (dbus_message_is_method_call(msg, DBUS_AGENT_IFACE, "RequestPasskey")) { // This method gets called when the service daemon needs to get the passkey // for an authentication. The return value should be a numeric value // between 0-999999. if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_OBJECT_PATH, &objectPath, DBUS_TYPE_INVALID)) { errorStr.AssignLiteral("Invalid arguments for RequestPasskey() method"); goto handle_error; } parameters.AppendElement(BluetoothNamedValue( NS_LITERAL_STRING("path"), NS_ConvertUTF8toUTF16(objectPath))); parameters.AppendElement(BluetoothNamedValue( NS_LITERAL_STRING("method"), NS_LITERAL_STRING("passkey"))); v = parameters; isPairingReq = true; } else if (dbus_message_is_method_call(msg, DBUS_AGENT_IFACE, "Release")) { // This method gets called when the service daemon unregisters the agent. // An agent can use it to do cleanup tasks. There is no need to unregister // the agent, because when this method gets called it has already been // unregistered. DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) { errorStr.AssignLiteral("Memory can't be allocated for the message."); goto handle_error; } dbus_connection_send(conn, reply, nullptr); dbus_message_unref(reply); // Do not send a notification to upper layer, too annoying. return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(msg, DBUS_AGENT_IFACE, "RequestPairingConsent")) { // Directly SetPairingconfirmation for RequestPairingConsent here if (!dbus_message_get_args(msg, nullptr, DBUS_TYPE_OBJECT_PATH, &objectPath, DBUS_TYPE_INVALID)) { errorStr.AssignLiteral("Invalid arguments: RequestPairingConsent()"); goto handle_error; } nsString address = GetAddressFromObjectPath(NS_ConvertUTF8toUTF16(objectPath)); sPairingReqTable->Put(address, msg); Task* task = new SetPairingConfirmationTask(address, true, nullptr); DispatchToDBusThread(task); // Increase dbus message reference counts, it will be decreased in // SetPairingConfirmationTask dbus_message_ref(msg); // Do not send a notification to upper layer return DBUS_HANDLER_RESULT_HANDLED; } else { #ifdef DEBUG BT_WARNING("agent handler %s: Unhandled event. Ignore.", __FUNCTION__); #endif return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (!errorStr.IsEmpty()) { BT_WARNING(NS_ConvertUTF16toUTF8(errorStr).get()); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } // Update value after parsing DBus message signal.value() = v; if (isPairingReq) { sPairingReqTable->Put( GetAddressFromObjectPath(NS_ConvertUTF8toUTF16(objectPath)), msg); // Increase ref count here because we need this message later. // It'll be unrefed when set*Internal() is called. dbus_message_ref(msg); AppendDeviceName(signal); } else { NS_DispatchToMainThread(new DistributeBluetoothSignalTask(signal)); } return DBUS_HANDLER_RESULT_HANDLED; handle_error: BT_WARNING(NS_ConvertUTF16toUTF8(errorStr).get()); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } class RegisterAgentReplyHandler : public DBusReplyHandler { public: RegisterAgentReplyHandler(const DBusObjectPathVTable* aAgentVTable) : mAgentVTable(aAgentVTable) { MOZ_ASSERT(aAgentVTable); } void Handle(DBusMessage* aReply) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); if (!aReply || (dbus_message_get_type(aReply) == DBUS_MESSAGE_TYPE_ERROR)) { return; } // There is no "RegisterAgent" function defined in device interface. // When we call "CreatePairedDevice", it will do device agent registration // for us. (See maemo.org/api_refs/5.0/beta/bluez/adapter.html) if (!dbus_connection_register_object_path(sDBusConnection->GetConnection(), KEY_REMOTE_AGENT, mAgentVTable, nullptr)) { BT_WARNING("%s: Can't register object path %s for remote device agent!", __FUNCTION__, KEY_REMOTE_AGENT); return; } NS_DispatchToMainThread(new PrepareProfileManagersRunnable()); } private: const DBusObjectPathVTable* mAgentVTable; }; class AddReservedServiceRecordsReplyHandler : public DBusReplyHandler { public: void Handle(DBusMessage* aReply) { static const DBusObjectPathVTable sAgentVTable = { nullptr, AgentEventFilter, nullptr, nullptr, nullptr, nullptr }; MOZ_ASSERT(!NS_IsMainThread()); // I/O thread if (!aReply || (dbus_message_get_type(aReply) == DBUS_MESSAGE_TYPE_ERROR)) { return; } // TODO/qdot: This needs to be held for the life of the bluetooth connection // so we could clean it up. For right now though, we can throw it away. nsTArray handles; ExtractHandles(aReply, handles); if(!RegisterAgent(&sAgentVTable)) { BT_WARNING("Failed to register agent"); } } private: void ExtractHandles(DBusMessage *aMessage, nsTArray& aOutHandles) { DBusError error; int length; uint32_t* handles = nullptr; dbus_error_init(&error); bool success = dbus_message_get_args(aMessage, &error, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &handles, &length, DBUS_TYPE_INVALID); if (success != TRUE) { LOG_AND_FREE_DBUS_ERROR(&error); return; } if (!handles) { BT_WARNING("Null array in extract_handles"); return; } for (int i = 0; i < length; ++i) { aOutHandles.AppendElement(handles[i]); } } bool RegisterAgent(const DBusObjectPathVTable* aAgentVTable) { const char* agentPath = KEY_LOCAL_AGENT; const char* capabilities = B2G_AGENT_CAPABILITIES; MOZ_ASSERT(sDBusConnection); // Local agent means agent for Adapter, not agent for Device. Some signals // will be passed to local agent, some will be passed to device agent. // For example, if a remote device would like to pair with us, then the // signal will be passed to local agent. If we start pairing process with // calling CreatePairedDevice, we'll get signal which should be passed to // device agent. if (!dbus_connection_register_object_path(sDBusConnection->GetConnection(), KEY_LOCAL_AGENT, aAgentVTable, nullptr)) { BT_WARNING("%s: Can't register object path %s for agent!", __FUNCTION__, KEY_LOCAL_AGENT); return false; } nsRefPtr handler = new RegisterAgentReplyHandler(aAgentVTable); MOZ_ASSERT(!sAdapterPath.IsEmpty()); bool success = sDBusConnection->SendWithReply( RegisterAgentReplyHandler::Callback, handler.get(), -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, "RegisterAgent", DBUS_TYPE_OBJECT_PATH, &agentPath, DBUS_TYPE_STRING, &capabilities, DBUS_TYPE_INVALID); NS_ENSURE_TRUE(success, false); unused << handler.forget(); // picked up by callback handler return true; } }; class AddReservedServiceRecordsTask : public Task { public: AddReservedServiceRecordsTask() { } void Run() { static const dbus_uint32_t sServices[] = { BluetoothServiceClass::HANDSFREE_AG, BluetoothServiceClass::HEADSET_AG, BluetoothServiceClass::OBJECT_PUSH }; MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsRefPtr handler = new AddReservedServiceRecordsReplyHandler(); const dbus_uint32_t* services = sServices; bool success = sDBusConnection->SendWithReply( DBusReplyHandler::Callback, handler.get(), -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, "AddReservedServiceRecords", DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &services, ArrayLength(sServices), DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << handler.forget(); /* picked up by callback handler */ } }; class PrepareAdapterRunnable : public nsRunnable { public: PrepareAdapterRunnable() { } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); Task* task = new AddReservedServiceRecordsTask(); DispatchToDBusThread(task); return NS_OK; } }; class RequestPlayStatusTask : public nsRunnable { public: RequestPlayStatusTask() { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread } nsresult Run() { MOZ_ASSERT(NS_IsMainThread()); BluetoothSignal signal(NS_LITERAL_STRING(REQUEST_MEDIA_PLAYSTATUS_ID), NS_LITERAL_STRING(KEY_ADAPTER), InfallibleTArray()); BluetoothService* bs = BluetoothService::Get(); NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE); bs->DistributeSignal(signal); return NS_OK; } }; // Called by dbus during WaitForAndDispatchEventNative() // This function is called on the IOThread static DBusHandlerResult EventFilter(DBusConnection* aConn, DBusMessage* aMsg, void* aData) { // I/O thread MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be called from Main Thread!"); if (dbus_message_get_type(aMsg) != DBUS_MESSAGE_TYPE_SIGNAL) { BT_WARNING("%s: event handler not interested in %s (not a signal).\n", __FUNCTION__, dbus_message_get_member(aMsg)); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (dbus_message_get_path(aMsg) == nullptr) { BT_WARNING("DBusMessage %s has no bluetooth destination, ignoring\n", dbus_message_get_member(aMsg)); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } DBusError err; dbus_error_init(&err); nsAutoString signalPath; nsAutoString signalName; nsAutoString signalInterface; BT_LOGD("%s: %s, %s, %s", __FUNCTION__, dbus_message_get_interface(aMsg), dbus_message_get_path(aMsg), dbus_message_get_member(aMsg)); signalInterface = NS_ConvertUTF8toUTF16(dbus_message_get_interface(aMsg)); signalPath = NS_ConvertUTF8toUTF16(dbus_message_get_path(aMsg)); signalName = NS_ConvertUTF8toUTF16(dbus_message_get_member(aMsg)); nsString errorStr; BluetoothValue v; // Since the signalPath extracted from dbus message is a object path, // we'd like to re-assign them to corresponding key entry in // BluetoothSignalObserverTable if (signalInterface.EqualsLiteral(DBUS_MANAGER_IFACE)) { signalPath.AssignLiteral(KEY_MANAGER); } else if (signalInterface.EqualsLiteral(DBUS_ADAPTER_IFACE)) { signalPath.AssignLiteral(KEY_ADAPTER); } else if (signalInterface.EqualsLiteral(DBUS_DEVICE_IFACE)){ signalPath = GetAddressFromObjectPath(signalPath); } if (dbus_message_is_signal(aMsg, DBUS_ADAPTER_IFACE, "DeviceFound")) { DBusMessageIter iter; if (!dbus_message_iter_init(aMsg, &iter)) { BT_WARNING("Can't create iterator!"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } const char* addr; dbus_message_iter_get_basic(&iter, &addr); if (!dbus_message_iter_next(&iter)) { errorStr.AssignLiteral("Unexpected message struct in msg DeviceFound"); } else { ParseProperties(&iter, v, errorStr, sDeviceProperties, ArrayLength(sDeviceProperties)); InfallibleTArray& properties = v.get_ArrayOfBluetoothNamedValue(); // The DBus DeviceFound message actually passes back a key value object // with the address as the key and the rest of the device properties as // a dict value. After we parse out the properties, we need to go back // and add the address to the ipdl dict we've created to make sure we // have all of the information to correctly build the device. nsAutoString address = NS_ConvertUTF8toUTF16(addr); properties.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("Address"), address)); properties.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("Path"), GetObjectPathFromAddress(signalPath, address))); if (!ContainsIcon(properties)) { for (uint32_t i = 0; i < properties.Length(); i++) { // It is possible that property Icon missed due to CoD of major // class is TOY but service class is "Audio", we need to assign // Icon as audio-card. This is for PTS test TC_AG_COD_BV_02_I. // As HFP specification defined that // service class is "Audio" can be considered as HFP AG. if (properties[i].name().EqualsLiteral("Class")) { if (HasAudioService(properties[i].value().get_uint32_t())) { v.get_ArrayOfBluetoothNamedValue().AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("Icon"), NS_LITERAL_STRING("audio-card"))); } break; } } } } } else if (dbus_message_is_signal(aMsg, DBUS_ADAPTER_IFACE, "DeviceDisappeared")) { const char* str; if (!dbus_message_get_args(aMsg, &err, DBUS_TYPE_STRING, &str, DBUS_TYPE_INVALID)) { LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, aMsg); errorStr.AssignLiteral("Cannot parse device address!"); } else { v = NS_ConvertUTF8toUTF16(str); } } else if (dbus_message_is_signal(aMsg, DBUS_ADAPTER_IFACE, "DeviceCreated")) { const char* str; if (!dbus_message_get_args(aMsg, &err, DBUS_TYPE_OBJECT_PATH, &str, DBUS_TYPE_INVALID)) { LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, aMsg); errorStr.AssignLiteral("Cannot parse device path!"); } else { v = NS_ConvertUTF8toUTF16(str); } } else if (dbus_message_is_signal(aMsg, DBUS_ADAPTER_IFACE, "DeviceRemoved")) { const char* str; if (!dbus_message_get_args(aMsg, &err, DBUS_TYPE_OBJECT_PATH, &str, DBUS_TYPE_INVALID)) { LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, aMsg); errorStr.AssignLiteral("Cannot parse device path!"); } else { v = NS_ConvertUTF8toUTF16(str); } } else if (dbus_message_is_signal(aMsg, DBUS_ADAPTER_IFACE, "PropertyChanged")) { ParsePropertyChange(aMsg, v, errorStr, sAdapterProperties, ArrayLength(sAdapterProperties)); } else if (dbus_message_is_signal(aMsg, DBUS_DEVICE_IFACE, "PropertyChanged")) { ParsePropertyChange(aMsg, v, errorStr, sDeviceProperties, ArrayLength(sDeviceProperties)); if (v.type() == BluetoothValue::T__None) { BT_WARNING("PropertyChanged event couldn't be parsed."); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } BluetoothNamedValue& property = v.get_ArrayOfBluetoothNamedValue()[0]; if (property.name().EqualsLiteral("Paired")) { // Original approach: Broadcast system message of // "bluetooth-pairedstatuschanged" from BluetoothService. BluetoothValue newValue(v); ToLowerCase(newValue.get_ArrayOfBluetoothNamedValue()[0].name()); BluetoothSignal signal(NS_LITERAL_STRING(PAIRED_STATUS_CHANGED_ID), NS_LITERAL_STRING(KEY_LOCAL_AGENT), newValue); NS_DispatchToMainThread(new DistributeBluetoothSignalTask(signal)); // New approach: Dispatch event from BluetoothAdapter bool status = property.value(); InfallibleTArray parameters; parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("address"), signalPath)); parameters.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("status"), status)); signal.path() = NS_LITERAL_STRING(KEY_ADAPTER); signal.value() = parameters; NS_DispatchToMainThread(new DistributeBluetoothSignalTask(signal)); } else if (property.name().EqualsLiteral("Connected")) { MonitorAutoLock lock(*sStopBluetoothMonitor); if (property.value().get_bool()) { ++sConnectedDeviceCount; } else { MOZ_ASSERT(sConnectedDeviceCount > 0); if (--sConnectedDeviceCount == 0) { lock.Notify(); } } } } else if (dbus_message_is_signal(aMsg, DBUS_MANAGER_IFACE, "AdapterAdded")) { const char* str; if (!dbus_message_get_args(aMsg, &err, DBUS_TYPE_OBJECT_PATH, &str, DBUS_TYPE_INVALID)) { LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, aMsg); errorStr.AssignLiteral("Cannot parse manager path!"); } else { v = NS_ConvertUTF8toUTF16(str); sAdapterPath = v.get_nsString(); NS_DispatchToMainThread(new TryFiringAdapterAddedRunnable(true)); NS_DispatchToMainThread(new PrepareAdapterRunnable()); /** * The adapter name isn't ready for the time being. Wait for the upcoming * signal PropertyChanged of adapter name, and then propagate signal * AdapterAdded to BluetoothManager. */ return DBUS_HANDLER_RESULT_HANDLED; } } else if (dbus_message_is_signal(aMsg, DBUS_MANAGER_IFACE, "PropertyChanged")) { ParsePropertyChange(aMsg, v, errorStr, sManagerProperties, ArrayLength(sManagerProperties)); } else if (dbus_message_is_signal(aMsg, DBUS_SINK_IFACE, "PropertyChanged")) { ParsePropertyChange(aMsg, v, errorStr, sSinkProperties, ArrayLength(sSinkProperties)); } else if (dbus_message_is_signal(aMsg, DBUS_CTL_IFACE, "GetPlayStatus")) { NS_DispatchToMainThread(new RequestPlayStatusTask()); return DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_signal(aMsg, DBUS_CTL_IFACE, "PropertyChanged")) { ParsePropertyChange(aMsg, v, errorStr, sControlProperties, ArrayLength(sControlProperties)); } else if (dbus_message_is_signal(aMsg, DBUS_INPUT_IFACE, "PropertyChanged")) { ParsePropertyChange(aMsg, v, errorStr, sInputProperties, ArrayLength(sInputProperties)); } else { errorStr = NS_ConvertUTF8toUTF16(dbus_message_get_member(aMsg)); errorStr.AppendLiteral(" Signal not handled!"); } if (!errorStr.IsEmpty()) { BT_WARNING(NS_ConvertUTF16toUTF8(errorStr).get()); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } BluetoothSignal signal(signalName, signalPath, v); nsRefPtr task; if (signalInterface.EqualsLiteral(DBUS_SINK_IFACE)) { task = new SinkPropertyChangedHandler(signal); } else if (signalInterface.EqualsLiteral(DBUS_CTL_IFACE)) { task = new ControlPropertyChangedHandler(signal); } else if (signalInterface.EqualsLiteral(DBUS_INPUT_IFACE)) { task = new InputPropertyChangedHandler(signal); } else { task = new DistributeBluetoothSignalTask(signal); } NS_DispatchToMainThread(task); return DBUS_HANDLER_RESULT_HANDLED; } static void OnDefaultAdapterReply(DBusMessage* aReply, void* aData) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread if (!aReply || dbus_message_is_error(aReply, DBUS_ERROR_TIMEOUT)) { return; } DBusError err; dbus_error_init(&err); BluetoothValue v; nsAutoString errorString; UnpackObjectPathMessage(aReply, &err, v, errorString); if (!errorString.IsEmpty()) { return; } sAdapterPath = v.get_nsString(); nsRefPtr b = new PrepareAdapterRunnable(); if (NS_FAILED(NS_DispatchToMainThread(b))) { BT_WARNING("Failed to dispatch to main thread!"); } } bool BluetoothDBusService::IsReady() { if (!IsEnabled() || !sDBusConnection || IsToggling()) { BT_WARNING("Bluetooth service is not ready yet!"); return false; } return true; } class StartDBusConnectionTask : public Task { public: StartDBusConnectionTask(RawDBusConnection* aConnection) : mConnection(aConnection) { MOZ_ASSERT(mConnection); } void Run() { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread if (sDBusConnection) { BT_WARNING("DBus connection has already been established."); nsRefPtr runnable = new BluetoothService::ToggleBtAck(true); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); } return; } // Add a filter for all incoming messages_base if (!dbus_connection_add_filter(mConnection->GetConnection(), EventFilter, nullptr, nullptr)) { BT_WARNING("Cannot create DBus Event Filter for DBus Thread!"); nsRefPtr runnable = new BluetoothService::ToggleBtAck(false); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); } return; } mConnection->Watch(); if (!sPairingReqTable) { sPairingReqTable = new nsDataHashtable; } sDBusConnection = mConnection.forget(); nsRefPtr runnable = new BluetoothService::ToggleBtAck(true); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); return; } /* Normally we'll receive the signal 'AdapterAdded' with the adapter object * path from the DBus daemon during start up. So, there's no need to query * the object path of default adapter here. However, if we restart from a * crash, the default adapter might already be available, so we ask the daemon * explicitly here. */ if (sAdapterPath.IsEmpty()) { bool success = sDBusConnection->SendWithReply(OnDefaultAdapterReply, nullptr, 1000, BLUEZ_DBUS_BASE_IFC, "/", DBUS_MANAGER_IFACE, "DefaultAdapter", DBUS_TYPE_INVALID); if (!success) { BT_WARNING("Failed to query default adapter!"); } } } private: nsAutoPtr mConnection; }; class StartBluetoothRunnable MOZ_FINAL : public nsRunnable { public: NS_IMETHOD Run() { // This could block. It should never be run on the main thread. MOZ_ASSERT(!NS_IsMainThread()); // BT thread #ifdef MOZ_WIDGET_GONK if (!sBluedroid.Enable()) { BT_WARNING("Bluetooth not available."); nsRefPtr runnable = new BluetoothService::ToggleBtAck(false); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); } return NS_ERROR_FAILURE; } #endif RawDBusConnection* connection = new RawDBusConnection(); nsresult rv = connection->EstablishDBusConnection(); if (NS_FAILED(rv)) { BT_WARNING("Failed to establish connection to BlueZ daemon"); nsRefPtr runnable = new BluetoothService::ToggleBtAck(false); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); } return NS_ERROR_FAILURE; } DBusError err; dbus_error_init(&err); // Set which messages will be processed by this dbus connection. // Since we are maintaining a single thread for all the DBus bluez // signals we want, register all of them in this thread at startup. // The event handler will sort the destinations out as needed. The // call to dbus_bus_add_match has to run on the BT thread because // it can block. for (uint32_t i = 0; i < ArrayLength(sBluetoothDBusSignals); ++i) { dbus_bus_add_match(connection->GetConnection(), sBluetoothDBusSignals[i], &err); if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); } } Task* task = new StartDBusConnectionTask(connection); DispatchToDBusThread(task); return NS_OK; } }; nsresult BluetoothDBusService::StartInternal(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(!aRunnable); nsRefPtr runnable = new StartBluetoothRunnable(); nsresult rv = DispatchToBtThread(runnable); if (NS_FAILED(rv)) { BT_WARNING("Failed to dispatch to BT thread!"); } return rv; } class DisableBluetoothRunnable MOZ_FINAL : public nsRunnable { public: NS_IMETHOD Run() { if (NS_IsMainThread()) { // Clear |sControllerArray| here while we're on the main thread sControllerArray.Clear(); // Forward this runnable to BT thread return DispatchToBtThread(this); } #ifdef MOZ_WIDGET_GONK MOZ_ASSERT(sBluedroid.IsEnabled()); // Disable() return true on success, so we need to invert it bool isEnabled = !sBluedroid.Disable(); #else bool isEnabled = false; #endif nsRefPtr runnable = new BluetoothService::ToggleBtAck(isEnabled); nsresult rv = NS_DispatchToMainThread(runnable); if (NS_FAILED(rv)) { BT_WARNING("Failed to dispatch to main thread!"); } return rv; } }; class DeleteDBusConnectionTask MOZ_FINAL : public Task { public: DeleteDBusConnectionTask() { } void Run() { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread if (!sDBusConnection) { BT_WARNING("DBus connection has not been established."); nsRefPtr runnable = new BluetoothService::ToggleBtAck(false); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); } return; } for (uint32_t i = 0; i < ArrayLength(sBluetoothDBusSignals); ++i) { dbus_bus_remove_match(sDBusConnection->GetConnection(), sBluetoothDBusSignals[i], NULL); } dbus_connection_remove_filter(sDBusConnection->GetConnection(), EventFilter, nullptr); if (!dbus_connection_unregister_object_path(sDBusConnection->GetConnection(), KEY_LOCAL_AGENT)) { BT_WARNING("%s: Can't unregister object path %s for agent!", __FUNCTION__, KEY_LOCAL_AGENT); } if (!dbus_connection_unregister_object_path(sDBusConnection->GetConnection(), KEY_REMOTE_AGENT)) { BT_WARNING("%s: Can't unregister object path %s for agent!", __FUNCTION__, KEY_REMOTE_AGENT); } // unref stored DBusMessages before clearing the hashtable sPairingReqTable->EnumerateRead(UnrefDBusMessage, nullptr); sPairingReqTable->Clear(); sIsPairing = 0; sConnectedDeviceCount = 0; // This command closes the DBus connection and all its instances // of DBusWatch will be removed and free'd. sDBusConnection = nullptr; // We can only dispatch to the BT thread if we're on the main // thread. Thus we dispatch our runnable to the main thread // from where it will forward itself to the BT thread. nsRefPtr runnable = new DisableBluetoothRunnable(); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to BT thread!"); } } private: static PLDHashOperator UnrefDBusMessage(const nsAString& key, DBusMessage* value, void* arg) { dbus_message_unref(value); return PL_DHASH_NEXT; } }; class StopBluetoothRunnable MOZ_FINAL : public nsRunnable { public: NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); // BT thread // This could block. It should never be run on the main thread. MonitorAutoLock lock(*sStopBluetoothMonitor); if (sConnectedDeviceCount > 0) { lock.Wait(PR_SecondsToInterval(TIMEOUT_FORCE_TO_DISABLE_BT)); } DispatchToDBusThread(new DeleteDBusConnectionTask()); return NS_OK; } }; nsresult BluetoothDBusService::StopInternal(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(!aRunnable); nsRefPtr runnable = new StopBluetoothRunnable(); nsresult rv = DispatchToBtThread(runnable); if (NS_FAILED(rv)) { BT_WARNING("Failed to dispatch to BT thread!"); } return rv; } class DefaultAdapterPathReplyHandler : public DBusReplyHandler { public: DefaultAdapterPathReplyHandler(BluetoothReplyRunnable* aRunnable) : mRunnable(aRunnable) { MOZ_ASSERT(mRunnable); } void Handle(DBusMessage* aReply) MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread if (!aReply || (dbus_message_get_type(aReply) == DBUS_MESSAGE_TYPE_ERROR)) { const char* errStr = "Timeout in DefaultAdapterPathReplyHandler"; if (aReply) { errStr = dbus_message_get_error_name(aReply); if (!errStr) { errStr = "Bluetooth DBus Error"; } } DispatchBluetoothReply(mRunnable, BluetoothValue(), NS_ConvertUTF8toUTF16(errStr)); return; } bool success; nsAutoString replyError; if (mAdapterPath.IsEmpty()) { success = HandleDefaultAdapterPathReply(aReply, replyError); } else { success = HandleGetPropertiesReply(aReply, replyError); } if (!success) { DispatchBluetoothReply(mRunnable, BluetoothValue(), replyError); } } protected: bool HandleDefaultAdapterPathReply(DBusMessage* aReply, nsAString& aReplyError) { BluetoothValue value; DBusError error; dbus_error_init(&error); MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); UnpackObjectPathMessage(aReply, &error, value, aReplyError); if (!aReplyError.IsEmpty()) { return false; } mAdapterPath = value.get_nsString(); // Acquire another reference to this reply handler nsRefPtr handler = this; bool success = sDBusConnection->SendWithReply( DefaultAdapterPathReplyHandler::Callback, handler.get(), 1000, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(mAdapterPath).get(), DBUS_ADAPTER_IFACE, "GetProperties", DBUS_TYPE_INVALID); if (!success) { aReplyError = NS_LITERAL_STRING("SendWithReply failed"); return false; } unused << handler.forget(); // picked up by callback handler return true; } bool HandleGetPropertiesReply(DBusMessage* aReply, nsAutoString& aReplyError) { BluetoothValue value; DBusError error; dbus_error_init(&error); MOZ_ASSERT(!NS_IsMainThread()); // I/O thread bool success = UnpackPropertiesMessage(aReply, &error, value, DBUS_ADAPTER_IFACE); if (!success) { aReplyError = NS_ConvertUTF8toUTF16(error.message); return false; } // We have to manually attach the path to the rest of the elements value.get_ArrayOfBluetoothNamedValue().AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("Path"), mAdapterPath)); // Dispatch result DispatchBluetoothReply(mRunnable, value, aReplyError); return true; } private: nsRefPtr mRunnable; nsString mAdapterPath; }; class DefaultAdapterTask : public Task { public: DefaultAdapterTask(BluetoothReplyRunnable* aRunnable) : mRunnable(aRunnable) { MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); nsRefPtr handler = new DefaultAdapterPathReplyHandler(mRunnable); bool success = sDBusConnection->SendWithReply( DefaultAdapterPathReplyHandler::Callback, handler.get(), 1000, BLUEZ_DBUS_BASE_IFC, "/", DBUS_MANAGER_IFACE, "DefaultAdapter", DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << handler.forget(); // picked up by callback handler } private: nsRefPtr mRunnable; }; nsresult BluetoothDBusService::GetAdaptersInternal(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); /** * TODO: implement method GetAdaptersInternal for bluez */ if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } Task* task = new DefaultAdapterTask(aRunnable); DispatchToDBusThread(task); return NS_OK; } static void OnSendDiscoveryMessageReply(DBusMessage *aReply, void *aData) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread nsAutoString errorStr; if (!aReply) { errorStr.AssignLiteral("SendDiscovery failed"); } nsRefPtr runnable = dont_AddRef(static_cast(aData)); DispatchBluetoothReply(runnable.get(), BluetoothValue(true), errorStr); } class SendDiscoveryMessageTask : public Task { public: SendDiscoveryMessageTask(const char* aMessageName, BluetoothReplyRunnable* aRunnable) : mMessageName(aMessageName) , mRunnable(aRunnable) { MOZ_ASSERT(!mMessageName.IsEmpty()); MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); bool success = sDBusConnection->SendWithReply( OnSendDiscoveryMessageReply, static_cast(mRunnable.get()), -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, mMessageName.get(), DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << mRunnable.forget(); // picked up by callback handler } private: const nsCString mMessageName; nsRefPtr mRunnable; }; nsresult BluetoothDBusService::SendDiscoveryMessage(const char* aMessageName, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!sAdapterPath.IsEmpty()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } Task* task = new SendDiscoveryMessageTask(aMessageName, aRunnable); DispatchToDBusThread(task); return NS_OK; } nsresult BluetoothDBusService::SendInputMessage(const nsAString& aDeviceAddress, const nsAString& aMessage) { DBusReplyCallback callback; if (aMessage.EqualsLiteral("Connect")) { callback = InputConnectCallback; } else if (aMessage.EqualsLiteral("Disconnect")) { callback = InputDisconnectCallback; } else { MOZ_ASSERT(false); return NS_ERROR_FAILURE; } MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsString objectPath = GetObjectPathFromAddress(sAdapterPath, aDeviceAddress); return SendAsyncDBusMessage(objectPath, DBUS_INPUT_IFACE, aMessage, callback); } class SendAsyncDBusMessageTask : public Task { public: SendAsyncDBusMessageTask(DBusReplyCallback aCallback, BluetoothServiceClass* aServiceClass, const nsACString& aObjectPath, const char* aInterface, const nsACString& aMessage) : mCallback(aCallback) , mServiceClass(aServiceClass) , mObjectPath(aObjectPath) , mInterface(aInterface) , mMessage(aMessage) { MOZ_ASSERT(mServiceClass); MOZ_ASSERT(!mObjectPath.IsEmpty()); MOZ_ASSERT(!mInterface.IsEmpty()); MOZ_ASSERT(!mMessage.IsEmpty()); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); bool success = sDBusConnection->SendWithReply( mCallback, static_cast(mServiceClass), -1, BLUEZ_DBUS_BASE_IFC, mObjectPath.get(), mInterface.get(), mMessage.get(), DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); mServiceClass.forget(); } private: DBusReplyCallback mCallback; nsAutoPtr mServiceClass; const nsCString mObjectPath; const nsCString mInterface; const nsCString mMessage; }; nsresult BluetoothDBusService::SendAsyncDBusMessage(const nsAString& aObjectPath, const char* aInterface, const nsAString& aMessage, DBusReplyCallback aCallback) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(IsEnabled()); MOZ_ASSERT(aCallback); MOZ_ASSERT(!aObjectPath.IsEmpty()); MOZ_ASSERT(aInterface); nsAutoPtr serviceClass(new BluetoothServiceClass()); if (!strcmp(aInterface, DBUS_SINK_IFACE)) { *serviceClass = BluetoothServiceClass::A2DP; } else if (!strcmp(aInterface, DBUS_INPUT_IFACE)) { *serviceClass = BluetoothServiceClass::HID; } else { MOZ_ASSERT(false); return NS_ERROR_FAILURE; } Task* task = new SendAsyncDBusMessageTask(aCallback, serviceClass.forget(), NS_ConvertUTF16toUTF8(aObjectPath), aInterface, NS_ConvertUTF16toUTF8(aMessage)); DispatchToDBusThread(task); return NS_OK; } nsresult BluetoothDBusService::SendSinkMessage(const nsAString& aDeviceAddress, const nsAString& aMessage) { DBusReplyCallback callback; if (aMessage.EqualsLiteral("Connect")) { callback = SinkConnectCallback; } else if (aMessage.EqualsLiteral("Disconnect")) { callback = SinkDisconnectCallback; } else { MOZ_ASSERT(false); return NS_ERROR_FAILURE; } MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsString objectPath = GetObjectPathFromAddress(sAdapterPath, aDeviceAddress); return SendAsyncDBusMessage(objectPath, DBUS_SINK_IFACE, aMessage, callback); } nsresult BluetoothDBusService::StopDiscoveryInternal(BluetoothReplyRunnable* aRunnable) { return SendDiscoveryMessage("StopDiscovery", aRunnable); } nsresult BluetoothDBusService::StartDiscoveryInternal(BluetoothReplyRunnable* aRunnable) { return SendDiscoveryMessage("StartDiscovery", aRunnable); } class BluetoothArrayOfDevicePropertiesReplyHandler : public DBusReplyHandler { public: BluetoothArrayOfDevicePropertiesReplyHandler( const nsTArray& aDeviceAddresses, const FilterFunc aFilterFunc, BluetoothReplyRunnable* aRunnable) : mDeviceAddresses(aDeviceAddresses) , mProcessedDeviceAddresses(0) , mFilterFunc(aFilterFunc) , mRunnable(aRunnable) , mValues(InfallibleTArray()) { MOZ_ASSERT(mRunnable); } void Handle(DBusMessage* aReply) MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(!sAdapterPath.IsEmpty()); MOZ_ASSERT(!mObjectPath.IsEmpty()); MOZ_ASSERT(mProcessedDeviceAddresses < mDeviceAddresses.Length()); const nsTArray::index_type i = mProcessedDeviceAddresses++; if (!aReply || (dbus_message_get_type(aReply) == DBUS_MESSAGE_TYPE_ERROR)) { BT_WARNING("Invalid DBus message"); ProcessRemainingDeviceAddresses(); return; } // Get device properties from result of GetProperties DBusError err; dbus_error_init(&err); BluetoothValue deviceProperties; bool success = UnpackPropertiesMessage(aReply, &err, deviceProperties, DBUS_DEVICE_IFACE); if (!success) { BT_WARNING("Failed to get device properties"); ProcessRemainingDeviceAddresses(); return; } InfallibleTArray& devicePropertiesArray = deviceProperties.get_ArrayOfBluetoothNamedValue(); // We have to manually attach the path to the rest of the elements devicePropertiesArray.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("Path"), mObjectPath)); // It is possible that property Icon missed due to CoD of major // class is TOY but service class is "Audio", we need to assign // Icon as audio-card. This is for PTS test TC_AG_COD_BV_02_I. // As HFP specification defined that // service class is "Audio" can be considered as HFP AG. if (!ContainsIcon(devicePropertiesArray)) { for (uint32_t j = 0; j < devicePropertiesArray.Length(); ++j) { BluetoothNamedValue& deviceProperty = devicePropertiesArray[j]; if (deviceProperty.name().EqualsLiteral("Class")) { if (HasAudioService(deviceProperty.value().get_uint32_t())) { devicePropertiesArray.AppendElement( BluetoothNamedValue(NS_LITERAL_STRING("Icon"), NS_LITERAL_STRING("audio-card"))); } break; } } } if (mFilterFunc(deviceProperties)) { mValues.get_ArrayOfBluetoothNamedValue().AppendElement( BluetoothNamedValue(mDeviceAddresses[i], deviceProperties)); } ProcessRemainingDeviceAddresses(); } void ProcessRemainingDeviceAddresses() { if (mProcessedDeviceAddresses < mDeviceAddresses.Length()) { if (!SendNextGetProperties()) { DispatchBluetoothReply(mRunnable, BluetoothValue(), NS_LITERAL_STRING( "SendNextGetProperties failed")); } } else { // Send resulting device properties DispatchBluetoothReply(mRunnable, mValues, EmptyString()); } } protected: bool SendNextGetProperties() { MOZ_ASSERT(mProcessedDeviceAddresses < mDeviceAddresses.Length()); MOZ_ASSERT(!sAdapterPath.IsEmpty()); MOZ_ASSERT(sDBusConnection); // cache object path for reply mObjectPath = GetObjectPathFromAddress(sAdapterPath, mDeviceAddresses[mProcessedDeviceAddresses]); nsRefPtr handler = this; bool success = sDBusConnection->SendWithReply( BluetoothArrayOfDevicePropertiesReplyHandler::Callback, handler.get(), 1000, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(mObjectPath).get(), DBUS_DEVICE_IFACE, "GetProperties", DBUS_TYPE_INVALID); NS_ENSURE_TRUE(success, false); unused << handler.forget(); // picked up by callback handler return true; } private: nsString mObjectPath; const nsTArray mDeviceAddresses; nsTArray::size_type mProcessedDeviceAddresses; const FilterFunc mFilterFunc; nsRefPtr mRunnable; BluetoothValue mValues; }; class ProcessRemainingDeviceAddressesTask : public Task { public: ProcessRemainingDeviceAddressesTask( BluetoothArrayOfDevicePropertiesReplyHandler* aHandler, BluetoothReplyRunnable* aRunnable) : mHandler(aHandler) , mRunnable(aRunnable) { MOZ_ASSERT(mHandler); MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread mHandler->ProcessRemainingDeviceAddresses(); } private: nsRefPtr mHandler; nsRefPtr mRunnable; }; nsresult BluetoothDBusService::GetConnectedDevicePropertiesInternal(uint16_t aServiceUuid, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); nsAutoString errorStr; BluetoothValue values = InfallibleTArray(); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } nsTArray deviceAddresses; BluetoothProfileManagerBase* profile = BluetoothUuidHelper::GetBluetoothProfileManager(aServiceUuid); if (!profile) { DispatchBluetoothReply(aRunnable, values, NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE)); return NS_OK; } if (profile->IsConnected()) { nsString address; profile->GetAddress(address); deviceAddresses.AppendElement(address); } BluetoothArrayOfDevicePropertiesReplyHandler* handler = new BluetoothArrayOfDevicePropertiesReplyHandler(deviceAddresses, GetConnectedDevicesFilter, aRunnable); Task* task = new ProcessRemainingDeviceAddressesTask(handler, aRunnable); DispatchToDBusThread(task); return NS_OK; } nsresult BluetoothDBusService::GetPairedDevicePropertiesInternal( const nsTArray& aDeviceAddresses, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } BluetoothArrayOfDevicePropertiesReplyHandler* handler = new BluetoothArrayOfDevicePropertiesReplyHandler(aDeviceAddresses, GetPairedDevicesFilter, aRunnable); Task* task = new ProcessRemainingDeviceAddressesTask(handler, aRunnable); DispatchToDBusThread(task); return NS_OK; } nsresult FetchUuidsInternal(const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable) { return NS_OK; } class SetPropertyTask : public Task { public: SetPropertyTask(BluetoothObjectType aType, const nsACString& aName, BluetoothReplyRunnable* aRunnable) : mType(aType) , mName(aName) , mRunnable(aRunnable) { MOZ_ASSERT(mRunnable); } void Send(unsigned int aType, const void* aValue) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); DBusMessage* msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), sBluetoothDBusIfaces[mType], "SetProperty"); if (!msg) { BT_WARNING("Could not allocate D-Bus message object!"); return; } const char* name = mName.get(); if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) { BT_WARNING("Couldn't append arguments to dbus message!"); return; } DBusMessageIter value_iter, iter; dbus_message_iter_init_append(msg, &iter); char var_type[2] = {(char)aType, '\0'}; if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, var_type, &value_iter) || !dbus_message_iter_append_basic(&value_iter, aType, aValue) || !dbus_message_iter_close_container(&iter, &value_iter)) { BT_WARNING("Could not append argument to method call!"); dbus_message_unref(msg); return; } // msg is unref'd as part of SendWithReply bool success = sDBusConnection->SendWithReply( GetVoidCallback, static_cast(mRunnable), 1000, msg); NS_ENSURE_TRUE_VOID(success); unused << mRunnable.forget(); // picked up by callback handler } private: BluetoothObjectType mType; const nsCString mName; nsRefPtr mRunnable; }; class SetUInt32PropertyTask : public SetPropertyTask { public: SetUInt32PropertyTask(BluetoothObjectType aType, const nsACString& aName, uint32_t aValue, BluetoothReplyRunnable* aRunnable) : SetPropertyTask(aType, aName, aRunnable) , mValue(aValue) { } void Run() MOZ_OVERRIDE { Send(DBUS_TYPE_UINT32, &mValue); } private: dbus_uint32_t mValue; }; class SetStringPropertyTask : public SetPropertyTask { public: SetStringPropertyTask(BluetoothObjectType aType, const nsACString& aName, const nsACString& aValue, BluetoothReplyRunnable* aRunnable) : SetPropertyTask(aType, aName, aRunnable) , mValue(aValue) { } void Run() MOZ_OVERRIDE { const char* value = mValue.get(); Send(DBUS_TYPE_STRING, &value); } private: const nsCString mValue; }; class SetBooleanPropertyTask : public SetPropertyTask { public: SetBooleanPropertyTask(BluetoothObjectType aType, const nsACString& aName, dbus_bool_t aValue, BluetoothReplyRunnable* aRunnable) : SetPropertyTask(aType, aName, aRunnable) , mValue(aValue) { } void Run() MOZ_OVERRIDE { Send(DBUS_TYPE_BOOLEAN, &mValue); } private: dbus_bool_t mValue; }; nsresult BluetoothDBusService::SetProperty(BluetoothObjectType aType, const BluetoothNamedValue& aValue, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } Task* task; if (aValue.value().type() == BluetoothValue::Tuint32_t) { task = new SetUInt32PropertyTask(aType, NS_ConvertUTF16toUTF8(aValue.name()), aValue.value().get_uint32_t(), aRunnable); } else if (aValue.value().type() == BluetoothValue::TnsString) { task = new SetStringPropertyTask(aType, NS_ConvertUTF16toUTF8(aValue.name()), NS_ConvertUTF16toUTF8(aValue.value().get_nsString()), aRunnable); } else if (aValue.value().type() == BluetoothValue::Tbool) { task = new SetBooleanPropertyTask(aType, NS_ConvertUTF16toUTF8(aValue.name()), aValue.value().get_bool(), aRunnable); } else { BT_WARNING("Property type not handled!"); return NS_ERROR_FAILURE; } DispatchToDBusThread(task); return NS_OK; } class CreatePairedDeviceInternalTask : public Task { public: CreatePairedDeviceInternalTask(const nsACString& aDeviceAddress, int aTimeout, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mTimeout(aTimeout) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const char *deviceAddress = mDeviceAddress.get(); const char *deviceAgentPath = KEY_REMOTE_AGENT; const char *capabilities = B2G_AGENT_CAPABILITIES; // Then send CreatePairedDevice, it will register a temp device agent then // unregister it after pairing process is over bool success = sDBusConnection->SendWithReply( GetObjectPathCallback, static_cast(mRunnable), mTimeout, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, "CreatePairedDevice", DBUS_TYPE_STRING, &deviceAddress, DBUS_TYPE_OBJECT_PATH, &deviceAgentPath, DBUS_TYPE_STRING, &capabilities, DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << mRunnable.forget(); // picked up by callback handler /** * FIXME: Bug 820274 * * If the user turns off Bluetooth in the middle of pairing process, * the callback function GetObjectPathCallback may still be called * while enabling next time by dbus daemon. To prevent this from * happening, added a flag to distinguish if Bluetooth has been * turned off. Nevertheless, we need a check if there is a better * solution. * * Please see Bug 818696 for more information. */ sIsPairing++; } private: const nsCString mDeviceAddress; int mTimeout; nsRefPtr mRunnable; }; nsresult BluetoothDBusService::CreatePairedDeviceInternal( const nsAString& aDeviceAddress, int aTimeout, BluetoothReplyRunnable* aRunnable) { Task* task = new CreatePairedDeviceInternalTask( NS_ConvertUTF16toUTF8(aDeviceAddress), aTimeout, aRunnable); DispatchToDBusThread(task); return NS_OK; } class RemoveDeviceTask : public Task { public: RemoveDeviceTask(const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsCString deviceObjectPath = NS_ConvertUTF16toUTF8(GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); const char* cstrDeviceObjectPath = deviceObjectPath.get(); bool success = sDBusConnection->SendWithReply( OnRemoveDeviceReply, static_cast(mRunnable.get()), -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, "RemoveDevice", DBUS_TYPE_OBJECT_PATH, &cstrDeviceObjectPath, DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << mRunnable.forget(); // picked up by callback handler } protected: static void OnRemoveDeviceReply(DBusMessage* aReply, void* aData) { nsAutoString errorStr; if (!aReply) { errorStr.AssignLiteral("RemoveDevice failed"); } nsRefPtr runnable = dont_AddRef( static_cast(aData)); DispatchBluetoothReply(runnable.get(), BluetoothValue(true), errorStr); } private: const nsString mDeviceAddress; nsRefPtr mRunnable; }; nsresult BluetoothDBusService::RemoveDeviceInternal(const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } Task* task = new RemoveDeviceTask(aDeviceAddress, aRunnable); DispatchToDBusThread(task); return NS_OK; } class SetPinCodeTask : public Task { public: SetPinCodeTask(const nsAString& aDeviceAddress, const nsACString& aPinCode, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mPinCode(aPinCode) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread nsAutoString errorStr; BluetoothValue v = true; DBusMessage *msg; if (!sPairingReqTable->Get(mDeviceAddress, &msg)) { BT_WARNING("%s: Couldn't get original request message.", __FUNCTION__); errorStr.AssignLiteral("Couldn't get original request message."); DispatchBluetoothReply(mRunnable, v, errorStr); return; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) { BT_WARNING("%s: Memory can't be allocated for the message.", __FUNCTION__); dbus_message_unref(msg); errorStr.AssignLiteral("Memory can't be allocated for the message."); DispatchBluetoothReply(mRunnable, v, errorStr); return; } const char* pinCode = mPinCode.get(); if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &pinCode, DBUS_TYPE_INVALID)) { BT_WARNING("%s: Couldn't append arguments to dbus message.", __FUNCTION__); errorStr.AssignLiteral("Couldn't append arguments to dbus message."); } else { MOZ_ASSERT(sDBusConnection); sDBusConnection->Send(reply); } dbus_message_unref(msg); dbus_message_unref(reply); sPairingReqTable->Remove(mDeviceAddress); DispatchBluetoothReply(mRunnable, v, errorStr); } private: const nsString mDeviceAddress; const nsCString mPinCode; nsRefPtr mRunnable; }; void BluetoothDBusService::SetPinCodeInternal(const nsAString& aDeviceAddress, const nsAString& aPinCode, BluetoothReplyRunnable* aRunnable) { Task* task = new SetPinCodeTask(aDeviceAddress, NS_ConvertUTF16toUTF8(aPinCode), aRunnable); DispatchToDBusThread(task); } class SetPasskeyTask : public Task { public: SetPasskeyTask(const nsAString& aDeviceAddress, uint32_t aPasskey, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mPasskey(aPasskey) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread nsAutoString errorStr; BluetoothValue v = true; DBusMessage *msg; if (!sPairingReqTable->Get(mDeviceAddress, &msg)) { BT_WARNING("%s: Couldn't get original request message.", __FUNCTION__); errorStr.AssignLiteral("Couldn't get original request message."); DispatchBluetoothReply(mRunnable, v, errorStr); return; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) { BT_WARNING("%s: Memory can't be allocated for the message.", __FUNCTION__); dbus_message_unref(msg); errorStr.AssignLiteral("Memory can't be allocated for the message."); DispatchBluetoothReply(mRunnable, v, errorStr); return; } uint32_t passkey = mPasskey; if (!dbus_message_append_args(reply, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID)) { BT_WARNING("%s: Couldn't append arguments to dbus message.", __FUNCTION__); errorStr.AssignLiteral("Couldn't append arguments to dbus message."); } else { MOZ_ASSERT(sDBusConnection); sDBusConnection->Send(reply); } dbus_message_unref(msg); dbus_message_unref(reply); sPairingReqTable->Remove(mDeviceAddress); DispatchBluetoothReply(mRunnable, v, errorStr); } private: nsString mDeviceAddress; uint32_t mPasskey; nsRefPtr mRunnable; }; void BluetoothDBusService::SetPasskeyInternal(const nsAString& aDeviceAddress, uint32_t aPasskey, BluetoothReplyRunnable* aRunnable) { Task* task = new SetPasskeyTask(aDeviceAddress, aPasskey, aRunnable); DispatchToDBusThread(task); } void BluetoothDBusService::SetPairingConfirmationInternal( const nsAString& aDeviceAddress, bool aConfirm, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); Task* task = new SetPairingConfirmationTask(aDeviceAddress, aConfirm, aRunnable); DispatchToDBusThread(task); } static void NextBluetoothProfileController() { MOZ_ASSERT(NS_IsMainThread()); // First, remove the task at the front which has been already done. NS_ENSURE_FALSE_VOID(sControllerArray.IsEmpty()); sControllerArray.RemoveElementAt(0); // Re-check if the task array is empty, if it's not, the next task will begin. NS_ENSURE_FALSE_VOID(sControllerArray.IsEmpty()); sControllerArray[0]->StartSession(); } static void ConnectDisconnect(bool aConnect, const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable, uint16_t aServiceUuid, uint32_t aCod = 0) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aRunnable); BluetoothProfileController* controller = new BluetoothProfileController(aConnect, aDeviceAddress, aRunnable, NextBluetoothProfileController, aServiceUuid, aCod); sControllerArray.AppendElement(controller); /** * If the request is the first element of the quene, start from here. Note * that other request is pushed into the quene and is popped out after the * first one is completed. See NextBluetoothProfileController() for details. */ if (sControllerArray.Length() == 1) { sControllerArray[0]->StartSession(); } } void BluetoothDBusService::Connect(const nsAString& aDeviceAddress, uint32_t aCod, uint16_t aServiceUuid, BluetoothReplyRunnable* aRunnable) { ConnectDisconnect(true, aDeviceAddress, aRunnable, aServiceUuid, aCod); } void BluetoothDBusService::Disconnect(const nsAString& aDeviceAddress, uint16_t aServiceUuid, BluetoothReplyRunnable* aRunnable) { ConnectDisconnect(false, aDeviceAddress, aRunnable, aServiceUuid); } bool BluetoothDBusService::IsConnected(const uint16_t aServiceUuid) { MOZ_ASSERT(NS_IsMainThread()); BluetoothProfileManagerBase* profile = BluetoothUuidHelper::GetBluetoothProfileManager(aServiceUuid); if (!profile) { BT_WARNING(ERR_UNKNOWN_PROFILE); return false; } NS_ENSURE_TRUE(profile, false); return profile->IsConnected(); } #ifdef MOZ_B2G_RIL void BluetoothDBusService::AnswerWaitingCall(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); hfp->AnswerWaitingCall(); DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString()); } void BluetoothDBusService::IgnoreWaitingCall(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); hfp->IgnoreWaitingCall(); DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString()); } void BluetoothDBusService::ToggleCalls(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); hfp->ToggleCalls(); DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString()); } #endif // MOZ_B2G_RIL class OnUpdateSdpRecordsRunnable : public nsRunnable { public: OnUpdateSdpRecordsRunnable(const nsAString& aObjectPath, BluetoothProfileManagerBase* aManager) : mManager(aManager) { MOZ_ASSERT(!aObjectPath.IsEmpty()); MOZ_ASSERT(aManager); mDeviceAddress = GetAddressFromObjectPath(aObjectPath); } nsresult Run() { MOZ_ASSERT(NS_IsMainThread()); mManager->OnUpdateSdpRecords(mDeviceAddress); return NS_OK; } private: nsString mDeviceAddress; BluetoothProfileManagerBase* mManager; }; class OnGetServiceChannelRunnable : public nsRunnable { public: OnGetServiceChannelRunnable(const nsAString& aDeviceAddress, const nsAString& aServiceUuid, int aChannel, BluetoothProfileManagerBase* aManager) : mDeviceAddress(aDeviceAddress) , mServiceUuid(aServiceUuid) , mChannel(aChannel) , mManager(aManager) { MOZ_ASSERT(!aDeviceAddress.IsEmpty()); MOZ_ASSERT(!aServiceUuid.IsEmpty()); MOZ_ASSERT(aManager); } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); mManager->OnGetServiceChannel(mDeviceAddress, mServiceUuid, mChannel); return NS_OK; } private: nsString mDeviceAddress; nsString mServiceUuid; int mChannel; BluetoothProfileManagerBase* mManager; }; class OnGetServiceChannelReplyHandler : public DBusReplyHandler { public: OnGetServiceChannelReplyHandler(const nsAString& aDeviceAddress, const nsAString& aServiceUUID, BluetoothProfileManagerBase* aBluetoothProfileManager) : mDeviceAddress(aDeviceAddress), mServiceUUID(aServiceUUID), mBluetoothProfileManager(aBluetoothProfileManager) { MOZ_ASSERT(mBluetoothProfileManager); } void Handle(DBusMessage* aReply) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread // The default channel is an invalid value of -1. We // update it if we have received a correct reply. Both // cases, valid and invalid channel numbers, are handled // in BluetoothProfileManagerBase::OnGetServiceChannel. int channel = -1; if (aReply && (dbus_message_get_type(aReply) != DBUS_MESSAGE_TYPE_ERROR)) { channel = dbus_returns_int32(aReply); } nsRefPtr r = new OnGetServiceChannelRunnable(mDeviceAddress, mServiceUUID, channel, mBluetoothProfileManager); nsresult rv = NS_DispatchToMainThread(r); NS_ENSURE_SUCCESS_VOID(rv); } private: nsString mDeviceAddress; nsString mServiceUUID; BluetoothProfileManagerBase* mBluetoothProfileManager; }; class GetServiceChannelTask : public Task { public: GetServiceChannelTask(const nsAString& aDeviceAddress, const nsAString& aServiceUUID, BluetoothProfileManagerBase* aBluetoothProfileManager) : mDeviceAddress(aDeviceAddress) , mServiceUUID(aServiceUUID) , mBluetoothProfileManager(aBluetoothProfileManager) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); MOZ_ASSERT(mBluetoothProfileManager); } void Run() MOZ_OVERRIDE { static const int sProtocolDescriptorList = 0x0004; MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsString objectPath = GetObjectPathFromAddress(sAdapterPath, mDeviceAddress); nsRefPtr handler = new OnGetServiceChannelReplyHandler(mDeviceAddress, mServiceUUID, mBluetoothProfileManager); nsCString serviceUUID = NS_ConvertUTF16toUTF8(mServiceUUID); const char* cstrServiceUUID = serviceUUID.get(); bool success = sDBusConnection->SendWithReply( OnGetServiceChannelReplyHandler::Callback, handler, -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(objectPath).get(), DBUS_DEVICE_IFACE, "GetServiceAttributeValue", DBUS_TYPE_STRING, &cstrServiceUUID, DBUS_TYPE_UINT16, &sProtocolDescriptorList, DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << handler.forget(); // picked up by callback handler } private: nsString mDeviceAddress; nsString mServiceUUID; BluetoothProfileManagerBase* mBluetoothProfileManager; }; nsresult BluetoothDBusService::GetServiceChannel(const nsAString& aDeviceAddress, const nsAString& aServiceUUID, BluetoothProfileManagerBase* aManager) { MOZ_ASSERT(NS_IsMainThread()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); return NS_OK; } #ifdef MOZ_WIDGET_GONK // GetServiceAttributeValue only exists in android's bluez dbus binding // implementation Task* task = new GetServiceChannelTask(aDeviceAddress, aServiceUUID, aManager); DispatchToDBusThread(task); #else // FIXME/Bug 793977 qdot: Just set something for desktop, until we have a // parser for the GetServiceAttributes xml block // // Even though we are on the main thread already, we need to dispatch a // runnable here. OnGetServiceChannel needs mRunnable to be set, which // happens after GetServiceChannel returns. nsRefPtr r = new OnGetServiceChannelRunnable(aDeviceAddress, aServiceUUID, 1, aManager); NS_DispatchToMainThread(r); #endif return NS_OK; } class UpdateSdpRecordsTask : public Task { public: UpdateSdpRecordsTask(const nsAString& aDeviceAddress, BluetoothProfileManagerBase* aBluetoothProfileManager) : mDeviceAddress(aDeviceAddress) , mBluetoothProfileManager(aBluetoothProfileManager) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); MOZ_ASSERT(mBluetoothProfileManager); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsString objectPath = GetObjectPathFromAddress(sAdapterPath, mDeviceAddress); // I choose to use raw pointer here because this is going to be passed as an // argument into SendWithReply() at once. OnUpdateSdpRecordsRunnable* callbackRunnable = new OnUpdateSdpRecordsRunnable(objectPath, mBluetoothProfileManager); sDBusConnection->SendWithReply(DiscoverServicesCallback, (void*)callbackRunnable, -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(objectPath).get(), DBUS_DEVICE_IFACE, "DiscoverServices", DBUS_TYPE_STRING, &EmptyCString(), DBUS_TYPE_INVALID); } protected: static void DiscoverServicesCallback(DBusMessage* aMsg, void* aData) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread nsRefPtr r( static_cast(aData)); NS_DispatchToMainThread(r); } private: const nsString mDeviceAddress; BluetoothProfileManagerBase* mBluetoothProfileManager; }; bool BluetoothDBusService::UpdateSdpRecords(const nsAString& aDeviceAddress, BluetoothProfileManagerBase* aManager) { MOZ_ASSERT(NS_IsMainThread()); Task* task = new UpdateSdpRecordsTask(aDeviceAddress, aManager); DispatchToDBusThread(task); return true; } void BluetoothDBusService::SendFile(const nsAString& aDeviceAddress, BlobParent* aBlobParent, BlobChild* aBlobChild, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); // Currently we only support one device sending one file at a time, // so we don't need aDeviceAddress here because the target device // has been determined when calling 'Connect()'. Nevertheless, keep // it for future use. BluetoothOppManager* opp = BluetoothOppManager::Get(); nsAutoString errorStr; if (!opp || !opp->SendFile(aDeviceAddress, aBlobParent)) { errorStr.AssignLiteral("Calling SendFile() failed"); } DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr); } void BluetoothDBusService::SendFile(const nsAString& aDeviceAddress, nsIDOMBlob* aBlob, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); // Currently we only support one device sending one file at a time, // so we don't need aDeviceAddress here because the target device // has been determined when calling 'Connect()'. Nevertheless, keep // it for future use. BluetoothOppManager* opp = BluetoothOppManager::Get(); nsAutoString errorStr; if (!opp || !opp->SendFile(aDeviceAddress, aBlob)) { errorStr.AssignLiteral("Calling SendFile() failed"); } DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr); } void BluetoothDBusService::StopSendingFile(const nsAString& aDeviceAddress, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); // Currently we only support one device sending one file at a time, // so we don't need aDeviceAddress here because the target device // has been determined when calling 'Connect()'. Nevertheless, keep // it for future use. BluetoothOppManager* opp = BluetoothOppManager::Get(); nsAutoString errorStr; if (!opp || !opp->StopSendingFile()) { errorStr.AssignLiteral("Calling StopSendingFile() failed"); } DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr); } void BluetoothDBusService::ConfirmReceivingFile(const nsAString& aDeviceAddress, bool aConfirm, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread(), "Must be called from main thread!"); // Currently we only support one device sending one file at a time, // so we don't need aDeviceAddress here because the target device // has been determined when calling 'Connect()'. Nevertheless, keep // it for future use. BluetoothOppManager* opp = BluetoothOppManager::Get(); nsAutoString errorStr; if (!opp || !opp->ConfirmReceivingFile(aConfirm)) { errorStr.AssignLiteral("Calling ConfirmReceivingFile() failed"); } DispatchBluetoothReply(aRunnable, BluetoothValue(true), errorStr); } void BluetoothDBusService::ConnectSco(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); if (!hfp || !hfp->ConnectSco(aRunnable)) { NS_NAMED_LITERAL_STRING(replyError, "Calling ConnectSco() failed"); DispatchBluetoothReply(aRunnable, BluetoothValue(), replyError); } } void BluetoothDBusService::DisconnectSco(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); if (!hfp || !hfp->DisconnectSco()) { NS_NAMED_LITERAL_STRING(replyError, "Calling DisconnectSco() failed"); DispatchBluetoothReply(aRunnable, BluetoothValue(), replyError); return; } DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString()); } void BluetoothDBusService::IsScoConnected(BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); BluetoothHfpManager* hfp = BluetoothHfpManager::Get(); if (!hfp) { NS_NAMED_LITERAL_STRING(replyError, "Fail to get BluetoothHfpManager"); DispatchBluetoothReply(aRunnable, BluetoothValue(), replyError); return; } DispatchBluetoothReply(aRunnable, hfp->IsScoConnected(), EmptyString()); } class SendMetadataTask : public Task { public: SendMetadataTask(const nsAString& aDeviceAddress, const nsACString& aTitle, const nsACString& aArtist, const nsACString& aAlbum, int64_t aMediaNumber, int64_t aTotalMediaCount, int64_t aDuration, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mTitle(aTitle) , mArtist(aArtist) , mAlbum(aAlbum) , mMediaNumber(aMediaNumber) , mTotalMediaCount(aTotalMediaCount) , mDuration(aDuration) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); // We currently don't support genre field in music player. // In order to send media metadata through AVRCP, we set genre to an empty // string to match the BlueZ method "UpdateMetaData" with signature "sssssss", // which takes genre field as the last parameter. nsCString tempGenre = EmptyCString(); nsCString tempMediaNumber = EmptyCString(); nsCString tempTotalMediaCount = EmptyCString(); nsCString tempDuration = EmptyCString(); if (mMediaNumber >= 0) { tempMediaNumber.AppendInt(mMediaNumber); } if (mTotalMediaCount >= 0) { tempTotalMediaCount.AppendInt(mTotalMediaCount); } if (mDuration >= 0) { tempDuration.AppendInt(mDuration); } const nsCString objectPath = NS_ConvertUTF16toUTF8( GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); const char* title = mTitle.get(); const char* album = mAlbum.get(); const char* artist = mArtist.get(); const char* mediaNumber = tempMediaNumber.get(); const char* totalMediaCount = tempTotalMediaCount.get(); const char* duration = tempDuration.get(); const char* genre = tempGenre.get(); bool success = sDBusConnection->SendWithReply( GetVoidCallback, static_cast(mRunnable.get()), -1, BLUEZ_DBUS_BASE_IFC, objectPath.get(), DBUS_CTL_IFACE, "UpdateMetaData", DBUS_TYPE_STRING, &title, DBUS_TYPE_STRING, &artist, DBUS_TYPE_STRING, &album, DBUS_TYPE_STRING, &mediaNumber, DBUS_TYPE_STRING, &totalMediaCount, DBUS_TYPE_STRING, &duration, DBUS_TYPE_STRING, &genre, DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << mRunnable.forget(); // picked up by callback handler } private: const nsString mDeviceAddress; const nsCString mTitle; const nsCString mArtist; const nsCString mAlbum; int64_t mMediaNumber; int64_t mTotalMediaCount; int64_t mDuration; nsRefPtr mRunnable; }; void BluetoothDBusService::SendMetaData(const nsAString& aTitle, const nsAString& aArtist, const nsAString& aAlbum, int64_t aMediaNumber, int64_t aTotalMediaCount, int64_t aDuration, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return; } BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); NS_ENSURE_TRUE_VOID(a2dp); if (!a2dp->IsConnected()) { DispatchBluetoothReply(aRunnable, BluetoothValue(), NS_LITERAL_STRING(ERR_A2DP_IS_DISCONNECTED)); return; } else if (!a2dp->IsAvrcpConnected()) { DispatchBluetoothReply(aRunnable, BluetoothValue(), NS_LITERAL_STRING(ERR_AVRCP_IS_DISCONNECTED)); return; } nsAutoString prevTitle, prevAlbum; a2dp->GetTitle(prevTitle); a2dp->GetAlbum(prevAlbum); if (aMediaNumber != a2dp->GetMediaNumber() || !aTitle.Equals(prevTitle) || !aAlbum.Equals(prevAlbum)) { UpdateNotification(ControlEventId::EVENT_TRACK_CHANGED, aMediaNumber); } nsAutoString deviceAddress; a2dp->GetAddress(deviceAddress); Task* task = new SendMetadataTask( deviceAddress, NS_ConvertUTF16toUTF8(aTitle), NS_ConvertUTF16toUTF8(aArtist), NS_ConvertUTF16toUTF8(aAlbum), aMediaNumber, aTotalMediaCount, aDuration, aRunnable); DispatchToDBusThread(task); a2dp->UpdateMetaData(aTitle, aArtist, aAlbum, aMediaNumber, aTotalMediaCount, aDuration); } static ControlPlayStatus PlayStatusStringToControlPlayStatus(const nsAString& aPlayStatus) { ControlPlayStatus playStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN; if (aPlayStatus.EqualsLiteral("STOPPED")) { playStatus = ControlPlayStatus::PLAYSTATUS_STOPPED; } else if (aPlayStatus.EqualsLiteral("PLAYING")) { playStatus = ControlPlayStatus::PLAYSTATUS_PLAYING; } else if (aPlayStatus.EqualsLiteral("PAUSED")) { playStatus = ControlPlayStatus::PLAYSTATUS_PAUSED; } else if (aPlayStatus.EqualsLiteral("FWD_SEEK")) { playStatus = ControlPlayStatus::PLAYSTATUS_FWD_SEEK; } else if (aPlayStatus.EqualsLiteral("REV_SEEK")) { playStatus = ControlPlayStatus::PLAYSTATUS_REV_SEEK; } else if (aPlayStatus.EqualsLiteral("ERROR")) { playStatus = ControlPlayStatus::PLAYSTATUS_ERROR; } return playStatus; } class SendPlayStatusTask : public Task { public: SendPlayStatusTask(const nsAString& aDeviceAddress, int64_t aDuration, int64_t aPosition, ControlPlayStatus aPlayStatus, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mDuration(aDuration) , mPosition(aPosition) , mPlayStatus(aPlayStatus) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); MOZ_ASSERT(mRunnable); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsCString objectPath = NS_ConvertUTF16toUTF8( GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); uint32_t tempPlayStatus = mPlayStatus; bool success = sDBusConnection->SendWithReply( GetVoidCallback, static_cast(mRunnable.get()), -1, BLUEZ_DBUS_BASE_IFC, objectPath.get(), DBUS_CTL_IFACE, "UpdatePlayStatus", DBUS_TYPE_UINT32, &mDuration, DBUS_TYPE_UINT32, &mPosition, DBUS_TYPE_UINT32, &tempPlayStatus, DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); unused << mRunnable.forget(); // picked up by callback handler } private: const nsString mDeviceAddress; int64_t mDuration; int64_t mPosition; ControlPlayStatus mPlayStatus; nsRefPtr mRunnable; }; void BluetoothDBusService::SendPlayStatus(int64_t aDuration, int64_t aPosition, const nsAString& aPlayStatus, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return; } ControlPlayStatus playStatus = PlayStatusStringToControlPlayStatus(aPlayStatus); if (playStatus == ControlPlayStatus::PLAYSTATUS_UNKNOWN) { DispatchBluetoothReply(aRunnable, BluetoothValue(), NS_LITERAL_STRING("Invalid play status")); return; } else if (aDuration < 0) { DispatchBluetoothReply(aRunnable, BluetoothValue(), NS_LITERAL_STRING("Invalid duration")); return; } else if (aPosition < 0) { DispatchBluetoothReply(aRunnable, BluetoothValue(), NS_LITERAL_STRING("Invalid position")); return; } BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); NS_ENSURE_TRUE_VOID(a2dp); if (!a2dp->IsConnected()) { DispatchBluetoothReply(aRunnable, BluetoothValue(), NS_LITERAL_STRING(ERR_A2DP_IS_DISCONNECTED)); return; } else if (!a2dp->IsAvrcpConnected()) { DispatchBluetoothReply(aRunnable, BluetoothValue(), NS_LITERAL_STRING(ERR_AVRCP_IS_DISCONNECTED)); return; } if (playStatus != a2dp->GetPlayStatus()) { UpdateNotification(ControlEventId::EVENT_PLAYBACK_STATUS_CHANGED, playStatus); } else if (aPosition != a2dp->GetPosition()) { UpdateNotification(ControlEventId::EVENT_PLAYBACK_POS_CHANGED, aPosition); } nsAutoString deviceAddress; a2dp->GetAddress(deviceAddress); Task* task = new SendPlayStatusTask(deviceAddress, aDuration, aPosition, playStatus, aRunnable); DispatchToDBusThread(task); a2dp->UpdatePlayStatus(aDuration, aPosition, playStatus); } static void ControlCallback(DBusMessage* aMsg, void* aParam) { NS_ENSURE_TRUE_VOID(aMsg); BluetoothValue v; nsAutoString replyError; UnpackVoidMessage(aMsg, nullptr, v, replyError); if (!v.get_bool()) { BT_WARNING(NS_ConvertUTF16toUTF8(replyError).get()); } } class UpdatePlayStatusTask : public Task { public: UpdatePlayStatusTask(const nsAString& aDeviceAddress, int32_t aDuration, int32_t aPosition, ControlPlayStatus aPlayStatus) : mDeviceAddress(aDeviceAddress) , mDuration(aDuration) , mPosition(aPosition) , mPlayStatus(aPlayStatus) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsCString objectPath = NS_ConvertUTF16toUTF8( GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); uint32_t tempPlayStatus = mPlayStatus; bool success = sDBusConnection->SendWithReply( ControlCallback, nullptr, -1, BLUEZ_DBUS_BASE_IFC, objectPath.get(), DBUS_CTL_IFACE, "UpdatePlayStatus", DBUS_TYPE_UINT32, &mDuration, DBUS_TYPE_UINT32, &mPosition, DBUS_TYPE_UINT32, &tempPlayStatus, DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); } private: const nsString mDeviceAddress; int32_t mDuration; int32_t mPosition; ControlPlayStatus mPlayStatus; }; void BluetoothDBusService::UpdatePlayStatus(uint32_t aDuration, uint32_t aPosition, ControlPlayStatus aPlayStatus) { MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_TRUE_VOID(this->IsReady()); BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); NS_ENSURE_TRUE_VOID(a2dp); MOZ_ASSERT(a2dp->IsConnected()); MOZ_ASSERT(a2dp->IsAvrcpConnected()); MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsAutoString deviceAddress; a2dp->GetAddress(deviceAddress); Task* task = new UpdatePlayStatusTask(deviceAddress, aDuration, aPosition, aPlayStatus); DispatchToDBusThread(task); } class UpdateNotificationTask : public Task { public: UpdateNotificationTask(const nsAString& aDeviceAddress, BluetoothDBusService::ControlEventId aEventId, uint64_t aData) : mDeviceAddress(aDeviceAddress) , mEventId(aEventId) , mData(aData) { MOZ_ASSERT(!mDeviceAddress.IsEmpty()); } void Run() MOZ_OVERRIDE { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsCString objectPath = NS_ConvertUTF16toUTF8( GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); uint16_t eventId = mEventId; bool success = sDBusConnection->SendWithReply( ControlCallback, nullptr, -1, BLUEZ_DBUS_BASE_IFC, objectPath.get(), DBUS_CTL_IFACE, "UpdateNotification", DBUS_TYPE_UINT16, &eventId, DBUS_TYPE_UINT64, &mData, DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); } private: const nsString mDeviceAddress; int16_t mEventId; int32_t mData; }; void BluetoothDBusService::UpdateNotification(ControlEventId aEventId, uint64_t aData) { MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_TRUE_VOID(this->IsReady()); BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get(); NS_ENSURE_TRUE_VOID(a2dp); MOZ_ASSERT(a2dp->IsConnected()); MOZ_ASSERT(a2dp->IsAvrcpConnected()); MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsAutoString deviceAddress; a2dp->GetAddress(deviceAddress); Task* task = new UpdateNotificationTask(deviceAddress, aEventId, aData); DispatchToDBusThread(task); }