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 "base/basictypes.h"
9 #include "BluetoothA2dpManager.h"
11 #include "BluetoothCommon.h"
12 #include "BluetoothService.h"
13 #include "BluetoothSocket.h"
14 #include "BluetoothUtils.h"
16 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
17 #include "mozilla/Services.h"
18 #include "mozilla/StaticPtr.h"
19 #include "nsIObserverService.h"
20 #include "MainThreadUtils.h"
23 using namespace mozilla;
24 USING_BLUETOOTH_NAMESPACE
26 namespace {
27 StaticRefPtr<BluetoothA2dpManager> sBluetoothA2dpManager;
28 bool sInShutdown = false;
29 } // anonymous namespace
31 NS_IMETHODIMP
32 BluetoothA2dpManager::Observe(nsISupports* aSubject,
33 const char* aTopic,
34 const char16_t* aData)
35 {
36 MOZ_ASSERT(sBluetoothA2dpManager);
38 if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
39 HandleShutdown();
40 return NS_OK;
41 }
43 MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!");
44 return NS_ERROR_UNEXPECTED;
45 }
47 BluetoothA2dpManager::BluetoothA2dpManager()
48 {
49 Reset();
50 }
52 void
53 BluetoothA2dpManager::Reset()
54 {
55 ResetA2dp();
56 ResetAvrcp();
57 }
59 bool
60 BluetoothA2dpManager::Init()
61 {
62 MOZ_ASSERT(NS_IsMainThread());
64 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
65 NS_ENSURE_TRUE(obs, false);
66 if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
67 BT_WARNING("Failed to add shutdown observer!");
68 return false;
69 }
71 return true;
72 }
74 BluetoothA2dpManager::~BluetoothA2dpManager()
75 {
76 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
77 NS_ENSURE_TRUE_VOID(obs);
78 if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
79 BT_WARNING("Failed to remove shutdown observer!");
80 }
81 }
83 void
84 BluetoothA2dpManager::ResetA2dp()
85 {
86 mA2dpConnected = false;
87 mSinkState = SinkState::SINK_DISCONNECTED;
88 mController = nullptr;
89 }
91 void
92 BluetoothA2dpManager::ResetAvrcp()
93 {
94 mAvrcpConnected = false;
95 mDuration = 0;
96 mMediaNumber = 0;
97 mTotalMediaCount = 0;
98 mPosition = 0;
99 mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN;
100 }
102 static BluetoothA2dpManager::SinkState
103 StatusStringToSinkState(const nsAString& aStatus)
104 {
105 BluetoothA2dpManager::SinkState state =
106 BluetoothA2dpManager::SinkState::SINK_UNKNOWN;
107 if (aStatus.EqualsLiteral("disconnected")) {
108 state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED;
109 } else if (aStatus.EqualsLiteral("connecting")) {
110 state = BluetoothA2dpManager::SinkState::SINK_CONNECTING;
111 } else if (aStatus.EqualsLiteral("connected")) {
112 state = BluetoothA2dpManager::SinkState::SINK_CONNECTED;
113 } else if (aStatus.EqualsLiteral("playing")) {
114 state = BluetoothA2dpManager::SinkState::SINK_PLAYING;
115 } else {
116 BT_WARNING("Unknown sink state");
117 }
118 return state;
119 }
121 //static
122 BluetoothA2dpManager*
123 BluetoothA2dpManager::Get()
124 {
125 MOZ_ASSERT(NS_IsMainThread());
127 // If sBluetoothA2dpManager already exists, exit early
128 if (sBluetoothA2dpManager) {
129 return sBluetoothA2dpManager;
130 }
132 // If we're in shutdown, don't create a new instance
133 NS_ENSURE_FALSE(sInShutdown, nullptr);
135 // Create a new instance, register, and return
136 BluetoothA2dpManager* manager = new BluetoothA2dpManager();
137 NS_ENSURE_TRUE(manager->Init(), nullptr);
139 sBluetoothA2dpManager = manager;
140 return sBluetoothA2dpManager;
141 }
143 void
144 BluetoothA2dpManager::HandleShutdown()
145 {
146 MOZ_ASSERT(NS_IsMainThread());
147 sInShutdown = true;
148 Disconnect(nullptr);
149 sBluetoothA2dpManager = nullptr;
150 }
152 void
153 BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress,
154 BluetoothProfileController* aController)
155 {
156 MOZ_ASSERT(NS_IsMainThread());
157 MOZ_ASSERT(!aDeviceAddress.IsEmpty());
158 MOZ_ASSERT(aController && !mController);
160 BluetoothService* bs = BluetoothService::Get();
161 if (!bs || sInShutdown) {
162 aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
163 return;
164 }
166 if (mA2dpConnected) {
167 aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED));
168 return;
169 }
171 mDeviceAddress = aDeviceAddress;
172 mController = aController;
174 if (NS_FAILED(bs->SendSinkMessage(aDeviceAddress,
175 NS_LITERAL_STRING("Connect")))) {
176 aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
177 return;
178 }
179 }
181 void
182 BluetoothA2dpManager::Disconnect(BluetoothProfileController* aController)
183 {
184 BluetoothService* bs = BluetoothService::Get();
185 if (!bs) {
186 if (aController) {
187 aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
188 }
189 return;
190 }
192 if (!mA2dpConnected) {
193 if (aController) {
194 aController->NotifyCompletion(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED));
195 }
196 return;
197 }
199 MOZ_ASSERT(!mDeviceAddress.IsEmpty());
200 MOZ_ASSERT(!mController);
202 mController = aController;
204 if (NS_FAILED(bs->SendSinkMessage(mDeviceAddress,
205 NS_LITERAL_STRING("Disconnect")))) {
206 aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
207 return;
208 }
209 }
211 void
212 BluetoothA2dpManager::OnConnect(const nsAString& aErrorStr)
213 {
214 MOZ_ASSERT(NS_IsMainThread());
216 /**
217 * On the one hand, notify the controller that we've done for outbound
218 * connections. On the other hand, we do nothing for inbound connections.
219 */
220 NS_ENSURE_TRUE_VOID(mController);
222 nsRefPtr<BluetoothProfileController> controller = mController.forget();
223 controller->NotifyCompletion(aErrorStr);
224 }
226 void
227 BluetoothA2dpManager::OnDisconnect(const nsAString& aErrorStr)
228 {
229 MOZ_ASSERT(NS_IsMainThread());
231 /**
232 * On the one hand, notify the controller that we've done for outbound
233 * connections. On the other hand, we do nothing for inbound connections.
234 */
235 NS_ENSURE_TRUE_VOID(mController);
237 nsRefPtr<BluetoothProfileController> controller = mController.forget();
238 controller->NotifyCompletion(aErrorStr);
240 Reset();
241 }
243 /* HandleSinkPropertyChanged update sink state in A2dp
244 *
245 * Possible values: "disconnected", "connecting", "connected", "playing"
246 *
247 * 1. "disconnected" -> "connecting"
248 * Either an incoming or outgoing connection attempt ongoing
249 * 2. "connecting" -> "disconnected"
250 * Connection attempt failed
251 * 3. "connecting" -> "connected"
252 * Successfully connected
253 * 4. "connected" -> "playing"
254 * Audio stream active
255 * 5. "playing" -> "connected"
256 * Audio stream suspended
257 * 6. "connected" -> "disconnected"
258 * "playing" -> "disconnected"
259 * Disconnected from local or the remote device
260 */
261 void
262 BluetoothA2dpManager::HandleSinkPropertyChanged(const BluetoothSignal& aSignal)
263 {
264 MOZ_ASSERT(NS_IsMainThread());
265 MOZ_ASSERT(aSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue);
267 const nsString& address = aSignal.path();
268 /**
269 * Update sink property only if
270 * - mDeviceAddress is empty (A2dp is disconnected), or
271 * - this property change is from the connected sink.
272 */
273 NS_ENSURE_TRUE_VOID(mDeviceAddress.IsEmpty() ||
274 mDeviceAddress.Equals(address));
276 const InfallibleTArray<BluetoothNamedValue>& arr =
277 aSignal.value().get_ArrayOfBluetoothNamedValue();
278 MOZ_ASSERT(arr.Length() == 1);
280 /**
281 * There are three properties:
282 * - "State": a string
283 * - "Connected": a boolean value
284 * - "Playing": a boolean value
285 *
286 * Note that only "State" is handled in this function.
287 */
289 const nsString& name = arr[0].name();
290 NS_ENSURE_TRUE_VOID(name.EqualsLiteral("State"));
292 const BluetoothValue& value = arr[0].value();
293 MOZ_ASSERT(value.type() == BluetoothValue::TnsString);
294 SinkState newState = StatusStringToSinkState(value.get_nsString());
295 NS_ENSURE_TRUE_VOID((newState != SinkState::SINK_UNKNOWN) &&
296 (newState != mSinkState));
298 /**
299 * Reject 'connected' state change if bluetooth is already disabled.
300 * Sink state would be reset to 'disconnected' when bluetooth is disabled.
301 *
302 * See bug 984284 for more information about the edge case.
303 */
304 NS_ENSURE_FALSE_VOID(newState == SinkState::SINK_CONNECTED &&
305 mSinkState == SinkState::SINK_DISCONNECTED);
307 SinkState prevState = mSinkState;
308 mSinkState = newState;
310 switch(mSinkState) {
311 case SinkState::SINK_CONNECTING:
312 // case 1: Either an incoming or outgoing connection attempt ongoing
313 MOZ_ASSERT(prevState == SinkState::SINK_DISCONNECTED);
314 break;
315 case SinkState::SINK_PLAYING:
316 // case 4: Audio stream active
317 MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED);
318 break;
319 case SinkState::SINK_CONNECTED:
320 // case 5: Audio stream suspended
321 if (prevState == SinkState::SINK_PLAYING) {
322 break;
323 }
325 // case 3: Successfully connected
326 MOZ_ASSERT(prevState == SinkState::SINK_CONNECTING);
328 mA2dpConnected = true;
329 mDeviceAddress = address;
330 NotifyConnectionStatusChanged();
332 OnConnect(EmptyString());
333 break;
334 case SinkState::SINK_DISCONNECTED:
335 // case 2: Connection attempt failed
336 if (prevState == SinkState::SINK_CONNECTING) {
337 OnConnect(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
338 break;
339 }
341 // case 6: Disconnected from the remote device
342 MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED ||
343 prevState == SinkState::SINK_PLAYING);
345 mA2dpConnected = false;
346 NotifyConnectionStatusChanged();
347 mDeviceAddress.Truncate();
348 OnDisconnect(EmptyString());
349 break;
350 default:
351 break;
352 }
353 }
355 void
356 BluetoothA2dpManager::NotifyConnectionStatusChanged()
357 {
358 MOZ_ASSERT(NS_IsMainThread());
360 // Notify Gecko observers
361 nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
362 NS_ENSURE_TRUE_VOID(obs);
364 if (NS_FAILED(obs->NotifyObservers(this,
365 BLUETOOTH_A2DP_STATUS_CHANGED_ID,
366 mDeviceAddress.get()))) {
367 BT_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!");
368 }
370 // Dispatch an event of status change
371 DispatchStatusChangedEvent(
372 NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected);
373 }
375 void
376 BluetoothA2dpManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
377 const nsAString& aServiceUuid,
378 int aChannel)
379 {
380 }
382 void
383 BluetoothA2dpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
384 {
385 }
387 void
388 BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress)
389 {
390 aDeviceAddress = mDeviceAddress;
391 }
393 bool
394 BluetoothA2dpManager::IsConnected()
395 {
396 return mA2dpConnected;
397 }
399 void
400 BluetoothA2dpManager::SetAvrcpConnected(bool aConnected)
401 {
402 mAvrcpConnected = aConnected;
403 if (!aConnected) {
404 ResetAvrcp();
405 }
406 }
408 bool
409 BluetoothA2dpManager::IsAvrcpConnected()
410 {
411 return mAvrcpConnected;
412 }
414 void
415 BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle,
416 const nsAString& aArtist,
417 const nsAString& aAlbum,
418 uint64_t aMediaNumber,
419 uint64_t aTotalMediaCount,
420 uint32_t aDuration)
421 {
422 mTitle.Assign(aTitle);
423 mArtist.Assign(aArtist);
424 mAlbum.Assign(aAlbum);
425 mMediaNumber = aMediaNumber;
426 mTotalMediaCount = aTotalMediaCount;
427 mDuration = aDuration;
428 }
430 void
431 BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration,
432 uint32_t aPosition,
433 ControlPlayStatus aPlayStatus)
434 {
435 mDuration = aDuration;
436 mPosition = aPosition;
437 mPlayStatus = aPlayStatus;
438 }
440 void
441 BluetoothA2dpManager::GetAlbum(nsAString& aAlbum)
442 {
443 aAlbum.Assign(mAlbum);
444 }
446 uint32_t
447 BluetoothA2dpManager::GetDuration()
448 {
449 return mDuration;
450 }
452 ControlPlayStatus
453 BluetoothA2dpManager::GetPlayStatus()
454 {
455 return mPlayStatus;
456 }
458 uint32_t
459 BluetoothA2dpManager::GetPosition()
460 {
461 return mPosition;
462 }
464 uint64_t
465 BluetoothA2dpManager::GetMediaNumber()
466 {
467 return mMediaNumber;
468 }
470 void
471 BluetoothA2dpManager::GetTitle(nsAString& aTitle)
472 {
473 aTitle.Assign(mTitle);
474 }
476 NS_IMPL_ISUPPORTS(BluetoothA2dpManager, nsIObserver)