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 258235 9bff295ea40376ede91bc90cd13593c04698b2b3
parent 258234 ee800c0e234fe61cefd1e98f16f92ffc2bf8a94a
child 258236 4e445d0cc426bce626352cfac5e0eb4e713f4c20
push id14722
push userkwierso@gmail.com
push dateTue, 18 Aug 2015 22:34:50 +0000
treeherderfx-team@1c10c8aed80d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1179181
milestone43.0a1
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 */