Bug 1179181 - Store separate volume setting into setting database. r=baku
authorAlastor Wu <alwu@mozilla.com>
Mon, 17 Aug 2015 19:38:39 +0800
changeset 258216 9bff295ea40376ede91bc90cd13593c04698b2b3
parent 258215 ee800c0e234fe61cefd1e98f16f92ffc2bf8a94a
child 258217 4e445d0cc426bce626352cfac5e0eb4e713f4c20
push id29246
push userkwierso@gmail.com
push dateTue, 18 Aug 2015 22:23:21 +0000
treeherdermozilla-central@db4616cd0721 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1179181
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1179181 - Store separate volume setting into setting database. r=baku
dom/system/gonk/AudioManager.cpp
dom/system/gonk/AudioManager.h
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -26,16 +26,17 @@
 #endif
 #include "nsISettingsService.h"
 #include "nsPrintfCString.h"
 
 #include "mozilla/Hal.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/MozPromise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "base/message_loop.h"
 
 #include "BluetoothCommon.h"
 #include "BluetoothHfpManagerBase.h"
 
 #include "nsJSUtils.h"
 #include "nsThreadUtils.h"
@@ -75,27 +76,47 @@ static const uint32_t sMaxStreamVolumeTb
   15,  // alarm
   15,  // notification
   15,  // BT SCO
   15,  // enforced audible
   15,  // DTMF
   15,  // TTS
   15,  // FM
 };
+
+// Use a half value of each volume category as the default volume.
+static const uint32_t sDefaultVolumeCategoriesTbl[VOLUME_TOTAL_NUMBER] = {
+  8, // VOLUME_MEDIA
+  8, // VOLUME_NOTIFICATION
+  8, // VOLUME_ALARM
+  3, // VOLUME_TELEPHONY
+  8, // VOLUME_BLUETOOTH_SCO
+};
+
+// Mappings AudioOutputProfiles to strings.
+static const nsAttrValue::EnumTable kAudioOutputProfilesTable[] = {
+  { "primary",   DEVICE_PRIMARY },
+  { "headset",   DEVICE_HEADSET },
+  { "bluetooth", DEVICE_BLUETOOTH },
+  { nullptr }
+};
+
 // A bitwise variable for recording what kind of headset is attached.
 static int sHeadsetState;
 #if defined(MOZ_B2G_BT) || ANDROID_VERSION >= 17
 static bool sBluetoothA2dpEnabled;
 #endif
 static const int kBtSampleRate = 8000;
 static bool sSwitchDone = true;
 #ifdef MOZ_B2G_BT
 static bool sA2dpSwitchDone = true;
 #endif
 
+typedef MozPromise<bool, const char*, true> VolumeInitPromise;
+
 namespace mozilla {
 namespace dom {
 namespace gonk {
 static const VolumeData gVolumeData[VOLUME_TOTAL_NUMBER] = {
   {"audio.volume.content",      VOLUME_MEDIA},
   {"audio.volume.notification", VOLUME_NOTIFICATION},
   {"audio.volume.alarm",        VOLUME_ALARM},
   {"audio.volume.telephony",    VOLUME_TELEPHONY},
@@ -179,56 +200,88 @@ public:
     AudioSystem::setPhoneState(static_cast<audio_mode_t>(phoneState));
 #endif
 
     AudioSystem::get_audio_flinger();
     return NS_OK;
   }
 };
 
-class AudioChannelVolInitCallback final : public nsISettingsServiceCallback
+class VolumeInitCallback final : public nsISettingsServiceCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
-  AudioChannelVolInitCallback() {}
+  VolumeInitCallback()
+    : mInitCounter(0)
+  {
+    mPromise = mPromiseHolder.Ensure(__func__);
+  }
+
+  nsRefPtr<VolumeInitPromise> GetPromise() const
+  {
+    return mPromise;
+  }
 
   NS_IMETHOD Handle(const nsAString& aName, JS::Handle<JS::Value> aResult)
   {
-    NS_ENSURE_TRUE(aResult.isInt32(), NS_OK);
     nsRefPtr<AudioManager> audioManager = AudioManager::GetInstance();
     MOZ_ASSERT(audioManager);
-    uint32_t volIndex = aResult.toInt32();
     for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) {
-      if (aName.EqualsASCII(gVolumeData[idx].mChannelName)) {
+      NS_ConvertASCIItoUTF16 volumeType(gVolumeData[idx].mChannelName);
+      if (StringBeginsWith(aName, volumeType)) {
+        AudioOutputProfiles profile = GetProfileFromSettingName(aName);
+        MOZ_ASSERT(profile != DEVICE_ERROR);
+
         uint32_t category = gVolumeData[idx].mCategory;
+        uint32_t volIndex = aResult.isInt32() ?
+                  aResult.toInt32() : sDefaultVolumeCategoriesTbl[category];
         nsresult rv = audioManager->ValidateVolumeIndex(category, volIndex);
         if (NS_WARN_IF(NS_FAILED(rv))) {
+          mPromiseHolder.Reject("Error : invalid volume index.", __func__);
           return rv;
         }
-        audioManager->InitProfilesVolume(gVolumeData[idx].mCategory, volIndex);
+
+        audioManager->InitProfileVolume(profile, category, volIndex);
+        if (++mInitCounter == DEVICE_TOTAL_NUMBER * VOLUME_TOTAL_NUMBER) {
+          mPromiseHolder.Resolve(true, __func__);
+        }
         return NS_OK;
       }
     }
-    NS_WARNING("unexpected event name for initializing volume control");
+    mPromiseHolder.Reject("Error : unexpected audio init event.", __func__);
     return NS_OK;
   }
 
   NS_IMETHOD HandleError(const nsAString& aName)
   {
-    LOG("AudioChannelVolInitCallback::HandleError: %s\n",
-      NS_ConvertUTF16toUTF8(aName).get());
+    mPromiseHolder.Reject(NS_ConvertUTF16toUTF8(aName).get(), __func__);
     return NS_OK;
   }
 
 protected:
-  ~AudioChannelVolInitCallback() {}
+  ~VolumeInitCallback() {}
+
+  AudioOutputProfiles GetProfileFromSettingName(const nsAString& aName) const
+  {
+    for (uint32_t idx = 0; kAudioOutputProfilesTable[idx].tag; ++idx) {
+      NS_ConvertASCIItoUTF16 profile(kAudioOutputProfilesTable[idx].tag);
+      if (StringEndsWith(aName, profile)) {
+        return static_cast<AudioOutputProfiles>(kAudioOutputProfilesTable[idx].value);
+      }
+    }
+    return DEVICE_ERROR;
+  }
+
+  nsRefPtr<VolumeInitPromise> mPromise;
+  MozPromiseHolder<VolumeInitPromise> mPromiseHolder;
+  uint32_t mInitCounter;
 };
 
-NS_IMPL_ISUPPORTS(AudioChannelVolInitCallback, nsISettingsServiceCallback)
+NS_IMPL_ISUPPORTS(VolumeInitCallback, nsISettingsServiceCallback)
 } /* namespace gonk */
 } /* namespace dom */
 } /* namespace mozilla */
 
 static void
 BinderDeadCallback(status_t aErr)
 {
   if (aErr == DEAD_OBJECT) {
@@ -532,27 +585,17 @@ AudioManager::AudioManager()
     mCurrentStreamVolumeTbl[loop] = sMaxStreamVolumeTbl[loop];
   }
   // Force publicnotification to output at maximal volume
   SetStreamVolumeIndex(AUDIO_STREAM_ENFORCED_AUDIBLE,
                        sMaxStreamVolumeTbl[AUDIO_STREAM_ENFORCED_AUDIBLE]);
   CreateAudioProfilesData();
 
   // Get the initial volume index from settings DB during boot up.
-  nsCOMPtr<nsISettingsService> settingsService =
-    do_GetService("@mozilla.org/settingsService;1");
-  NS_ENSURE_TRUE_VOID(settingsService);
-  nsCOMPtr<nsISettingsServiceLock> lock;
-  nsresult rv = settingsService->CreateLock(nullptr, getter_AddRefs(lock));
-  NS_ENSURE_SUCCESS_VOID(rv);
-  nsCOMPtr<nsISettingsServiceCallback> callback = new AudioChannelVolInitCallback();
-  NS_ENSURE_TRUE_VOID(callback);
-  for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) {
-    lock->Get(gVolumeData[idx].mChannelName, callback);
-  }
+  InitVolumeFromDatabase();
 
   // Gecko only control stream volume not master so set to default value
   // directly.
   AudioSystem::setMasterVolume(1.0);
   AudioSystem::setErrorCallback(BinderDeadCallback);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_ENSURE_TRUE_VOID(obs);
@@ -602,16 +645,19 @@ AudioManager::~AudioManager() {
     NS_WARNING("Failed to remove bluetooth hfp NREC status changed observer!");
   }
   if (NS_FAILED(obs->RemoveObserver(this, MOZ_SETTINGS_CHANGE_ID))) {
     NS_WARNING("Failed to remove mozsettings-changed observer!");
   }
   if (NS_FAILED(obs->RemoveObserver(this,  AUDIO_CHANNEL_PROCESS_CHANGED))) {
     NS_WARNING("Failed to remove audio-channel-process-changed!");
   }
+
+  // Store the present volume setting to setting database.
+  SendVolumeChangeNotification(FindAudioProfileData(mPresentProfile));
 }
 
 static StaticRefPtr<AudioManager> sAudioManager;
 
 already_AddRefed<AudioManager>
 AudioManager::GetInstance()
 {
   // Avoid createing AudioManager from content process.
@@ -1019,16 +1065,73 @@ AudioManager::FindAudioProfileData(Audio
     if (mAudioProfiles[idx]->GetProfile() == aProfile) {
       return mAudioProfiles[idx];
     }
   }
   NS_WARNING("Can't find audio profile data");
   return nullptr;
 }
 
+nsAutoCString
+AudioManager::AppendProfileToVolumeSetting(const char* aName, AudioOutputProfiles aProfile)
+{
+  nsAutoCString topic;
+  topic.Assign(aName);
+  for (uint32_t idx = 0; kAudioOutputProfilesTable[idx].tag; ++idx) {
+    if (kAudioOutputProfilesTable[idx].value == aProfile) {
+      topic.Append(".");
+      topic.Append(kAudioOutputProfilesTable[idx].tag);
+      break;
+    }
+  }
+  return topic;
+}
+
+
+void
+AudioManager::InitVolumeFromDatabase()
+{
+  nsresult rv;
+  nsCOMPtr<nsISettingsService> service = do_GetService(SETTINGS_SERVICE, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsCOMPtr<nsISettingsServiceLock> lock;
+  rv = service->CreateLock(nullptr, getter_AddRefs(lock));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsRefPtr<VolumeInitCallback> callback = new VolumeInitCallback();
+  MOZ_ASSERT(callback);
+  callback->GetPromise()->Then(AbstractThread::MainThread(), __func__, this,
+                               &AudioManager::InitProfileVolumeSucceeded,
+                               &AudioManager::InitProfileVolumeFailed);
+
+  for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) {
+    for (uint32_t pIdx = 0; pIdx < DEVICE_TOTAL_NUMBER; ++pIdx) {
+      lock->Get(AppendProfileToVolumeSetting(gVolumeData[idx].mChannelName,
+                  static_cast<AudioOutputProfiles>(pIdx)).get(), callback);
+    }
+  }
+}
+
+void
+AudioManager::InitProfileVolumeSucceeded()
+{
+  SendVolumeChangeNotification(FindAudioProfileData(mPresentProfile));
+}
+
+void
+AudioManager::InitProfileVolumeFailed(const char* aError)
+{
+  NS_WARNING(aError);
+}
+
 void
 AudioManager::SendVolumeChangeNotification(AudioProfileData* aProfileData)
 {
   MOZ_ASSERT(aProfileData);
   nsresult rv;
   nsCOMPtr<nsISettingsService> service = do_GetService(SETTINGS_SERVICE, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
@@ -1040,39 +1143,45 @@ AudioManager::SendVolumeChangeNotificati
     return;
   }
 
   // Send events to update the Gaia volume
   mozilla::AutoSafeJSContext cx;
   JS::Rooted<JS::Value> value(cx);
   for (uint32_t idx = 0; idx < VOLUME_TOTAL_NUMBER; ++idx) {
     value.setInt32(aProfileData->mVolumeTable[gVolumeData[idx].mCategory]);
+    // For reducing the code dependency, Gaia doesn't need to know the current
+    // profile, it only need to care about different volume categories.
+    // However, we need to send the setting volume to the permanent database,
+    // so that we can store the volume setting even if the phone reboots.
     lock->Set(gVolumeData[idx].mChannelName, value, nullptr, nullptr);
+    lock->Set(AppendProfileToVolumeSetting(gVolumeData[idx].mChannelName,
+               mPresentProfile).get(), value, nullptr, nullptr);
   }
 }
 
 void
 AudioManager::CreateAudioProfilesData()
 {
   MOZ_ASSERT(mAudioProfiles.IsEmpty(), "mAudioProfiles should be empty!");
   for (uint32_t idx = 0; idx < DEVICE_TOTAL_NUMBER; ++idx) {
     AudioProfileData* profile = new AudioProfileData(static_cast<AudioOutputProfiles>(idx));
     mAudioProfiles.AppendElement(profile);
   }
   UpdateProfileState(DEVICE_PRIMARY, true);
 }
 
 void
-AudioManager::InitProfilesVolume(uint32_t aCategory, uint32_t aIndex)
+AudioManager::InitProfileVolume(AudioOutputProfiles aProfile,
+                                 uint32_t aCategory,
+                                 uint32_t aIndex)
 {
-  uint32_t profilesNum = mAudioProfiles.Length();
-  MOZ_ASSERT(profilesNum == DEVICE_TOTAL_NUMBER, "Error profile numbers!");
-  for (uint32_t idx = 0; idx < profilesNum; ++idx) {
-    mAudioProfiles[idx]->mVolumeTable[aCategory] = aIndex;
-  }
+  AudioProfileData* profileData = FindAudioProfileData(aProfile);
+  MOZ_ASSERT(profileData);
+  profileData->mVolumeTable[aCategory] = aIndex;
   SetVolumeByCategory(aCategory, aIndex);
 }
 
 void
 AudioManager::SwitchProfileData(AudioOutputProfiles aProfile,
                                 bool aActive)
 {
   MOZ_ASSERT(DEVICE_PRIMARY <= aProfile &&
--- a/dom/system/gonk/AudioManager.h
+++ b/dom/system/gonk/AudioManager.h
@@ -38,16 +38,17 @@ namespace gonk {
 
 /**
  * FxOS can remeber the separate volume settings on difference output profiles.
  * (1) Primary : speaker, receiver
  * (2) Headset : wired headphone/headset
  * (3) Bluetooth : BT SCO/A2DP devices
  **/
 enum AudioOutputProfiles {
+  DEVICE_ERROR        = -1,
   DEVICE_PRIMARY      = 0,
   DEVICE_HEADSET      = 1,
   DEVICE_BLUETOOTH    = 2,
   DEVICE_TOTAL_NUMBER = 3,
 };
 
 /**
  * We have five sound volume settings from UX spec,
@@ -68,33 +69,33 @@ enum AudioVolumeCategories {
 };
 
 struct VolumeData {
   const char* mChannelName;
   uint32_t mCategory;
 };
 
 class RecoverTask;
-class AudioChannelVolInitCallback;
+class VolumeInitCallback;
 class AudioProfileData;
 
 class AudioManager final : public nsIAudioManager
                          , public nsIObserver
 {
 public:
   static already_AddRefed<AudioManager> GetInstance();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIAUDIOMANAGER
   NS_DECL_NSIOBSERVER
 
   // When audio backend is dead, recovery task needs to read all volume
   // settings then set back into audio backend.
   friend class RecoverTask;
-  friend class AudioChannelVolInitCallback;
+  friend class VolumeInitCallback;
 
   // Open or close the specific profile
   void SwitchProfileData(AudioOutputProfiles aProfile, bool aActive);
 
   // Validate whether the volume index is within the range
   nsresult ValidateVolumeIndex(uint32_t aCategory, uint32_t aIndex) const;
 
 protected:
@@ -117,17 +118,18 @@ private:
   void HandleBluetoothStatusChanged(nsISupports* aSubject,
                                     const char* aTopic,
                                     const nsCString aAddress);
   void HandleAudioChannelProcessChanged();
 
   void CreateAudioProfilesData();
 
   // Init the volume setting from the init setting callback
-  void InitProfilesVolume(uint32_t aCatogory, uint32_t aIndex);
+  void InitProfileVolume(AudioOutputProfiles aProfile,
+                        uint32_t aCatogory, uint32_t aIndex);
 
   // Update volume data of profiles
   void UpdateVolumeToProfile(AudioProfileData* aProfileData);
 
   // Apply the volume data to device
   void UpdateVolumeFromProfile(AudioProfileData* aProfileData);
 
   // Send the volume changing event to Gaia
@@ -138,16 +140,27 @@ private:
 
   // Volume control functions
   nsresult SetVolumeByCategory(uint32_t aCategory, uint32_t aIndex);
   uint32_t GetVolumeByCategory(uint32_t aCategory) const;
   uint32_t GetMaxVolumeByCategory(uint32_t aCategory) const;
 
   AudioProfileData* FindAudioProfileData(AudioOutputProfiles aProfile);
 
+  // Append the profile to the volume setting string.
+  nsAutoCString AppendProfileToVolumeSetting(const char* aName,
+                                             AudioOutputProfiles aProfile);
+
+  // Init volume from the settings database.
+  void InitVolumeFromDatabase();
+
+  // Promise functions.
+  void InitProfileVolumeSucceeded();
+  void InitProfileVolumeFailed(const char* aError);
+
   AudioManager();
   ~AudioManager();
 };
 
 } /* namespace gonk */
 } /* namespace dom */
 } /* namespace mozilla */