RDK-56073: initial support for user settings Change-Id: I9bd3333d46a07969424a3f952684a01059f47afc
diff --git a/src/third_party/starboard/rdk/shared/application_rdk.cc b/src/third_party/starboard/rdk/shared/application_rdk.cc index cbfa0f7..daebcb9 100644 --- a/src/third_party/starboard/rdk/shared/application_rdk.cc +++ b/src/third_party/starboard/rdk/shared/application_rdk.cc
@@ -433,6 +433,18 @@ return kSbTimeSecond; } +void Application::InjectAccessibilitySettingsChanged() { + Inject(new Event(kSbEventTypeAccessibilitySettingsChanged, NULL, NULL)); +} + +void Application::InjectAccessibilityCaptionSettingsChanged() { + Inject(new Event(kSbEventTypeAccessibilityCaptionSettingsChanged, NULL, NULL)); +} + +void Application::InjectAccessibilityTextToSpeechSettingsChanged() { + Inject(new Event(kSbEventTypeAccessibilityTextToSpeechSettingsChanged, NULL, NULL)); +} + } // namespace shared } // namespace rdk } // namespace starboard
diff --git a/src/third_party/starboard/rdk/shared/application_rdk.h b/src/third_party/starboard/rdk/shared/application_rdk.h index 1bd3dda..b61c8c8 100644 --- a/src/third_party/starboard/rdk/shared/application_rdk.h +++ b/src/third_party/starboard/rdk/shared/application_rdk.h
@@ -78,6 +78,10 @@ bool IsStartImmediate() override { return !HasPreloadSwitch(); } bool IsPreloadImmediate() override { return HasPreloadSwitch(); } + void InjectAccessibilitySettingsChanged(); + void InjectAccessibilityCaptionSettingsChanged(); + void InjectAccessibilityTextToSpeechSettingsChanged(); + protected: // --- Application overrides --- void Initialize() override;
diff --git a/src/third_party/starboard/rdk/shared/rdkservices.cc b/src/third_party/starboard/rdk/shared/rdkservices.cc index 215ef9f..8d86260 100644 --- a/src/third_party/starboard/rdk/shared/rdkservices.cc +++ b/src/third_party/starboard/rdk/shared/rdkservices.cc
@@ -72,7 +72,7 @@ const char kNetworkCallsign[] = "org.rdk.Network.1"; const char kTTSCallsign[] = "org.rdk.TextToSpeech.1"; const char kAuthServiceCallsign[] = "org.rdk.AuthService.1"; - +const char kUserSetingsCallsign[] = "org.rdk.UserSettings.1"; const char kDeviceInfoCallsign[] = "DeviceInfo.1"; const char kBluetoothCallsign[] = "org.rdk.Bluetooth.1"; @@ -241,191 +241,12 @@ SB_ONCE_INITIALIZE_FUNCTION(DeviceIdImpl, GetDeviceIdImpl); -struct TextToSpeechImpl { -private: - ::starboard::atomic_bool is_enabled_ { false }; - ::starboard::atomic_bool needs_refresh_ { true }; - ::starboard::atomic_bool did_subscribe_ { false }; - int64_t speech_id_ { -1 }; - int32_t speech_request_num_ { 0 }; - ServiceLink tts_link_ { kTTSCallsign }; - ::starboard::Mutex mutex_; - ::starboard::ConditionVariable condition_ { mutex_ }; - - std::string client_id_; - struct IsTTSEnabledInfo : public Core::JSON::Container { - IsTTSEnabledInfo() - : Core::JSON::Container() { - Add(_T("isenabled"), &IsEnabled); - } - IsTTSEnabledInfo(const IsTTSEnabledInfo&) = delete; - IsTTSEnabledInfo& operator=(const IsTTSEnabledInfo&) = delete; - - Core::JSON::Boolean IsEnabled; - }; - - struct SpeakResult : public Core::JSON::Container { - SpeakResult() - : Core::JSON::Container() - , SpeechId(-1) { - Add(_T("speechid"), &SpeechId); - } - SpeakResult(const SpeakResult&) = delete; - SpeakResult& operator=(const SpeakResult&) = delete; - - Core::JSON::DecSInt64 SpeechId; - }; - - struct StateInfo : public Core::JSON::Container { - StateInfo() - : Core::JSON::Container() - , State(false) { - Add(_T("state"), &State); - } - StateInfo(const StateInfo& other) - : Core::JSON::Container() - , State(other.State) { - Add(_T("state"), &State); - } - StateInfo& operator=(const StateInfo&) = delete; - - Core::JSON::Boolean State; - }; - - void OnCancelResult(const Core::JSON::String&, const Core::JSONRPC::Error*) { - } - - void OnStateChanged(const StateInfo& info) { - is_enabled_.store( info.State.Value() ); - } - - void OnSpeakResult(const SpeakResult& result, const Core::JSONRPC::Error* err) { - ::starboard::ScopedLock lock(mutex_); - if (err) { - SB_LOG(ERROR) - << "TTS speak request failed. Error code: " - << err->Code.Value() - << " message: " - << err->Text.Value(); - speech_id_ = -1; - } - else { - speech_id_ = result.SpeechId; - } - --speech_request_num_; - condition_.Broadcast(); - } - -public: - TextToSpeechImpl() = default; - - void Speak(const std::string &text) { - Refresh(); - if (!is_enabled_.load()) - return; - - JsonObject params; - params.Set(_T("text"), text); - params.Set(_T("callsign"), client_id_ ); - - uint64_t rc = tts_link_.Dispatch(kDefaultTimeoutMs, "speak", params, &TextToSpeechImpl::OnSpeakResult, this); - if (Core::ERROR_NONE == rc) { - ::starboard::ScopedLock lock(mutex_); - ++speech_request_num_; - } - } - - void Cancel() { - Refresh(); - if (!is_enabled_.load()) - return; - - int64_t speechId = -1; - - { - ::starboard::ScopedLock lock(mutex_); - if (speech_request_num_ != 0) { - if (!condition_.WaitTimed(kSbTimeMillisecond) || speech_request_num_ != 0) - return; - } - speechId = speech_id_; - } - - if (speechId < 0) - return; - - JsonObject params; - params.Set(_T("speechid"), speechId); - - tts_link_.Dispatch(kDefaultTimeoutMs, "cancel", params, &TextToSpeechImpl::OnCancelResult, this); - } - - bool IsEnabled() { - Refresh(); - return is_enabled_.load(); - } - - void Refresh() { - if (!needs_refresh_.load()) - return; - - uint32_t rc; - if (!did_subscribe_.load()) { - bool old_val = did_subscribe_.exchange(true); - if (old_val == false) { - rc = tts_link_.Subscribe<StateInfo>(kDefaultTimeoutMs, "onttsstatechanged", &TextToSpeechImpl::OnStateChanged, this); - if (Core::ERROR_UNAVAILABLE == rc || kPriviligedRequestErrorCode == rc) { - needs_refresh_.store(false); - SB_LOG(ERROR) << "Failed to subscribe to '" << kTTSCallsign - << ".onstatechanged' event, rc=" << rc - << " ( " << Core::ErrorToString(rc) << " )"; - return; - } - if (Core::ERROR_NONE != rc && Core::ERROR_DUPLICATE_KEY != rc) { - did_subscribe_.store(false); - SB_LOG(ERROR) << "Failed to subscribe to '" << kTTSCallsign - << ".onstatechanged' event, rc=" << rc - << " ( " << Core::ErrorToString(rc) << " )." - << " Going to try again next time."; - return; - } - } - } - - IsTTSEnabledInfo info; - rc = tts_link_.Get(kDefaultTimeoutMs, "isttsenabled", info); - if (Core::ERROR_NONE == rc) { - is_enabled_.store( info.IsEnabled.Value() ); - } - if (Core::SystemInfo::GetEnvironment(_T("CLIENT_IDENTIFIER"), client_id_) == true) { - std::string::size_type pos = client_id_.find(','); - if (pos != std::string::npos) - client_id_.erase(pos, std::string::npos); - } else { - client_id_ = "Cobalt"; - } - - needs_refresh_.store(false); - } - - void Teardown() { - if (did_subscribe_.load()) { - tts_link_.Unsubscribe(kDefaultTimeoutMs, "onttsstatechanged"); - did_subscribe_.store(false); - } - tts_link_.Teardown(); - needs_refresh_.store(true); - is_enabled_.store(false); - } -}; - -SB_ONCE_INITIALIZE_FUNCTION(TextToSpeechImpl, GetTextToSpeech); - struct AccessibilityImpl { private: ::starboard::Mutex mutex_; SbAccessibilityDisplaySettings display_settings_ { }; SbAccessibilityCaptionSettings caption_settings_ { }; + bool is_voice_guidance_enabled_ { false }; public: AccessibilityImpl() { @@ -448,7 +269,6 @@ } void SetSettings(const std::string& json) { - SB_LOG(INFO) << "Updating accessibility settings: " << json; JsonData::Accessibility::AccessibilityData settings; @@ -461,6 +281,9 @@ ::starboard::ScopedLock lock(mutex_); + bool was_cc_enabled = caption_settings_.is_enabled; + bool was_highcontrast_enabled = display_settings_.is_high_contrast_text_enabled; + memset(&display_settings_, 0, sizeof(display_settings_)); memset(&caption_settings_, 0, sizeof(caption_settings_)); @@ -512,6 +335,14 @@ display_settings_.is_high_contrast_text_enabled = settings.TextDisplay.IsHighContrastTextEnabled.Value(); } + + if (auto* app = Application::Get(); app != nullptr) { + if (was_cc_enabled != caption_settings_.is_enabled) + app->InjectAccessibilityCaptionSettingsChanged(); + + if (was_highcontrast_enabled != display_settings_.is_high_contrast_text_enabled) + app->InjectAccessibilitySettingsChanged(); + } } bool GetSettings(std::string& out_json) { @@ -567,10 +398,226 @@ return false; } + void SetCaptionEnabled(bool enabled, bool notify_on_change = true) { + { + ::starboard::ScopedLock lock(mutex_); + notify_on_change &= (caption_settings_.is_enabled != enabled); + caption_settings_.is_enabled = enabled; + } + if (notify_on_change && Application::Get()) { + SB_LOG(INFO) << "Accessibility closed caption setting changed, enabled = " << enabled; + Application::Get()->InjectAccessibilityCaptionSettingsChanged(); + } + } + + void SetHighContrastEnabled(bool enabled, bool notify_on_change = true) { + { + ::starboard::ScopedLock lock(mutex_); + notify_on_change &= (display_settings_.is_high_contrast_text_enabled != enabled); + display_settings_.is_high_contrast_text_enabled = enabled; + } + if (notify_on_change && Application::Get()) { + SB_LOG(INFO) << "Accessibility high contrast text setting changed, enabled = " << enabled; + Application::Get()->InjectAccessibilitySettingsChanged(); + } + } + + void SetVoiceGuidanceEnabled(bool enabled, bool notify_on_change = true) { + { + ::starboard::ScopedLock lock(mutex_); + notify_on_change &= (is_voice_guidance_enabled_ != enabled); + is_voice_guidance_enabled_ = enabled; + } + if (notify_on_change && Application::Get()) { + SB_LOG(INFO) << "Accessibility voice guidance setting changed, enabled = " << enabled; + Application::Get()->InjectAccessibilityTextToSpeechSettingsChanged(); + } + } + + bool GetVoiceGuidanceEnabled() const { + ::starboard::ScopedLock lock(mutex_); + return is_voice_guidance_enabled_; + } }; SB_ONCE_INITIALIZE_FUNCTION(AccessibilityImpl, GetAccessibility); +struct TextToSpeechImpl { +private: + ::starboard::atomic_bool is_enabled_ { false }; + ::starboard::atomic_bool needs_refresh_ { true }; + ::starboard::atomic_bool did_subscribe_ { false }; + int64_t speech_id_ { -1 }; + int32_t speech_request_num_ { 0 }; + ServiceLink tts_link_ { kTTSCallsign }; + ::starboard::Mutex mutex_; + ::starboard::ConditionVariable condition_ { mutex_ }; + std::string client_id_; + + struct IsTTSEnabledInfo : public Core::JSON::Container { + IsTTSEnabledInfo() + : Core::JSON::Container() { + Add(_T("isenabled"), &IsEnabled); + } + IsTTSEnabledInfo(const IsTTSEnabledInfo&) = delete; + IsTTSEnabledInfo& operator=(const IsTTSEnabledInfo&) = delete; + + Core::JSON::Boolean IsEnabled; + }; + + struct SpeakResult : public Core::JSON::Container { + SpeakResult() + : Core::JSON::Container() + , SpeechId(-1) { + Add(_T("speechid"), &SpeechId); + } + SpeakResult(const SpeakResult&) = delete; + SpeakResult& operator=(const SpeakResult&) = delete; + + Core::JSON::DecSInt64 SpeechId; + }; + + struct StateInfo : public Core::JSON::Container { + StateInfo() + : Core::JSON::Container() + , State(false) { + Add(_T("state"), &State); + } + StateInfo(const StateInfo& other) + : Core::JSON::Container() + , State(other.State) { + Add(_T("state"), &State); + } + StateInfo& operator=(const StateInfo&) = delete; + + Core::JSON::Boolean State; + }; + + void OnCancelResult(const Core::JSON::String&, const Core::JSONRPC::Error*) { + } + + void OnStateChanged(const StateInfo& info) { + is_enabled_.store( info.State.Value() ); + GetAccessibility()->SetVoiceGuidanceEnabled( info.State.Value() ); + } + + void OnSpeakResult(const SpeakResult& result, const Core::JSONRPC::Error* err) { + ::starboard::ScopedLock lock(mutex_); + if (err) { + SB_LOG(ERROR) + << "TTS speak request failed. Error code: " + << err->Code.Value() + << " message: " + << err->Text.Value(); + speech_id_ = -1; + } + else { + speech_id_ = result.SpeechId; + } + --speech_request_num_; + condition_.Broadcast(); + } + +public: + TextToSpeechImpl() = default; + + void Speak(const std::string &text) { + if (!IsEnabled()) + return; + + JsonObject params; + params.Set(_T("text"), text); + params.Set(_T("callsign"), client_id_ ); + + uint64_t rc = tts_link_.Dispatch(kDefaultTimeoutMs, "speak", params, &TextToSpeechImpl::OnSpeakResult, this); + if (Core::ERROR_NONE == rc) { + ::starboard::ScopedLock lock(mutex_); + ++speech_request_num_; + } + } + + void Cancel() { + int64_t speechId = -1; + + { + ::starboard::ScopedLock lock(mutex_); + if (speech_request_num_ != 0) { + if (!condition_.WaitTimed(kSbTimeMillisecond) || speech_request_num_ != 0) + return; + } + speechId = std::exchange(speech_id_, -1); + } + + if (speechId < 0) + return; + + JsonObject params; + params.Set(_T("speechid"), speechId); + + tts_link_.Dispatch(kDefaultTimeoutMs, "cancel", params, &TextToSpeechImpl::OnCancelResult, this); + } + + bool IsEnabled() { + Refresh(); + return is_enabled_.load() || GetAccessibility()->GetVoiceGuidanceEnabled(); + } + + void Refresh() { + if (!needs_refresh_.load()) + return; + + uint32_t rc; + if (!did_subscribe_.load()) { + bool old_val = did_subscribe_.exchange(true); + if (old_val == false) { + rc = tts_link_.Subscribe<StateInfo>(kDefaultTimeoutMs, "onttsstatechanged", &TextToSpeechImpl::OnStateChanged, this); + if (Core::ERROR_UNAVAILABLE == rc || kPriviligedRequestErrorCode == rc) { + needs_refresh_.store(false); + SB_LOG(ERROR) << "Failed to subscribe to '" << kTTSCallsign + << ".onstatechanged' event, rc=" << rc + << " ( " << Core::ErrorToString(rc) << " )"; + return; + } + if (Core::ERROR_NONE != rc && Core::ERROR_DUPLICATE_KEY != rc) { + did_subscribe_.store(false); + SB_LOG(ERROR) << "Failed to subscribe to '" << kTTSCallsign + << ".onstatechanged' event, rc=" << rc + << " ( " << Core::ErrorToString(rc) << " )." + << " Going to try again next time."; + return; + } + } + } + + IsTTSEnabledInfo info; + rc = tts_link_.Get(kDefaultTimeoutMs, "isttsenabled", info); + if (Core::ERROR_NONE == rc) { + is_enabled_.store( info.IsEnabled.Value() ); + } + if (Core::SystemInfo::GetEnvironment(_T("CLIENT_IDENTIFIER"), client_id_) == true) { + std::string::size_type pos = client_id_.find(','); + if (pos != std::string::npos) + client_id_.erase(pos, std::string::npos); + } else { + client_id_ = "Cobalt"; + } + + needs_refresh_.store(false); + } + + void Teardown() { + if (did_subscribe_.load()) { + tts_link_.Unsubscribe(kDefaultTimeoutMs, "onttsstatechanged"); + did_subscribe_.store(false); + } + tts_link_.Teardown(); + needs_refresh_.store(true); + is_enabled_.store(false); + } +}; + +SB_ONCE_INITIALIZE_FUNCTION(TextToSpeechImpl, GetTextToSpeech); + struct SystemPropertiesImpl { struct SystemPropertiesData : public Core::JSON::Container { SystemPropertiesData() @@ -1462,6 +1509,148 @@ return !out.empty(); } +struct UserSettingsImpl { +private: + ::starboard::Mutex mutex_; + std::vector<std::string> subscriptions_ { }; + bool needs_refresh_ { true }; + ServiceLink link_ { kUserSetingsCallsign }; + + struct StateChangedInfo : public Core::JSON::Container { + StateChangedInfo() + : Core::JSON::Container() + , Enabled(false) { + Add(_T("enabled"), &Enabled); + } + StateChangedInfo(const StateChangedInfo& other) + : Core::JSON::Container() + , Enabled(other.Enabled) { + Add(_T("enabled"), &Enabled); + } + StateChangedInfo& operator=(const StateChangedInfo&) = delete; + + Core::JSON::Boolean Enabled; + }; + + void OnVoiceGuidanceChanged(const StateChangedInfo& info) { + bool is_voice_guidance_enabled = info.Enabled.Value(); + SB_LOG(INFO) << "User setting changed. voice guidance enabled = " << is_voice_guidance_enabled; + GetAccessibility()->SetVoiceGuidanceEnabled(is_voice_guidance_enabled); + }; + + void OnHighContrastChanged(const StateChangedInfo& info) { + bool is_highcontrast_enabled = info.Enabled.Value(); + SB_LOG(INFO) << "User setting changed. highcontrast enabled = " << is_highcontrast_enabled; + GetAccessibility()->SetHighContrastEnabled(is_highcontrast_enabled); + }; + + void OnCaptionsChanged(const StateChangedInfo& info) { + bool is_cc_enabled = info.Enabled.Value(); + SB_LOG(INFO) << "User setting changed. cc enabled = " << is_cc_enabled; + GetAccessibility()->SetCaptionEnabled(is_cc_enabled); + }; + + bool RefreshSubscriptions() { + using NotificationHandlerCb = void(UserSettingsImpl::*)(const StateChangedInfo&); + + const std::map<std::string, NotificationHandlerCb> kNotificationHandlers = { + {"onVoiceGuidanceChanged", &UserSettingsImpl::OnVoiceGuidanceChanged }, + {"onHighContrastChanged", &UserSettingsImpl::OnHighContrastChanged }, + {"onCaptionsChanged", &UserSettingsImpl::OnCaptionsChanged }, + }; + + uint32_t rc; + + mutex_.DCheckAcquired(); + + for (const auto& kv : kNotificationHandlers) { + const auto& name = kv.first; + const auto& callback = kv.second; + + if (std::find(subscriptions_.begin(), subscriptions_.end(), name) + != subscriptions_.end()) + continue; + + rc = link_.Subscribe<StateChangedInfo>( + kDefaultTimeoutMs, name, callback, this); + + if (Core::ERROR_UNAVAILABLE == rc || kPriviligedRequestErrorCode == rc) { + needs_refresh_ = false; + SB_LOG(ERROR) << "Failed to subscribe to '" << kUserSetingsCallsign + << "." << name << "' event, rc = " << rc + << " ( " << Core::ErrorToString(rc) << " )"; + return false; + } + + if (Core::ERROR_NONE != rc && Core::ERROR_DUPLICATE_KEY != rc) { + SB_LOG(ERROR) << "Failed to subscribe to '" << kUserSetingsCallsign + << "." << name << "' event, rc = " << rc + << " ( " << Core::ErrorToString(rc) << " )."; + continue; + } + + subscriptions_.push_back(name); + } + + return true; + } + +public: + UserSettingsImpl() = default; + + void Refresh() { + ::starboard::ScopedLock lock(mutex_); + if (!needs_refresh_) + return; + + if (!RefreshSubscriptions()) + return; + + bool is_voice_guidance_enabled = false; + bool is_cc_enabled = false; + bool is_highcontrast_enabled = false; + + const std::map<std::string, bool*> kSettings = { + {"getVoiceGuidance", &is_voice_guidance_enabled}, + {"getHighContrast", &is_highcontrast_enabled}, + {"getCaptions", &is_cc_enabled}, + }; + + for (const auto& kv: kSettings) { + uint32_t rc; + Core::JSON::Boolean result; + + rc = link_.Get(kDefaultTimeoutMs, kv.first, result); + + if (Core::ERROR_NONE == rc) { + SB_LOG(INFO) << "User setting: " << kv.first << " = " << result.Value(); + (*kv.second) = result.Value(); + } else { + SB_LOG(ERROR) << "Failed to get user setting '" << kv.first<< ", rc=" << rc + << " ( " << Core::ErrorToString(rc) << " )."; + } + } + + needs_refresh_ = false; + + auto *accessibility = GetAccessibility(); + accessibility->SetVoiceGuidanceEnabled(is_voice_guidance_enabled, false); + accessibility->SetCaptionEnabled(is_cc_enabled, false); + accessibility->SetHighContrastEnabled(is_highcontrast_enabled, false); + } + + void Teardown() { + ::starboard::ScopedLock lock(mutex_); + for (const auto& subscription : subscriptions_) + link_.Unsubscribe(kDefaultTimeoutMs, subscription.c_str()); + link_.Teardown(); + needs_refresh_ = true; + subscriptions_.clear(); + } +}; + +SB_ONCE_INITIALIZE_FUNCTION(UserSettingsImpl, GetUserSettings); + } // namespace ResolutionInfo DisplayInfo::GetResolution() { @@ -1497,6 +1686,7 @@ } bool TextToSpeech::IsEnabled() { + GetUserSettings()->Refresh(); return GetTextToSpeech()->IsEnabled(); } @@ -1505,10 +1695,12 @@ } bool Accessibility::GetCaptionSettings(SbAccessibilityCaptionSettings* out) { + GetUserSettings()->Refresh(); return GetAccessibility()->GetCaptionSettings(out); } bool Accessibility::GetDisplaySettings(SbAccessibilityDisplaySettings* out) { + GetUserSettings()->Refresh(); return GetAccessibility()->GetDisplaySettings(out); } @@ -1601,6 +1793,7 @@ GetTextToSpeech()->Teardown(); GetNetworkInfo()->Teardown(); GetDeviceInfo()->Teardown(); + GetUserSettings()->Teardown(); } } // namespace shared