Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "BluetoothProfileController.h"
8 #include "BluetoothReplyRunnable.h"
10 #include "BluetoothA2dpManager.h"
11 #include "BluetoothHfpManager.h"
12 #include "BluetoothHidManager.h"
13 #include "BluetoothUtils.h"
15 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
16 #include "nsComponentManagerUtils.h"
18 USING_BLUETOOTH_NAMESPACE
20 #define BT_LOGR_PROFILE(mgr, msg, ...) \
21 do { \
22 nsCString name; \
23 mgr->GetName(name); \
24 BT_LOGR("[%s] " msg, name.get(), ##__VA_ARGS__); \
25 } while(0)
27 #define CONNECTION_TIMEOUT_MS 15000
29 class CheckProfileStatusCallback : public nsITimerCallback
30 {
31 public:
32 NS_DECL_ISUPPORTS
33 NS_DECL_NSITIMERCALLBACK
35 CheckProfileStatusCallback(BluetoothProfileController* aController)
36 : mController(aController)
37 {
38 MOZ_ASSERT(aController);
39 }
41 virtual ~CheckProfileStatusCallback()
42 {
43 mController = nullptr;
44 }
46 private:
47 nsRefPtr<BluetoothProfileController> mController;
48 };
50 BluetoothProfileController::BluetoothProfileController(
51 bool aConnect,
52 const nsAString& aDeviceAddress,
53 BluetoothReplyRunnable* aRunnable,
54 BluetoothProfileControllerCallback aCallback,
55 uint16_t aServiceUuid,
56 uint32_t aCod)
57 : mConnect(aConnect)
58 , mDeviceAddress(aDeviceAddress)
59 , mRunnable(aRunnable)
60 , mCallback(aCallback)
61 , mCurrentProfileFinished(false)
62 , mSuccess(false)
63 , mProfilesIndex(-1)
64 {
65 MOZ_ASSERT(!aDeviceAddress.IsEmpty());
66 MOZ_ASSERT(aRunnable);
67 MOZ_ASSERT(aCallback);
69 mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
70 MOZ_ASSERT(mTimer);
72 mCheckProfileStatusCallback = new CheckProfileStatusCallback(this);
73 mProfiles.Clear();
75 /**
76 * If the service uuid is not specified, either connect multiple profiles
77 * based on Cod, or disconnect all connected profiles.
78 */
79 if (!aServiceUuid) {
80 mTarget.cod = aCod;
81 SetupProfiles(false);
82 } else {
83 BluetoothServiceClass serviceClass =
84 BluetoothUuidHelper::GetBluetoothServiceClass(aServiceUuid);
85 mTarget.service = serviceClass;
86 SetupProfiles(true);
87 }
88 }
90 BluetoothProfileController::~BluetoothProfileController()
91 {
92 mProfiles.Clear();
93 mRunnable = nullptr;
94 mCallback = nullptr;
96 if (mTimer) {
97 mTimer->Cancel();
98 }
99 }
101 void
102 BluetoothProfileController::AddProfileWithServiceClass(
103 BluetoothServiceClass aClass)
104 {
105 BluetoothProfileManagerBase* profile;
106 switch (aClass) {
107 case BluetoothServiceClass::HANDSFREE:
108 case BluetoothServiceClass::HEADSET:
109 profile = BluetoothHfpManager::Get();
110 break;
111 case BluetoothServiceClass::A2DP:
112 profile = BluetoothA2dpManager::Get();
113 break;
114 case BluetoothServiceClass::HID:
115 profile = BluetoothHidManager::Get();
116 break;
117 default:
118 DispatchBluetoothReply(mRunnable, BluetoothValue(),
119 NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE));
120 mCallback();
121 return;
122 }
124 AddProfile(profile);
125 }
127 void
128 BluetoothProfileController::AddProfile(BluetoothProfileManagerBase* aProfile,
129 bool aCheckConnected)
130 {
131 if (!aProfile) {
132 DispatchBluetoothReply(mRunnable, BluetoothValue(),
133 NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
134 mCallback();
135 return;
136 }
138 if (aCheckConnected && !aProfile->IsConnected()) {
139 BT_WARNING("The profile is not connected.");
140 return;
141 }
143 mProfiles.AppendElement(aProfile);
144 }
146 void
147 BluetoothProfileController::SetupProfiles(bool aAssignServiceClass)
148 {
149 MOZ_ASSERT(NS_IsMainThread());
151 /**
152 * When a service class is assigned, only its corresponding profile is put
153 * into array.
154 */
155 if (aAssignServiceClass) {
156 AddProfileWithServiceClass(mTarget.service);
157 return;
158 }
160 // For a disconnect request, all connected profiles are put into array.
161 if (!mConnect) {
162 AddProfile(BluetoothHidManager::Get(), true);
163 AddProfile(BluetoothA2dpManager::Get(), true);
164 AddProfile(BluetoothHfpManager::Get(), true);
165 return;
166 }
168 /**
169 * For a connect request, put multiple profiles into array and connect to
170 * all of them sequencely.
171 */
172 bool hasAudio = HAS_AUDIO(mTarget.cod);
173 bool hasRendering = HAS_RENDERING(mTarget.cod);
174 bool isPeripheral = IS_PERIPHERAL(mTarget.cod);
175 bool isRemoteControl = IS_REMOTE_CONTROL(mTarget.cod);
176 bool isKeyboard = IS_KEYBOARD(mTarget.cod);
177 bool isPointingDevice = IS_POINTING_DEVICE(mTarget.cod);
179 NS_ENSURE_TRUE_VOID(hasAudio || hasRendering || isPeripheral);
181 // Audio bit should be set if remote device supports HFP/HSP.
182 if (hasAudio) {
183 AddProfile(BluetoothHfpManager::Get());
184 }
186 // Rendering bit should be set if remote device supports A2DP.
187 // A device which supports AVRCP should claim that it's a peripheral and it's
188 // a remote control.
189 if (hasRendering || (isPeripheral && isRemoteControl)) {
190 AddProfile(BluetoothA2dpManager::Get());
191 }
193 // A device which supports HID should claim that it's a peripheral and it's
194 // either a keyboard, a pointing device, or both.
195 if (isPeripheral && (isKeyboard || isPointingDevice)) {
196 AddProfile(BluetoothHidManager::Get());
197 }
198 }
200 NS_IMPL_ISUPPORTS(CheckProfileStatusCallback, nsITimerCallback)
202 NS_IMETHODIMP
203 CheckProfileStatusCallback::Notify(nsITimer* aTimer)
204 {
205 MOZ_ASSERT(mController);
206 // Continue on the next profile since we haven't got the callback after
207 // timeout.
208 mController->GiveupAndContinue();
210 return NS_OK;
211 }
213 void
214 BluetoothProfileController::StartSession()
215 {
216 MOZ_ASSERT(NS_IsMainThread());
217 MOZ_ASSERT(!mDeviceAddress.IsEmpty());
218 MOZ_ASSERT(mProfilesIndex == -1);
219 MOZ_ASSERT(mTimer);
221 if (mProfiles.Length() < 1) {
222 BT_LOGR("No queued profile.");
223 EndSession();
224 return;
225 }
227 if (mTimer) {
228 mTimer->InitWithCallback(mCheckProfileStatusCallback, CONNECTION_TIMEOUT_MS,
229 nsITimer::TYPE_ONE_SHOT);
230 }
232 BT_LOGR("%s", mConnect ? "connecting" : "disconnecting");
234 Next();
235 }
237 void
238 BluetoothProfileController::EndSession()
239 {
240 MOZ_ASSERT(mRunnable && mCallback);
242 BT_LOGR("mSuccess %d", mSuccess);
244 // The action has completed, so the DOM request should be replied then invoke
245 // the callback.
246 if (mSuccess) {
247 DispatchBluetoothReply(mRunnable, BluetoothValue(true), EmptyString());
248 } else if (mConnect) {
249 DispatchBluetoothReply(mRunnable, BluetoothValue(true),
250 NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
251 } else {
252 DispatchBluetoothReply(mRunnable, BluetoothValue(true),
253 NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED));
254 }
256 mCallback();
257 }
259 void
260 BluetoothProfileController::Next()
261 {
262 MOZ_ASSERT(NS_IsMainThread());
263 MOZ_ASSERT(!mDeviceAddress.IsEmpty());
264 MOZ_ASSERT(mProfilesIndex < (int)mProfiles.Length());
265 MOZ_ASSERT(mTimer);
267 mCurrentProfileFinished = false;
269 if (++mProfilesIndex >= (int)mProfiles.Length()) {
270 EndSession();
271 return;
272 }
274 BT_LOGR_PROFILE(mProfiles[mProfilesIndex], "");
276 if (mConnect) {
277 mProfiles[mProfilesIndex]->Connect(mDeviceAddress, this);
278 } else {
279 mProfiles[mProfilesIndex]->Disconnect(this);
280 }
281 }
283 void
284 BluetoothProfileController::NotifyCompletion(const nsAString& aErrorStr)
285 {
286 MOZ_ASSERT(NS_IsMainThread());
287 MOZ_ASSERT(mTimer);
288 MOZ_ASSERT(mProfiles.Length() > 0);
290 BT_LOGR_PROFILE(mProfiles[mProfilesIndex], "<%s>",
291 NS_ConvertUTF16toUTF8(aErrorStr).get());
293 mCurrentProfileFinished = true;
295 if (mTimer) {
296 mTimer->Cancel();
297 }
299 mSuccess |= aErrorStr.IsEmpty();
301 Next();
302 }
304 void
305 BluetoothProfileController::GiveupAndContinue()
306 {
307 MOZ_ASSERT(!mCurrentProfileFinished);
308 MOZ_ASSERT(mProfilesIndex < (int)mProfiles.Length());
310 BT_LOGR_PROFILE(mProfiles[mProfilesIndex], ERR_OPERATION_TIMEOUT);
311 mProfiles[mProfilesIndex]->Reset();
312 Next();
313 }