Ring: Add ring's sound and vibration 72/90572/11
authorKamil Lipiszko <k.lipiszko@samsung.com>
Thu, 22 Sep 2016 12:54:19 +0000 (14:54 +0200)
committerLukasz Stanislawski <l.stanislaws@samsung.com>
Wed, 12 Oct 2016 10:44:45 +0000 (03:44 -0700)
This commit includes sound manager and feedback haptic
implementation to enable playing sound and vibration
during the ringing.

Change-Id: I0784bf244e2a274839eee17802ed031c9f3f4e4f
Signed-off-by: Kamil Lipiszko <k.lipiszko@samsung.com>
clock/inc/Presenter/RingPresenter.h
clock/inc/Utils/Utils.h
clock/inc/View/RingView.h
clock/project_def.prop
clock/res/settings/ringtone_sdk.mp3 [new file with mode: 0644]
clock/src/Presenter/RingPresenter.cpp
clock/src/Utils/Utils.cpp
clock/src/View/RingView.cpp
clock/tizen-manifest.xml

index 37689e7..b16bd99 100644 (file)
@@ -30,9 +30,6 @@ namespace presenter {
                RingPresenter(view::RingView *view, model::Alarm *alarm);
                ~RingPresenter();
 
-               void DismissButtonClicked(void);
-               void SnoozeButtonClicked(void);
-
        private:
                view::RingView *view_;
                model::Ring *model_;
@@ -41,7 +38,30 @@ namespace presenter {
 
                common::CounterAnimator animator_;
 
+               /**
+                * @brief Function called on animator's timeout request.
+                *
+                * This function updates the Ring's time counter on every
+                * animator's tick.
+                */
                void TimeUpdateRequest(void);
+
+               /**
+                * @brief Callback function called on dismiss button click.
+                */
+               void DismissButtonClicked(void);
+
+               /**
+                * @brief Callback function called on snooze button click.
+                */
+               void SnoozeButtonClicked(void);
+
+               /**
+                * @brief Callback function called on sound stream focus change.
+                */
+               void SoundStreamChanged(void);
+
+               static const char *SOUND_DEFAULT;
        };
 
 } //namespace presenter
index 288d832..1bfaa6a 100644 (file)
@@ -47,6 +47,13 @@ namespace utils {
                 * @note returns statically allocated string
                 */
                static const char *GetAppResourcePath(enum AppSubdirectory dir, const char *relative);
+
+               /**
+                * @brief Checks whether file with passed path exists.
+                *
+                * @param[in] path The path to the file.
+                */
+               static bool FileExists(const char *path);
        };
 }
 
index fb4ead5..0f47bfd 100644 (file)
@@ -20,6 +20,7 @@
 #include <Elementary.h>
 #include <vector>
 #include <functional>
+#include <player.h>
 
 #include "View/CounterView.h"
 
@@ -29,24 +30,134 @@ namespace view {
                BUTTON_DISMISS_CLICKED,
                BUTTON_SNOOZE_CLICKED,
 
+               SOUND_STREAM_FOCUS_CHANGED,
+
                MAX,
        };
 
        class RingView : public ui::IView {
        public:
+               /**
+                * @brief Creates class object and ring view.
+                */
                RingView();
+
+               /**
+                * @brief Destroys class object and ring view.
+                */
                ~RingView();
 
+               /**
+                * @brief Shows countdown counter with timer's elapsed time.
+                */
                void ShowCounter(void);
+
+               /**
+                * @brief Sets title of the inocming ring view.
+                *
+                * @param[in] title The title to be set
+                */
                void SetTitle(const char *title);
+
+               /**
+                * @brief Sets text for main content view with alarm's time and current date.
+                *
+                * @param[in] ampm The AM or PM string to be shown
+                * @param[in] hour The alarm hour to be shown
+                * @param[in] date The current date string
+                */
                void SetTimeLabel(const char *ampm, const char *hour, const char *date);
 
+               /**
+                * @brief Displays time in a countdown time counter.
+                *
+                * @param[in] hour The hour to be displayed
+                * @param[in] min The minute to be displayed
+                * @param[in] sec The second to be displayed
+                */
                void DisplayTime(int hour, int min, int sec);
 
+               /**
+                * @brief Registers signal with the RingSignal type.
+                *
+                * @param[in] func The function to be called on passed type signal
+                * @param[in] type The signal type on which callback should be called
+                */
                void RegisterSignal(std::function<void(void)>func, RingSignal type);
+
+               /**
+                * @brief Emits signal with the assigned type.
+                *
+                * @param[in] type The type of the signal whose callback must be called
+                */
                void EmitSignal(RingSignal type);
+
+               /**
+                * @brief Shows or hides snooze swipe button.
+                *
+                * @param[in] enabled True if snooze button should be visible, otherwise false.
+                */
                void EnableSnooze(bool enabled);
 
+               /**
+                * @brief Creates sound stream and plays music with the passed uri
+                *      path and relative to system volume value.
+                *
+                * @param[in] uri The uri path to the sound
+                * @param[in] volume The relative volume of the sound to be played
+                */
+               void PlayMusic(const char *uri, double volume);
+
+               /**
+                * @brief Stops the currently playing music and destroys created with PlayMusic
+                *      sound stream. After music is stopped, to play it againg it is necessary
+                *      to call PlayMusic method.
+                */
+               void StopMusic(void);
+
+               /**
+                * @brief Pauses the currently playing music without destroying sound stream.
+                */
+               void PauseMusic(void);
+
+               /**
+                * @brief Resumes paused music.
+                */
+               void ResumeMusic(void);
+
+               /**
+                * @brief Starts device vibration.
+                */
+               void StartVibration(void);
+
+               /**
+                * @brief Stops device vibration.
+                */
+               void StopVibration(void);
+
+               /**
+                * @brief Destroys and zeroes player handler.
+                */
+               void DestroyPlayer(void);
+
+               /**
+                * @brief Destroys and zeroes stream info handler.
+                */
+               void DestroyStreamInfo(void);
+
+               /**
+                * @brief Returns current player state.
+                */
+               player_state_e GetPlayerState(void);
+
+               /**
+                * @brief Returns current player focus state.
+                */
+               sound_stream_focus_state_e GetStreamFocusState(void);
+
+               /**
+                * @brief Returns the Evas_Object of the view.
+                */
                Evas_Object *GetEvasObject();
 
        private:
@@ -54,15 +165,44 @@ namespace view {
                Evas_Object *layout_;
                view::CounterView *counter_;
 
+               player_h player_;
+               sound_stream_info_h stream_info_;
+
                std::vector<std::function<void(void)>> signals_
                        = std::vector<std::function<void(void)>>((int)RingSignal::MAX, nullptr);
 
                static const char *GROUP;
                static const char *EDJE_FILE;
 
+               /**
+                * @brief Callback function to be called on dismiss button swipe.
+                */
                static void DismissCb(void *data, Evas_Object *obj, const char *emission, const char *source);
+
+               /**
+                * @brief Callback function to be called on snooze button swipe.
+                */
                static void SnoozeCb(void *data, Evas_Object *obj, const char *emission, const char *source);
 
+               /**
+                * @brief Callback function called when the sound stream is interrupted and
+                *      playback device handles other application or stream sound.
+                */
+               static void PlayerInterruptedCb(player_interrupted_code_e code, void *data);
+
+               /**
+                * @brief Callback function called on stream focus change.
+                */
+               static void StreamFocusChangedCb(sound_stream_info_h stream_info, sound_stream_focus_change_reason_e reason, const char *extra_info, void *user_data);
+
+
+               /**
+                * @brief Sets the main content for the view from the following:
+                *      - time counter,
+                *      - alarm info view,
+                *
+                * @param[in] ly The layout of the ring view where content must be placed.
+                */
                void SetMainContent(Evas_Object *ly);
        };
 
index 329162c..dcad828 100644 (file)
@@ -52,7 +52,7 @@ USER_LIB_DIRS_ABS =
 # EDC Resource Path
 USER_EDCS_IMAGE_DIRS = res/edje/images
 USER_EDCS_IMAGE_DIRS_ABS = 
-USER_EDCS_SOUND_DIRS = res/edje/sounds
+USER_EDCS_SOUND_DIRS = res/edje/sounds res/edje/settings
 USER_EDCS_SOUND_DIRS_ABS = 
 USER_EDCS_FONT_DIRS = res/edje/fonts
 USER_EDCS_FONT_DIRS_ABS = 
diff --git a/clock/res/settings/ringtone_sdk.mp3 b/clock/res/settings/ringtone_sdk.mp3
new file mode 100644 (file)
index 0000000..f159e35
Binary files /dev/null and b/clock/res/settings/ringtone_sdk.mp3 differ
index 085e0c1..2a72e5e 100644 (file)
 
 #include "Presenter/RingPresenter.h"
 #include "Utils/Time.h"
+#include "Utils/Utils.h"
 #include "Controller/RingController.h"
 
 namespace presenter {
 
+const char *SOUND_DEFAULT = "settings/ringtone_sdk.mp3";
+
 RingPresenter::RingPresenter(view::RingView *view, model::Ring *model)
        : view_(view), model_(model)
 {
@@ -32,27 +35,45 @@ RingPresenter::RingPresenter(view::RingView *view, model::Ring *model)
                        view::RingSignal::BUTTON_DISMISS_CLICKED);
        view_->RegisterSignal(std::bind(&RingPresenter::SnoozeButtonClicked, this),
                        view::RingSignal::BUTTON_SNOOZE_CLICKED);
+       view_->RegisterSignal(std::bind(&RingPresenter::SoundStreamChanged, this),
+                       view::RingSignal::SOUND_STREAM_FOCUS_CHANGED);
 
        model_->Run();
        animator_.Start();
        view_->EnableSnooze(false);
+       view_->PlayMusic(utils::Utils::GetAppResourcePath(utils::Utils::APP_DIR_RESOURCE, presenter::SOUND_DEFAULT), 1.0);
+       view_->StartVibration();
 }
 
 RingPresenter::RingPresenter(view::RingView *view, model::Alarm *alarm)
        : view_(view), model_(NULL), alarm_(alarm)
 {
+       if (!alarm)
+               return;
 
        view_->RegisterSignal(std::bind(&RingPresenter::DismissButtonClicked, this),
                        view::RingSignal::BUTTON_DISMISS_CLICKED);
        view_->RegisterSignal(std::bind(&RingPresenter::SnoozeButtonClicked, this),
                        view::RingSignal::BUTTON_SNOOZE_CLICKED);
+       view_->RegisterSignal(std::bind(&RingPresenter::SoundStreamChanged, this),
+                       view::RingSignal::SOUND_STREAM_FOCUS_CHANGED);
+
+       utils::Time time = alarm_->GetTime();
 
-       utils::Time time = alarm->GetTime();
+       view_->SetTitle(alarm_->GetName().c_str());
+       view_->SetTimeLabel(time.Meridiem().c_str(), time.GetFormattedTime("HH:mm").c_str(), time.GetDate().c_str());
+       if (utils::Utils::FileExists(alarm_->GetMelody().c_str()))
+               view_->PlayMusic(alarm_->GetMelody().c_str(), alarm_->GetVolume());
+       else {
+               DBG("Invalid alarm's sound path. Playing default.");
+               view_->PlayMusic(utils::Utils::GetAppResourcePath(
+                                       utils::Utils::APP_DIR_RESOURCE, presenter::SOUND_DEFAULT),
+                               alarm_->GetVolume());
+       }
 
-       view->SetTitle(alarm->GetName().c_str());
-       view->SetTimeLabel(time.Meridiem().c_str(), time.GetFormattedTime("HH:mm").c_str(), time.GetDate().c_str());
+       view_->StartVibration();
 
-       if (alarm->CanSnooze())
+       if (alarm_->CanSnooze())
                view_->EnableSnooze(true);
        else
                view_->EnableSnooze(false);
@@ -60,6 +81,9 @@ RingPresenter::RingPresenter(view::RingView *view, model::Alarm *alarm)
 
 RingPresenter::~RingPresenter()
 {
+       view_->StopMusic();
+       view_->StopVibration();
+       animator_.Remove();
 }
 
 void RingPresenter::TimeUpdateRequest()
@@ -69,16 +93,46 @@ void RingPresenter::TimeUpdateRequest()
 
 void RingPresenter::DismissButtonClicked()
 {
-       alarm_->Dismiss();
-       animator_.Remove();
+       if (alarm_)
+               alarm_->Dismiss();
+
        controller::RingController::GetInstance().ShutDown();
 }
 
 void RingPresenter::SnoozeButtonClicked()
 {
-       alarm_->Snooze();
+       if (alarm_)
+               alarm_->Snooze();
+
        animator_.Remove();
        controller::RingController::GetInstance().ShutDown();
 }
 
+void RingPresenter::SoundStreamChanged()
+{
+       switch (view_->GetStreamFocusState()) {
+       case SOUND_STREAM_FOCUS_STATE_ACQUIRED:
+               if (view_->GetPlayerState() == PLAYER_STATE_PAUSED)
+                       view_->ResumeMusic();
+               else {
+                       view_->PlayMusic(
+                               alarm_ ? alarm_->GetMelody().c_str() : "/opt/share/settings/Ringtones/ringtone_sdk.mp3",
+                               alarm_ ? alarm_->GetVolume() : 1.0);
+               }
+
+               view_->StartVibration();
+               break;
+       case SOUND_STREAM_FOCUS_STATE_RELEASED:
+               if (view_->GetPlayerState() == PLAYER_STATE_PLAYING)
+                       view_->PauseMusic();
+               else
+                       view_->StopMusic();
+               view_->StopVibration();
+               break;
+       default:
+               ERR("Invalid stream state");
+               return;
+       }
+}
+
 } //namespace presenter
index 2f72b42..791518f 100644 (file)
@@ -22,6 +22,7 @@
 #include <app_common.h>
 #include <Elementary.h>
 #include <limits.h>
+#include <Ecore.h>
 
 using namespace utils;
 
@@ -71,3 +72,8 @@ const char *Utils::GetAppResourcePath(enum AppSubdirectory dir, const char *rela
 
        return &buf[0];
 }
+
+bool Utils::FileExists(const char *path)
+{
+       return ecore_file_exists(path);
+}
index c894bc0..dfa6f2c 100644 (file)
@@ -15,6 +15,8 @@
  */
 
 #include <efl_util.h>
+#include <sound_manager.h>
+#include <feedback.h>
 
 #include "View/RingView.h"
 #include "View/MainView.h"
@@ -26,7 +28,8 @@ namespace view {
 const char *RingView::EDJE_FILE = "edje/ring.edj";
 const char *RingView::GROUP = "main";
 
-RingView::RingView() : win_(NULL), layout_(NULL), counter_(NULL)
+RingView::RingView()
+       : win_(nullptr), layout_(nullptr), counter_(nullptr), player_(nullptr), stream_info_(nullptr)
 {
        win_ = elm_win_add(NULL, "Ring", ELM_WIN_NOTIFICATION);
        if (!win_) {
@@ -190,4 +193,235 @@ void RingView::EmitSignal(RingSignal type)
                signals_.at((int)type)();
 }
 
+void RingView::PlayMusic(const char *uri, double volume)
+{
+       if (player_ || stream_info_)
+               StopMusic();
+
+       DBG("Play music from: %s", uri);
+       int ret = sound_manager_create_stream_information(SOUND_STREAM_TYPE_ALARM,
+                       StreamFocusChangedCb, this, &stream_info_);
+       if (ret != SOUND_MANAGER_ERROR_NONE) {
+               ERR("sound_manager_create_stream_information failed[%d]: %s", ret, get_error_message(ret));
+               return;
+       }
+
+       ret = sound_manager_acquire_focus(stream_info_, SOUND_STREAM_FOCUS_FOR_PLAYBACK, NULL);
+       if (ret != SOUND_MANAGER_ERROR_NONE) {
+               DestroyStreamInfo();
+               ERR("sound_manager_acquire_focus failed[%d]: %s", ret, get_error_message(ret));
+               return;
+       }
+
+       int maxVolume = 0;
+       ret = sound_manager_get_max_volume(SOUND_TYPE_ALARM, &maxVolume);
+       if (ret != SOUND_MANAGER_ERROR_NONE) {
+               ERR("sound_manager_get_max_volume failed[%d]: %s", ret, get_error_message(ret));
+               DestroyStreamInfo();
+               return;
+       }
+
+       ret = sound_manager_set_volume(SOUND_TYPE_ALARM, maxVolume);
+       if (ret != SOUND_MANAGER_ERROR_NONE) {
+               ERR("sound_manager_set_volume failed[%d]: %s", ret, get_error_message(ret));
+               DestroyStreamInfo();
+               return;
+       }
+
+       ret = player_create(&player_);
+       if (ret != PLAYER_ERROR_NONE) {
+               ERR("player_create failed[%d]: %s", ret, get_error_message(ret));
+               DestroyStreamInfo();
+               return;
+       }
+
+       ret = player_set_uri(player_, uri);
+       if (ret != PLAYER_ERROR_NONE) {
+               ERR("player_set_uri failed[%d]: %s", ret, get_error_message(ret));
+               DestroyPlayer();
+               DestroyStreamInfo();
+               return;
+       }
+
+       ret = player_set_audio_policy_info(player_, stream_info_);
+       if (ret != PLAYER_ERROR_NONE) {
+               ERR("player_set_audio_policy_info failed[%d]: %s", ret, get_error_message(ret));
+               DestroyPlayer();
+               DestroyStreamInfo();
+               return;
+       }
+
+       ret = player_prepare(player_);
+       if (ret != PLAYER_ERROR_NONE) {
+               ERR("player_prepare failed[%d]: %s", ret, get_error_message(ret));
+               DestroyPlayer();
+               DestroyStreamInfo();
+               return;
+       }
+
+       ret = player_set_looping(player_, true);
+       if (ret != PLAYER_ERROR_NONE) {
+               ERR("player_set_looping failed[%d]: %s", ret, get_error_message(ret));
+               DestroyPlayer();
+               DestroyStreamInfo();
+               return;
+       }
+
+       ret = player_set_volume(player_, volume, volume);
+       if (ret != PLAYER_ERROR_NONE) {
+               ERR("player_set_volume failed[%d]: %s", ret, get_error_message(ret));
+               DestroyPlayer();
+               DestroyStreamInfo();
+               return;
+       }
+
+       if (GetPlayerState() == PLAYER_STATE_READY) {
+               ret = player_start(player_);
+               if (ret != PLAYER_ERROR_NONE) {
+                       ERR("player_start failed[%d]: %s", ret, get_error_message(ret));
+                       StopMusic();
+                       return;
+               }
+       }
+}
+
+void RingView::DestroyPlayer()
+{
+       player_destroy(player_);
+       player_ = nullptr;
+}
+
+void RingView::DestroyStreamInfo()
+{
+       sound_manager_destroy_stream_information(stream_info_);
+       stream_info_ = nullptr;
+}
+
+void RingView::StreamFocusChangedCb(sound_stream_info_h streamInfo,
+               sound_stream_focus_change_reason_e reason, const char *extraInfo, void *userData)
+{
+       RingView *object = static_cast<RingView *>(userData);
+
+       object->EmitSignal(view::RingSignal::SOUND_STREAM_FOCUS_CHANGED);
+}
+
+void RingView::PauseMusic()
+{
+       if (!player_) {
+               DBG("Player is not created");
+               return;
+       }
+
+       if (GetPlayerState() == PLAYER_STATE_PLAYING)
+               player_pause(player_);
+       else {
+               ERR("Player is not playing.");
+               return;
+       }
+}
+
+void RingView::ResumeMusic()
+{
+       if (!player_) {
+               DBG("Player is not created");
+               return;
+       }
+
+       if (GetPlayerState() == PLAYER_STATE_PAUSED)
+               player_start(player_);
+       else {
+               ERR("Player is not paused.");
+               return;
+       }
+}
+
+void RingView::StopMusic()
+{
+       int ret = 0;
+
+       if (player_) {
+               ret = player_stop(player_);
+               if (ret != PLAYER_ERROR_NONE)
+                       ERR("player_stop failed[%d]: %s", ret, get_error_message(ret));
+
+               ret = player_unprepare(player_);
+               if (ret != PLAYER_ERROR_NONE)
+                       ERR("player_unprepare failed[%d]: %s", ret, get_error_message(ret));
+
+               DestroyPlayer();
+       }
+
+       if (stream_info_) {
+               ret = sound_manager_release_focus(stream_info_, SOUND_STREAM_FOCUS_FOR_PLAYBACK, NULL);
+               if (ret != SOUND_MANAGER_ERROR_NONE)
+                       ERR("sound_manager_release_focus failed[%d]: %s", ret, get_error_message(ret));
+
+               DestroyStreamInfo();
+       }
+}
+
+player_state_e RingView::GetPlayerState()
+{
+       if (!player_)
+               return PLAYER_STATE_NONE;
+
+       player_state_e playerState;
+       int ret = player_get_state(player_, &playerState);
+       if (ret != PLAYER_ERROR_NONE) {
+               ERR("player_get_state failed[%d]: %s ", ret, get_error_message(ret));
+               return PLAYER_STATE_NONE;
+       }
+
+       return playerState;
+}
+
+sound_stream_focus_state_e RingView::GetStreamFocusState()
+{
+       sound_stream_focus_state_e focusState;
+
+       int ret = sound_manager_get_focus_state(stream_info_, &focusState, NULL);
+       if (ret != SOUND_MANAGER_ERROR_NONE) {
+               ERR("sound_manager_get_focus_state failed[%d]: %s", ret, get_error_message(ret));
+               return SOUND_STREAM_FOCUS_STATE_RELEASED;
+       }
+
+       return focusState;
+}
+
+void RingView::StartVibration()
+{
+       int ret = feedback_initialize();
+       if (ret != FEEDBACK_ERROR_NONE) {
+               ERR("feedback_initialize failed[%d]: %s", ret, get_error_message(ret));
+               return;
+       }
+
+       bool status = false;
+       ret = feedback_is_supported_pattern(FEEDBACK_TYPE_VIBRATION, FEEDBACK_PATTERN_TIMER, &status);
+       if (ret != FEEDBACK_ERROR_NONE || !status) {
+               ERR("feedback_is_supported_pattern failed[%d]: %s", ret, get_error_message(ret));
+               feedback_deinitialize();
+               return;
+       }
+
+       ret = feedback_play_type(FEEDBACK_TYPE_VIBRATION, FEEDBACK_PATTERN_TIMER);
+       if (ret != FEEDBACK_ERROR_NONE) {
+               ERR("feedback_play_type failed[%d]: %s", ret, get_error_message(ret));
+               feedback_deinitialize();
+       }
+}
+
+void RingView::StopVibration(void)
+{
+       int ret = feedback_stop();
+       if (ret != FEEDBACK_ERROR_NONE) {
+               ERR("feedback_stop failed[%d]: %s", ret, get_error_message(ret));
+               return;
+       }
+
+       ret = feedback_deinitialize();
+       if (ret != FEEDBACK_ERROR_NONE)
+               ERR("feedback_deinitialize failed[%d]: %s", ret, get_error_message(ret));
+}
+
 } //namespace view
index b799d8c..2e81fbd 100644 (file)
@@ -6,8 +6,10 @@
         <icon>clock.png</icon>
     </ui-application>
     <privileges>
-        <privilege>http://tizen.org/privilege/window.priority.set</privilege>
-        <privilege>http://tizen.org/privilege/appmanager.launch</privilege>
         <privilege>http://tizen.org/privilege/alarm.set</privilege>
+        <privilege>http://tizen.org/privilege/appmanager.launch</privilege>
+       <privilege>http://tizen.org/privilege/window.priority.set</privilege>
+       <privilege>http://tizen.org/privilege/haptic</privilege>
+       <privilege>http://tizen.org/privilege/volume.set</privilege>
     </privileges>
 </manifest>