1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/bluetooth/BluetoothProfileController.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,314 @@ 1.4 +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ 1.5 +/* vim: set ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "BluetoothProfileController.h" 1.11 +#include "BluetoothReplyRunnable.h" 1.12 + 1.13 +#include "BluetoothA2dpManager.h" 1.14 +#include "BluetoothHfpManager.h" 1.15 +#include "BluetoothHidManager.h" 1.16 +#include "BluetoothUtils.h" 1.17 + 1.18 +#include "mozilla/dom/bluetooth/BluetoothTypes.h" 1.19 +#include "nsComponentManagerUtils.h" 1.20 + 1.21 +USING_BLUETOOTH_NAMESPACE 1.22 + 1.23 +#define BT_LOGR_PROFILE(mgr, msg, ...) \ 1.24 + do { \ 1.25 + nsCString name; \ 1.26 + mgr->GetName(name); \ 1.27 + BT_LOGR("[%s] " msg, name.get(), ##__VA_ARGS__); \ 1.28 + } while(0) 1.29 + 1.30 +#define CONNECTION_TIMEOUT_MS 15000 1.31 + 1.32 +class CheckProfileStatusCallback : public nsITimerCallback 1.33 +{ 1.34 +public: 1.35 + NS_DECL_ISUPPORTS 1.36 + NS_DECL_NSITIMERCALLBACK 1.37 + 1.38 + CheckProfileStatusCallback(BluetoothProfileController* aController) 1.39 + : mController(aController) 1.40 + { 1.41 + MOZ_ASSERT(aController); 1.42 + } 1.43 + 1.44 + virtual ~CheckProfileStatusCallback() 1.45 + { 1.46 + mController = nullptr; 1.47 + } 1.48 + 1.49 +private: 1.50 + nsRefPtr<BluetoothProfileController> mController; 1.51 +}; 1.52 + 1.53 +BluetoothProfileController::BluetoothProfileController( 1.54 + bool aConnect, 1.55 + const nsAString& aDeviceAddress, 1.56 + BluetoothReplyRunnable* aRunnable, 1.57 + BluetoothProfileControllerCallback aCallback, 1.58 + uint16_t aServiceUuid, 1.59 + uint32_t aCod) 1.60 + : mConnect(aConnect) 1.61 + , mDeviceAddress(aDeviceAddress) 1.62 + , mRunnable(aRunnable) 1.63 + , mCallback(aCallback) 1.64 + , mCurrentProfileFinished(false) 1.65 + , mSuccess(false) 1.66 + , mProfilesIndex(-1) 1.67 +{ 1.68 + MOZ_ASSERT(!aDeviceAddress.IsEmpty()); 1.69 + MOZ_ASSERT(aRunnable); 1.70 + MOZ_ASSERT(aCallback); 1.71 + 1.72 + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); 1.73 + MOZ_ASSERT(mTimer); 1.74 + 1.75 + mCheckProfileStatusCallback = new CheckProfileStatusCallback(this); 1.76 + mProfiles.Clear(); 1.77 + 1.78 + /** 1.79 + * If the service uuid is not specified, either connect multiple profiles 1.80 + * based on Cod, or disconnect all connected profiles. 1.81 + */ 1.82 + if (!aServiceUuid) { 1.83 + mTarget.cod = aCod; 1.84 + SetupProfiles(false); 1.85 + } else { 1.86 + BluetoothServiceClass serviceClass = 1.87 + BluetoothUuidHelper::GetBluetoothServiceClass(aServiceUuid); 1.88 + mTarget.service = serviceClass; 1.89 + SetupProfiles(true); 1.90 + } 1.91 +} 1.92 + 1.93 +BluetoothProfileController::~BluetoothProfileController() 1.94 +{ 1.95 + mProfiles.Clear(); 1.96 + mRunnable = nullptr; 1.97 + mCallback = nullptr; 1.98 + 1.99 + if (mTimer) { 1.100 + mTimer->Cancel(); 1.101 + } 1.102 +} 1.103 + 1.104 +void 1.105 +BluetoothProfileController::AddProfileWithServiceClass( 1.106 + BluetoothServiceClass aClass) 1.107 +{ 1.108 + BluetoothProfileManagerBase* profile; 1.109 + switch (aClass) { 1.110 + case BluetoothServiceClass::HANDSFREE: 1.111 + case BluetoothServiceClass::HEADSET: 1.112 + profile = BluetoothHfpManager::Get(); 1.113 + break; 1.114 + case BluetoothServiceClass::A2DP: 1.115 + profile = BluetoothA2dpManager::Get(); 1.116 + break; 1.117 + case BluetoothServiceClass::HID: 1.118 + profile = BluetoothHidManager::Get(); 1.119 + break; 1.120 + default: 1.121 + DispatchBluetoothReply(mRunnable, BluetoothValue(), 1.122 + NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE)); 1.123 + mCallback(); 1.124 + return; 1.125 + } 1.126 + 1.127 + AddProfile(profile); 1.128 +} 1.129 + 1.130 +void 1.131 +BluetoothProfileController::AddProfile(BluetoothProfileManagerBase* aProfile, 1.132 + bool aCheckConnected) 1.133 +{ 1.134 + if (!aProfile) { 1.135 + DispatchBluetoothReply(mRunnable, BluetoothValue(), 1.136 + NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE)); 1.137 + mCallback(); 1.138 + return; 1.139 + } 1.140 + 1.141 + if (aCheckConnected && !aProfile->IsConnected()) { 1.142 + BT_WARNING("The profile is not connected."); 1.143 + return; 1.144 + } 1.145 + 1.146 + mProfiles.AppendElement(aProfile); 1.147 +} 1.148 + 1.149 +void 1.150 +BluetoothProfileController::SetupProfiles(bool aAssignServiceClass) 1.151 +{ 1.152 + MOZ_ASSERT(NS_IsMainThread()); 1.153 + 1.154 + /** 1.155 + * When a service class is assigned, only its corresponding profile is put 1.156 + * into array. 1.157 + */ 1.158 + if (aAssignServiceClass) { 1.159 + AddProfileWithServiceClass(mTarget.service); 1.160 + return; 1.161 + } 1.162 + 1.163 + // For a disconnect request, all connected profiles are put into array. 1.164 + if (!mConnect) { 1.165 + AddProfile(BluetoothHidManager::Get(), true); 1.166 + AddProfile(BluetoothA2dpManager::Get(), true); 1.167 + AddProfile(BluetoothHfpManager::Get(), true); 1.168 + return; 1.169 + } 1.170 + 1.171 + /** 1.172 + * For a connect request, put multiple profiles into array and connect to 1.173 + * all of them sequencely. 1.174 + */ 1.175 + bool hasAudio = HAS_AUDIO(mTarget.cod); 1.176 + bool hasRendering = HAS_RENDERING(mTarget.cod); 1.177 + bool isPeripheral = IS_PERIPHERAL(mTarget.cod); 1.178 + bool isRemoteControl = IS_REMOTE_CONTROL(mTarget.cod); 1.179 + bool isKeyboard = IS_KEYBOARD(mTarget.cod); 1.180 + bool isPointingDevice = IS_POINTING_DEVICE(mTarget.cod); 1.181 + 1.182 + NS_ENSURE_TRUE_VOID(hasAudio || hasRendering || isPeripheral); 1.183 + 1.184 + // Audio bit should be set if remote device supports HFP/HSP. 1.185 + if (hasAudio) { 1.186 + AddProfile(BluetoothHfpManager::Get()); 1.187 + } 1.188 + 1.189 + // Rendering bit should be set if remote device supports A2DP. 1.190 + // A device which supports AVRCP should claim that it's a peripheral and it's 1.191 + // a remote control. 1.192 + if (hasRendering || (isPeripheral && isRemoteControl)) { 1.193 + AddProfile(BluetoothA2dpManager::Get()); 1.194 + } 1.195 + 1.196 + // A device which supports HID should claim that it's a peripheral and it's 1.197 + // either a keyboard, a pointing device, or both. 1.198 + if (isPeripheral && (isKeyboard || isPointingDevice)) { 1.199 + AddProfile(BluetoothHidManager::Get()); 1.200 + } 1.201 +} 1.202 + 1.203 +NS_IMPL_ISUPPORTS(CheckProfileStatusCallback, nsITimerCallback) 1.204 + 1.205 +NS_IMETHODIMP 1.206 +CheckProfileStatusCallback::Notify(nsITimer* aTimer) 1.207 +{ 1.208 + MOZ_ASSERT(mController); 1.209 + // Continue on the next profile since we haven't got the callback after 1.210 + // timeout. 1.211 + mController->GiveupAndContinue(); 1.212 + 1.213 + return NS_OK; 1.214 +} 1.215 + 1.216 +void 1.217 +BluetoothProfileController::StartSession() 1.218 +{ 1.219 + MOZ_ASSERT(NS_IsMainThread()); 1.220 + MOZ_ASSERT(!mDeviceAddress.IsEmpty()); 1.221 + MOZ_ASSERT(mProfilesIndex == -1); 1.222 + MOZ_ASSERT(mTimer); 1.223 + 1.224 + if (mProfiles.Length() < 1) { 1.225 + BT_LOGR("No queued profile."); 1.226 + EndSession(); 1.227 + return; 1.228 + } 1.229 + 1.230 + if (mTimer) { 1.231 + mTimer->InitWithCallback(mCheckProfileStatusCallback, CONNECTION_TIMEOUT_MS, 1.232 + nsITimer::TYPE_ONE_SHOT); 1.233 + } 1.234 + 1.235 + BT_LOGR("%s", mConnect ? "connecting" : "disconnecting"); 1.236 + 1.237 + Next(); 1.238 +} 1.239 + 1.240 +void 1.241 +BluetoothProfileController::EndSession() 1.242 +{ 1.243 + MOZ_ASSERT(mRunnable && mCallback); 1.244 + 1.245 + BT_LOGR("mSuccess %d", mSuccess); 1.246 + 1.247 + // The action has completed, so the DOM request should be replied then invoke 1.248 + // the callback. 1.249 + if (mSuccess) { 1.250 + DispatchBluetoothReply(mRunnable, BluetoothValue(true), EmptyString()); 1.251 + } else if (mConnect) { 1.252 + DispatchBluetoothReply(mRunnable, BluetoothValue(true), 1.253 + NS_LITERAL_STRING(ERR_CONNECTION_FAILED)); 1.254 + } else { 1.255 + DispatchBluetoothReply(mRunnable, BluetoothValue(true), 1.256 + NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED)); 1.257 + } 1.258 + 1.259 + mCallback(); 1.260 +} 1.261 + 1.262 +void 1.263 +BluetoothProfileController::Next() 1.264 +{ 1.265 + MOZ_ASSERT(NS_IsMainThread()); 1.266 + MOZ_ASSERT(!mDeviceAddress.IsEmpty()); 1.267 + MOZ_ASSERT(mProfilesIndex < (int)mProfiles.Length()); 1.268 + MOZ_ASSERT(mTimer); 1.269 + 1.270 + mCurrentProfileFinished = false; 1.271 + 1.272 + if (++mProfilesIndex >= (int)mProfiles.Length()) { 1.273 + EndSession(); 1.274 + return; 1.275 + } 1.276 + 1.277 + BT_LOGR_PROFILE(mProfiles[mProfilesIndex], ""); 1.278 + 1.279 + if (mConnect) { 1.280 + mProfiles[mProfilesIndex]->Connect(mDeviceAddress, this); 1.281 + } else { 1.282 + mProfiles[mProfilesIndex]->Disconnect(this); 1.283 + } 1.284 +} 1.285 + 1.286 +void 1.287 +BluetoothProfileController::NotifyCompletion(const nsAString& aErrorStr) 1.288 +{ 1.289 + MOZ_ASSERT(NS_IsMainThread()); 1.290 + MOZ_ASSERT(mTimer); 1.291 + MOZ_ASSERT(mProfiles.Length() > 0); 1.292 + 1.293 + BT_LOGR_PROFILE(mProfiles[mProfilesIndex], "<%s>", 1.294 + NS_ConvertUTF16toUTF8(aErrorStr).get()); 1.295 + 1.296 + mCurrentProfileFinished = true; 1.297 + 1.298 + if (mTimer) { 1.299 + mTimer->Cancel(); 1.300 + } 1.301 + 1.302 + mSuccess |= aErrorStr.IsEmpty(); 1.303 + 1.304 + Next(); 1.305 +} 1.306 + 1.307 +void 1.308 +BluetoothProfileController::GiveupAndContinue() 1.309 +{ 1.310 + MOZ_ASSERT(!mCurrentProfileFinished); 1.311 + MOZ_ASSERT(mProfilesIndex < (int)mProfiles.Length()); 1.312 + 1.313 + BT_LOGR_PROFILE(mProfiles[mProfilesIndex], ERR_OPERATION_TIMEOUT); 1.314 + mProfiles[mProfilesIndex]->Reset(); 1.315 + Next(); 1.316 +} 1.317 +