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"
9 #include "BluetoothA2dpManager.h"
10 #include "BluetoothHfpManager.h"
11 #include "BluetoothHidManager.h"
12 #include "BluetoothReplyRunnable.h"
13 #include "BluetoothService.h"
14 #include "BluetoothUtils.h"
16 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
17 #include "nsComponentManagerUtils.h"
19 USING_BLUETOOTH_NAMESPACE
21 #define BT_LOGR_PROFILE(mgr, msg, ...) \
25 BT_LOGR("[%s] " msg, name.get(), ##__VA_ARGS__); \
28 #define CONNECTION_TIMEOUT_MS 15000
30 class CheckProfileStatusCallback
: public nsITimerCallback
34 NS_DECL_NSITIMERCALLBACK
36 CheckProfileStatusCallback(BluetoothProfileController
* aController
)
37 : mController(aController
)
39 MOZ_ASSERT(aController
);
42 virtual ~CheckProfileStatusCallback()
44 mController
= nullptr;
48 nsRefPtr
<BluetoothProfileController
> mController
;
51 BluetoothProfileController::BluetoothProfileController(
53 const nsAString
& aDeviceAddress
,
54 BluetoothReplyRunnable
* aRunnable
,
55 BluetoothProfileControllerCallback aCallback
,
56 uint16_t aServiceUuid
,
59 , mDeviceAddress(aDeviceAddress
)
60 , mRunnable(aRunnable
)
61 , mCallback(aCallback
)
62 , mCurrentProfileFinished(false)
66 MOZ_ASSERT(!aDeviceAddress
.IsEmpty());
67 MOZ_ASSERT(aRunnable
);
68 MOZ_ASSERT(aCallback
);
70 mTimer
= do_CreateInstance(NS_TIMER_CONTRACTID
);
76 * If the service uuid is not specified, either connect multiple profiles
77 * based on Cod, or disconnect all connected profiles.
83 BluetoothServiceClass serviceClass
=
84 BluetoothUuidHelper::GetBluetoothServiceClass(aServiceUuid
);
85 mTarget
.service
= serviceClass
;
90 BluetoothProfileController::~BluetoothProfileController()
102 BluetoothProfileController::AddProfileWithServiceClass(
103 BluetoothServiceClass aClass
)
105 BluetoothProfileManagerBase
* profile
;
107 case BluetoothServiceClass::HANDSFREE
:
108 case BluetoothServiceClass::HEADSET
:
109 profile
= BluetoothHfpManager::Get();
111 case BluetoothServiceClass::A2DP
:
112 profile
= BluetoothA2dpManager::Get();
114 case BluetoothServiceClass::HID
:
115 profile
= BluetoothHidManager::Get();
118 DispatchBluetoothReply(mRunnable
, BluetoothValue(),
119 NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE
));
128 BluetoothProfileController::AddProfile(BluetoothProfileManagerBase
* aProfile
,
129 bool aCheckConnected
)
132 DispatchBluetoothReply(mRunnable
, BluetoothValue(),
133 NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE
));
138 if (aCheckConnected
&& !aProfile
->IsConnected()) {
139 BT_WARNING("The profile is not connected.");
143 mProfiles
.AppendElement(aProfile
);
147 BluetoothProfileController::SetupProfiles(bool aAssignServiceClass
)
149 MOZ_ASSERT(NS_IsMainThread());
152 * When a service class is assigned, only its corresponding profile is put
155 if (aAssignServiceClass
) {
156 AddProfileWithServiceClass(mTarget
.service
);
160 // For a disconnect request, all connected profiles are put into array.
162 AddProfile(BluetoothHidManager::Get(), true);
163 AddProfile(BluetoothA2dpManager::Get(), true);
164 AddProfile(BluetoothHfpManager::Get(), true);
169 * For a connect request, put multiple profiles into array and connect to
170 * all of them sequencely.
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.
183 AddProfile(BluetoothHfpManager::Get());
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
189 if (hasRendering
|| (isPeripheral
&& isRemoteControl
)) {
190 AddProfile(BluetoothA2dpManager::Get());
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());
200 NS_IMPL_ISUPPORTS(CheckProfileStatusCallback
, nsITimerCallback
)
203 CheckProfileStatusCallback::Notify(nsITimer
* aTimer
)
205 MOZ_ASSERT(mController
);
206 // Continue on the next profile since we haven't got the callback after
208 mController
->GiveupAndContinue();
214 BluetoothProfileController::StartSession()
216 MOZ_ASSERT(NS_IsMainThread());
217 MOZ_ASSERT(!mDeviceAddress
.IsEmpty());
218 MOZ_ASSERT(mProfilesIndex
== -1);
221 if (!IsBtServiceAvailable()) {
226 if (mProfiles
.Length() < 1) {
227 BT_LOGR("No queued profile.");
233 mTimer
->InitWithCallback(new CheckProfileStatusCallback(this),
234 CONNECTION_TIMEOUT_MS
, nsITimer::TYPE_ONE_SHOT
);
237 BT_LOGR("%s", mConnect
? "connecting" : "disconnecting");
243 BluetoothProfileController::EndSession()
245 MOZ_ASSERT(mRunnable
&& mCallback
);
247 BT_LOGR("mSuccess %d", mSuccess
);
249 // Don't have to check profile status and retrigger session after connection
250 // timeout, since session is end.
255 // The action has completed, so the DOM request should be replied then invoke
258 DispatchBluetoothReply(mRunnable
, BluetoothValue(true), EmptyString());
259 } else if (mConnect
) {
260 DispatchBluetoothReply(mRunnable
, BluetoothValue(true),
261 NS_LITERAL_STRING(ERR_CONNECTION_FAILED
));
263 DispatchBluetoothReply(mRunnable
, BluetoothValue(true),
264 NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED
));
271 BluetoothProfileController::Next()
273 MOZ_ASSERT(NS_IsMainThread());
274 MOZ_ASSERT(!mDeviceAddress
.IsEmpty());
275 MOZ_ASSERT(mProfilesIndex
< (int)mProfiles
.Length());
278 mCurrentProfileFinished
= false;
280 if (!IsBtServiceAvailable()) {
285 if (++mProfilesIndex
>= (int)mProfiles
.Length()) {
290 BT_LOGR_PROFILE(mProfiles
[mProfilesIndex
], "");
293 mProfiles
[mProfilesIndex
]->Connect(mDeviceAddress
, this);
295 mProfiles
[mProfilesIndex
]->Disconnect(this);
300 BluetoothProfileController::IsBtServiceAvailable() const
302 BluetoothService
* bs
= BluetoothService::Get();
303 return (bs
&& bs
->IsEnabled() && !bs
->IsToggling());
307 BluetoothProfileController::NotifyCompletion(const nsAString
& aErrorStr
)
309 MOZ_ASSERT(NS_IsMainThread());
311 MOZ_ASSERT(mProfiles
.Length() > 0);
313 BT_LOGR_PROFILE(mProfiles
[mProfilesIndex
], "<%s>",
314 NS_ConvertUTF16toUTF8(aErrorStr
).get());
316 mCurrentProfileFinished
= true;
322 mSuccess
|= aErrorStr
.IsEmpty();
328 BluetoothProfileController::GiveupAndContinue()
330 MOZ_ASSERT(!mCurrentProfileFinished
);
331 MOZ_ASSERT(mProfilesIndex
< (int)mProfiles
.Length());
333 BT_LOGR_PROFILE(mProfiles
[mProfilesIndex
], ERR_OPERATION_TIMEOUT
);
334 mProfiles
[mProfilesIndex
]->Reset();
336 if (IsBtServiceAvailable()) {