dom/bluetooth/bluez/BluetoothA2dpManager.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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)

mercurial