diff options
Diffstat (limited to 'src/components/application_manager/src/resumption')
5 files changed, 576 insertions, 140 deletions
diff --git a/src/components/application_manager/src/resumption/resume_ctrl_impl.cc b/src/components/application_manager/src/resumption/resume_ctrl_impl.cc index 72f2246e7c..3e77078ee5 100644 --- a/src/components/application_manager/src/resumption/resume_ctrl_impl.cc +++ b/src/components/application_manager/src/resumption/resume_ctrl_impl.cc @@ -35,6 +35,7 @@ #include <algorithm> #include "application_manager/application_manager.h" +#include "application_manager/rpc_service.h" #include "utils/file_system.h" #include "connection_handler/connection_handler_impl.h" @@ -47,17 +48,22 @@ #include "utils/helpers.h" #include "application_manager/resumption/resumption_data_db.h" #include "application_manager/resumption/resumption_data_json.h" -#include "utils/make_shared.h" + #include "utils/timer_task_impl.h" namespace resumption { using namespace application_manager; +static mobile_api::HMILevel::eType PickHigherHmiLevel( + mobile_api::HMILevel::eType val1, mobile_api::HMILevel::eType val2); +static mobile_api::HMILevel::eType PickLowerHmiLevel( + mobile_api::HMILevel::eType val1, mobile_api::HMILevel::eType val2); +static mobile_api::HMILevel::eType ConvertHmiLevelString(const std::string str); + CREATE_LOGGERPTR_GLOBAL(logger_, "Resumption") ResumeCtrlImpl::ResumeCtrlImpl(ApplicationManager& application_manager) : event_engine::EventObserver(application_manager.event_dispatcher()) - , queue_lock_(false) , restore_hmi_level_timer_( "RsmCtrlRstore", new timer::TimerTaskImpl<ResumeCtrlImpl>( @@ -68,13 +74,20 @@ ResumeCtrlImpl::ResumeCtrlImpl(ApplicationManager& application_manager) , is_resumption_active_(false) , is_data_saved_(false) , is_suspended_(false) - , launch_time_(time(NULL)) + , launch_time_(time(nullptr)) + , low_voltage_time_(0) + , wake_up_time_(0) , application_manager_(application_manager) {} #ifdef BUILD_TESTS void ResumeCtrlImpl::set_resumption_storage( - utils::SharedPtr<ResumptionData> mock_storage) { + std::shared_ptr<ResumptionData> mock_storage) { resumption_storage_ = mock_storage; } + +bool ResumeCtrlImpl::get_resumption_active() const { + sync_primitives::AutoLock auto_lock(queue_lock_); + return is_resumption_active_; +} #endif // BUILD_TESTS bool ResumeCtrlImpl::Init(resumption::LastState& last_state) { @@ -117,6 +130,8 @@ bool ResumeCtrlImpl::Init(resumption::LastState& last_state) { application_manager_.get_settings() .app_resumption_save_persistent_data_timeout(), timer::kPeriodic); + + resumption_storage_->IncrementGlobalIgnOnCounter(); return true; } @@ -133,9 +148,13 @@ void ResumeCtrlImpl::SaveAllApplications() { void ResumeCtrlImpl::SaveApplication(ApplicationSharedPtr application) { LOG4CXX_AUTO_TRACE(logger_); DCHECK_OR_RETURN_VOID(application); - LOG4CXX_INFO(logger_, - "application with appID " << application->app_id() - << " will be saved"); + if (application_manager_.IsLowVoltage()) { + LOG4CXX_DEBUG(logger_, "Low Voltage state is active"); + return; + } + LOG4CXX_DEBUG(logger_, + "application with appID " << application->app_id() + << " will be saved"); resumption_storage_->SaveApplication(application); } @@ -157,10 +176,38 @@ bool ResumeCtrlImpl::RestoreAppHMIState(ApplicationSharedPtr application) { if (result) { DCHECK_OR_RETURN(application, false); if (saved_app.keyExists(strings::hmi_level)) { - const HMILevel::eType saved_hmi_level = - static_cast<mobile_apis::HMILevel::eType>( - saved_app[strings::hmi_level].asInt()); - LOG4CXX_DEBUG(logger_, "Saved HMI Level is : " << saved_hmi_level); + HMILevel::eType saved_hmi_level; + if (HMILevel::eType::INVALID_ENUM != + application->deferred_resumption_hmi_level()) { + saved_hmi_level = application->deferred_resumption_hmi_level(); + LOG4CXX_INFO(logger_, + "Retry resuming into HMI level : " << saved_hmi_level); + application->set_deferred_resumption_hmi_level( + HMILevel::eType::INVALID_ENUM); + } else { + saved_hmi_level = static_cast<mobile_apis::HMILevel::eType>( + saved_app[strings::hmi_level].asInt()); + LOG4CXX_DEBUG(logger_, "Saved HMI Level is : " << saved_hmi_level); + } + + // Check one of the high-bandwidth transports (configured through + // smartDeviceLink.ini file) is available. If not, then the HMI level to + // resume into will be modified. + if (!application_manager_.CheckResumptionRequiredTransportAvailable( + application)) { + mobile_apis::HMILevel::eType low_bandwidth_level = + GetHmiLevelOnLowBandwidthTransport(application); + + application->set_deferred_resumption_hmi_level(saved_hmi_level); + + saved_hmi_level = + PickLowerHmiLevel(saved_hmi_level, low_bandwidth_level); + LOG4CXX_DEBUG( + logger_, + "High-bandwidth transport not available, app will resume into : " + << saved_hmi_level); + } + return SetAppHMIState(application, saved_hmi_level, true); } else { result = false; @@ -175,9 +222,23 @@ bool ResumeCtrlImpl::RestoreAppHMIState(ApplicationSharedPtr application) { bool ResumeCtrlImpl::SetupDefaultHMILevel(ApplicationSharedPtr application) { LOG4CXX_AUTO_TRACE(logger_); DCHECK_OR_RETURN(application, false); - mobile_apis::HMILevel::eType default_hmi = + mobile_apis::HMILevel::eType hmi_level = application_manager_.GetDefaultHmiLevel(application); - return SetAppHMIState(application, default_hmi, false); + + // Check one of the high-bandwidth transports (configured through + // smartDeviceLink.ini file) is available. If not, then the HMI level to + // resume into will be modified. + if (!application_manager_.CheckResumptionRequiredTransportAvailable( + application)) { + mobile_apis::HMILevel::eType low_bandwidth_level = + GetHmiLevelOnLowBandwidthTransport(application); + hmi_level = PickLowerHmiLevel(hmi_level, low_bandwidth_level); + LOG4CXX_DEBUG( + logger_, + "High-bandwidth transport not available, default HMI level is set to : " + << hmi_level); + } + return SetAppHMIState(application, hmi_level, false); } void ResumeCtrlImpl::ApplicationResumptiOnTimer() { @@ -257,6 +318,10 @@ uint32_t ResumeCtrlImpl::GetHMIApplicationID( bool ResumeCtrlImpl::RemoveApplicationFromSaved( ApplicationConstSharedPtr application) { + if (application_manager_.IsLowVoltage()) { + LOG4CXX_DEBUG(logger_, "Low Voltage state is active"); + return false; + } const std::string& device_mac = application->mac_address(); return resumption_storage_->RemoveApplicationFromSaved( application->policy_app_id(), device_mac); @@ -270,8 +335,11 @@ void ResumeCtrlImpl::OnSuspend() { void ResumeCtrlImpl::OnIgnitionOff() { LOG4CXX_AUTO_TRACE(logger_); - resumption_storage_->IncrementIgnOffCount(); - FinalPersistData(); + if (!application_manager_.IsLowVoltage()) { + resumption_storage_->IncrementIgnOffCount(); + resumption_storage_->ResetGlobalIgnOnCount(); + FinalPersistData(); + } } void ResumeCtrlImpl::OnAwake() { @@ -281,6 +349,25 @@ void ResumeCtrlImpl::OnAwake() { StartSavePersistentDataTimer(); } +void ResumeCtrlImpl::SaveLowVoltageTime() { + low_voltage_time_ = time(nullptr); + LOG4CXX_DEBUG(logger_, + "Low Voltage timestamp : " << low_voltage_time_ << " saved"); +} + +void ResumeCtrlImpl::SaveWakeUpTime() { + wake_up_time_ = std::time(nullptr); + LOG4CXX_DEBUG(logger_, "Wake Up timestamp : " << wake_up_time_ << " saved"); +} + +time_t ResumeCtrlImpl::LowVoltageTime() const { + return low_voltage_time_; +} + +time_t ResumeCtrlImpl::WakeUpTime() const { + return wake_up_time_; +} + bool ResumeCtrlImpl::is_suspended() const { return is_suspended_; } @@ -352,6 +439,28 @@ bool ResumeCtrlImpl::StartResumptionOnlyHMILevel( return result; } +void ResumeCtrlImpl::RetryResumption(const uint32_t app_id) { + LOG4CXX_AUTO_TRACE(logger_); + + ApplicationSharedPtr app = application_manager_.application(app_id); + if (!app) { + LOG4CXX_WARN(logger_, "Invalid app_id = " << app_id); + return; + } + + { + sync_primitives::AutoLock auto_lock(queue_lock_); + // check and update resumption deferred flag in queue_lock_ + if (mobile_api::HMILevel::eType::INVALID_ENUM == + app->deferred_resumption_hmi_level()) { + LOG4CXX_DEBUG(logger_, "No need to retry resumption for app: " << app_id); + return; + } + } + + AddToResumptionTimerQueue(app_id); +} + void ResumeCtrlImpl::StartAppHmiStateResumption( ApplicationSharedPtr application) { using namespace date_time; @@ -365,15 +474,33 @@ void ResumeCtrlImpl::StartAppHmiStateResumption( LOG4CXX_ERROR(logger_, "Application was not saved"); return; } - const uint32_t ign_off_count = saved_app[strings::ign_off_count].asUInt(); - bool restore_data_allowed = false; - restore_data_allowed = - CheckAppRestrictions(application, saved_app) && - ((0 == ign_off_count) || CheckIgnCycleRestrictions(saved_app)); - if (restore_data_allowed) { + + const bool is_hmi_level_applicable_to_resume = + CheckAppRestrictions(application, saved_app); + + if (!is_hmi_level_applicable_to_resume) { + LOG4CXX_DEBUG(logger_, "No applicable HMI level found for resuming"); + return; + } + + const bool is_resume_allowed_by_low_voltage = + CheckLowVoltageRestrictions(saved_app); + + const bool is_hmi_level_allowed_by_ign_cycle = + CheckIgnCycleRestrictions(saved_app); + + const bool restore_hmi_level_allowed = + is_resume_allowed_by_low_voltage && is_hmi_level_allowed_by_ign_cycle; + + if (restore_hmi_level_allowed) { LOG4CXX_INFO(logger_, "Resume application " << application->policy_app_id()); RestoreAppHMIState(application); + if (mobile_apis::HMILevel::eType::INVALID_ENUM != + application->deferred_resumption_hmi_level()) { + // the application has not been fully resumed + return; + } RemoveApplicationFromSaved(application); } else { LOG4CXX_INFO(logger_, @@ -544,8 +671,12 @@ void ResumeCtrlImpl::AddCommands(ApplicationSharedPtr application, saved_app[strings::application_commands]; for (size_t i = 0; i < app_commands.length(); ++i) { const smart_objects::SmartObject& command = app_commands[i]; + const uint32_t cmd_id = command[strings::cmd_id].asUInt(); + const bool is_resumption = true; - application->AddCommand(command[strings::cmd_id].asUInt(), command); + application->AddCommand(cmd_id, command); + application->help_prompt_manager().OnVrCommandAdded( + cmd_id, command, is_resumption); } ProcessHMIRequests(MessageHelper::CreateAddCommandRequestToHMI( application, application_manager_)); @@ -606,34 +737,25 @@ void ResumeCtrlImpl::AddSubscriptions( ApplicationSharedPtr application, const smart_objects::SmartObject& saved_app) { LOG4CXX_AUTO_TRACE(logger_); - if (saved_app.keyExists(strings::application_subscribtions)) { - const smart_objects::SmartObject& subscribtions = - saved_app[strings::application_subscribtions]; + if (saved_app.keyExists(strings::application_subscriptions)) { + const smart_objects::SmartObject& subscriptions = + saved_app[strings::application_subscriptions]; - if (subscribtions.keyExists(strings::application_buttons)) { - const smart_objects::SmartObject& subscribtions_buttons = - subscribtions[strings::application_buttons]; + if (subscriptions.keyExists(strings::application_buttons)) { + const smart_objects::SmartObject& subscriptions_buttons = + subscriptions[strings::application_buttons]; mobile_apis::ButtonName::eType btn; - for (size_t i = 0; i < subscribtions_buttons.length(); ++i) { + for (size_t i = 0; i < subscriptions_buttons.length(); ++i) { btn = static_cast<mobile_apis::ButtonName::eType>( - (subscribtions_buttons[i]).asInt()); + (subscriptions_buttons[i]).asInt()); application->SubscribeToButton(btn); } } MessageHelper::SendAllOnButtonSubscriptionNotificationsForApp( application, application_manager_); - if (subscribtions.keyExists(strings::application_vehicle_info)) { - const smart_objects::SmartObject& subscribtions_ivi = - subscribtions[strings::application_vehicle_info]; - mobile_apis::VehicleDataType::eType ivi; - for (size_t i = 0; i < subscribtions_ivi.length(); ++i) { - ivi = static_cast<mobile_apis::VehicleDataType::eType>( - (subscribtions_ivi[i]).asInt()); - application->SubscribeToIVI(ivi); - } - ProcessHMIRequests(MessageHelper::GetIVISubscriptionRequests( - application, application_manager_)); + for (auto& extension : application->Extensions()) { + extension->ProcessResumption(subscriptions); } } } @@ -641,30 +763,62 @@ void ResumeCtrlImpl::AddSubscriptions( bool ResumeCtrlImpl::CheckIgnCycleRestrictions( const smart_objects::SmartObject& saved_app) { LOG4CXX_AUTO_TRACE(logger_); - bool result = true; if (!CheckDelayAfterIgnOn()) { - LOG4CXX_INFO(logger_, "Application was connected long after ign on"); - result = false; + LOG4CXX_DEBUG(logger_, "Application was connected long after ign on"); + return false; } - if (!DisconnectedJustBeforeIgnOff(saved_app)) { - LOG4CXX_INFO(logger_, "Application was dissconnected long before ign off"); - result = false; + if (!CheckDelayBeforeIgnOff(saved_app)) { + LOG4CXX_DEBUG(logger_, "Application was disconnected long before ign off"); + return false; } - return result; + return true; } -bool ResumeCtrlImpl::DisconnectedJustBeforeIgnOff( +bool ResumeCtrlImpl::CheckLowVoltageRestrictions( const smart_objects::SmartObject& saved_app) { + LOG4CXX_AUTO_TRACE(logger_); + + if (!CheckDelayBeforeLowVoltage(saved_app)) { + LOG4CXX_DEBUG(logger_, + "Application was disconnected long before low voltage"); + return false; + } + + if (!CheckDelayAfterWakeUp()) { + LOG4CXX_DEBUG(logger_, "Application was connected long after wake up"); + return false; + } + + LOG4CXX_DEBUG(logger_, "HMI Level resuming in not restricted by Low Voltage"); + return true; +} + +bool ResumeCtrlImpl::CheckDelayBeforeIgnOff( + const smart_objects::SmartObject& saved_app) const { using namespace date_time; LOG4CXX_AUTO_TRACE(logger_); DCHECK_OR_RETURN(saved_app.keyExists(strings::time_stamp), false); const time_t time_stamp = static_cast<time_t>(saved_app[strings::time_stamp].asInt()); - time_t ign_off_time = + const time_t ign_off_time = static_cast<time_t>(resumption_storage_->GetIgnOffTime()); + + if (CheckIgnCyclesData() && 0 == ign_off_time) { + LOG4CXX_DEBUG( + logger_, "No IGNITION OFF records found: This is first Ignition cycle"); + return true; + } + + // This means that ignition off timestamp was not saved + // Possible reasons: Low Voltage event, core crash etc. + if (ign_off_time < time_stamp) { + LOG4CXX_DEBUG(logger_, "Last IGNITION OFF record missed"); + return true; + } + const uint32_t sec_spent_before_ign = labs(ign_off_time - time_stamp); LOG4CXX_DEBUG( logger_, @@ -677,6 +831,71 @@ bool ResumeCtrlImpl::DisconnectedJustBeforeIgnOff( application_manager_.get_settings().resumption_delay_before_ign(); } +bool ResumeCtrlImpl::CheckDelayBeforeLowVoltage( + const smart_objects::SmartObject& saved_app) const { + using namespace date_time; + LOG4CXX_AUTO_TRACE(logger_); + DCHECK_OR_RETURN(saved_app.keyExists(strings::time_stamp), false); + + if (0 == LowVoltageTime()) { + LOG4CXX_DEBUG(logger_, "No Low Voltage signal timestamp saved"); + return true; + } + + const time_t unregistration_time_stamp = + static_cast<time_t>(saved_app[strings::time_stamp].asInt()); + const time_t low_voltage_timestamp = static_cast<time_t>(LowVoltageTime()); + const int32_t sec_spent_before_low_voltage = + (low_voltage_timestamp - unregistration_time_stamp); + if (0 > sec_spent_before_low_voltage) { + LOG4CXX_DEBUG(logger_, + "Low Voltage time: " + << low_voltage_timestamp + << "; App disconnect time: " << unregistration_time_stamp + << "; Secs between app disconnect and low voltage event " + << sec_spent_before_low_voltage); + return true; + } + + const uint32_t secs_between_app_disconnect_and_low_voltage = + static_cast<uint32_t>(sec_spent_before_low_voltage); + const uint32_t wait_time = + application_manager_.get_settings().resumption_delay_before_ign(); + LOG4CXX_DEBUG(logger_, + "Low Voltage time: " + << low_voltage_timestamp + << "; App disconnect time: " << unregistration_time_stamp + << "; Secs between app disconnect and low voltage event " + << secs_between_app_disconnect_and_low_voltage + << "; Timeout for HMI level resuming: " << wait_time); + return secs_between_app_disconnect_and_low_voltage <= wait_time; +} + +bool ResumeCtrlImpl::CheckDelayAfterWakeUp() const { + using namespace date_time; + LOG4CXX_AUTO_TRACE(logger_); + + if (0 == WakeUpTime()) { + LOG4CXX_DEBUG(logger_, "No WakeUp signal timestamp saved"); + return true; + } + + const time_t current_time = time(nullptr); + const time_t wake_up_timestamp = static_cast<time_t>(WakeUpTime()); + + const uint32_t seconds_from_wake_up_signal = + labs(current_time - wake_up_timestamp); + const uint32_t wait_time = + application_manager_.get_settings().resumption_delay_after_ign(); + LOG4CXX_DEBUG( + logger_, + "Current time: " << current_time << "; WakeUp Signal time: " + << wake_up_timestamp << "; Seconds passed from wake up: " + << seconds_from_wake_up_signal + << "; Timeout for HMI level resuming: " << wait_time); + return seconds_from_wake_up_signal <= wait_time; +} + bool ResumeCtrlImpl::CheckAppRestrictions( ApplicationConstSharedPtr application, const smart_objects::SmartObject& saved_app) { @@ -685,16 +904,23 @@ bool ResumeCtrlImpl::CheckAppRestrictions( LOG4CXX_AUTO_TRACE(logger_); DCHECK_OR_RETURN(saved_app.keyExists(strings::hmi_level), false); - const HMILevel::eType hmi_level = - static_cast<HMILevel::eType>(saved_app[strings::hmi_level].asInt()); + HMILevel::eType hmi_level; + if (mobile_api::HMILevel::eType::INVALID_ENUM != + application->deferred_resumption_hmi_level()) { + hmi_level = application->deferred_resumption_hmi_level(); + } else { + hmi_level = + static_cast<HMILevel::eType>(saved_app[strings::hmi_level].asInt()); + } const bool result = Compare<HMILevel::eType, EQ, ONE>( hmi_level, HMILevel::HMI_FULL, HMILevel::HMI_LIMITED) ? true : false; LOG4CXX_DEBUG(logger_, - "is_media_app " << application->is_media_application() - << "; hmi_level " << hmi_level << " result " - << result); + "is_media_app: " << application->is_media_application() + << "; hmi_level: " << hmi_level << "; result: " + << (result ? "Applicable for resume" + : "Non-applicable for resume")); return result; } @@ -707,11 +933,36 @@ bool ResumeCtrlImpl::CheckIcons(ApplicationSharedPtr application, return mobile_apis::Result::INVALID_DATA != verify_images; } -bool ResumeCtrlImpl::CheckDelayAfterIgnOn() { +bool ResumeCtrlImpl::CheckIgnCyclesData() const { + LOG4CXX_AUTO_TRACE(logger_); + const uint32_t global_ign_on_count = + resumption_storage_->GetGlobalIgnOnCounter(); + const uint32_t the_first_ignition = 1; + const bool is_emergency_ign_off_occurred = + global_ign_on_count > the_first_ignition; + // global_ign_on_count is reseting to 0 at ignition off + // global_ign_on_count is incrementing at ignition on + // global_ign_on_count > 1 means that correct ignition off was not present. + if (is_emergency_ign_off_occurred) { + LOG4CXX_WARN(logger_, + "Emergency IGN OFF occurred. Possibly after Low Voltage"); + return false; + } + return true; +} + +bool ResumeCtrlImpl::CheckDelayAfterIgnOn() const { using namespace date_time; LOG4CXX_AUTO_TRACE(logger_); - const time_t curr_time = time(NULL); + const time_t ign_off_time = GetIgnOffTime(); + + if (CheckIgnCyclesData() && 0 == ign_off_time) { + LOG4CXX_DEBUG(logger_, "This is first Ignition cycle"); + return true; + } + const time_t curr_time = time(nullptr); const time_t sdl_launch_time = LaunchTime(); + const uint32_t seconds_from_sdl_start = labs(curr_time - sdl_launch_time); const uint32_t wait_time = application_manager_.get_settings().resumption_delay_after_ign(); @@ -727,7 +978,7 @@ time_t ResumeCtrlImpl::LaunchTime() const { return launch_time_; } -time_t ResumeCtrlImpl::GetIgnOffTime() { +time_t ResumeCtrlImpl::GetIgnOffTime() const { return resumption_storage_->GetIgnOffTime(); } @@ -743,7 +994,7 @@ bool ResumeCtrlImpl::ProcessHMIRequest(smart_objects::SmartObjectSPtr request, (*request)[strings::correlation_id].asInt(); subscribe_on_event(function_id, hmi_correlation_id); } - if (!application_manager_.ManageHMICommand(request)) { + if (!application_manager_.GetRPCService().ManageHMICommand(request)) { LOG4CXX_ERROR(logger_, "Unable to send request"); return false; } @@ -762,14 +1013,21 @@ void ResumeCtrlImpl::ProcessHMIRequests( void ResumeCtrlImpl::AddToResumptionTimerQueue(const uint32_t app_id) { LOG4CXX_AUTO_TRACE(logger_); + bool run_resumption = false; queue_lock_.Acquire(); waiting_for_timer_.push_back(app_id); + + if (!is_resumption_active_) { + is_resumption_active_ = true; + run_resumption = true; + } queue_lock_.Release(); LOG4CXX_DEBUG(logger_, "Application ID " << app_id << " have been added" " to resumption queue."); - if (!is_resumption_active_) { - is_resumption_active_ = true; + if (run_resumption) { + LOG4CXX_DEBUG(logger_, + "Application ID " << app_id << " will be restored by timer"); restore_hmi_level_timer_.Start( application_manager_.get_settings().app_resuming_timeout(), timer::kSingleShot); @@ -834,4 +1092,97 @@ bool ResumeCtrlImpl::IsAppDataResumptionExpired( return max_ign_off_count <= application[strings::ign_off_count].asInt(); } +mobile_apis::HMILevel::eType ResumeCtrlImpl::GetHmiLevelOnLowBandwidthTransport( + ApplicationConstSharedPtr application) const { + using namespace mobile_apis; + LOG4CXX_AUTO_TRACE(logger_); + + const ApplicationManagerSettings& settings = + application_manager_.get_settings(); + const std::string& level_for_navi_app = + settings.navigation_lowbandwidth_resumption_level(); + const std::string& level_for_projection_app = + settings.projection_lowbandwidth_resumption_level(); + const std::string& level_for_media_app = + settings.media_lowbandwidth_resumption_level(); + + HMILevel::eType result_level = HMILevel::HMI_NONE; + HMILevel::eType level; + + // NAVIGATION, PROJECTION and media apps have special exceptions. Their HMI + // level can be configured through .ini file. when the app has multiple + // AppHMIType, then the highest level will be applied. + if (application->is_navi()) { + level = ConvertHmiLevelString(level_for_navi_app); + LOG4CXX_DEBUG(logger_, + "NAVIGATION apps may have level " + << level + << " while high-bandwidth transport is not available."); + result_level = PickHigherHmiLevel(level, result_level); + } + if (application->mobile_projection_enabled()) { + level = ConvertHmiLevelString(level_for_projection_app); + LOG4CXX_DEBUG(logger_, + "PROJECTION apps may have level " + << level + << " while high-bandwidth transport is not available."); + result_level = PickHigherHmiLevel(level, result_level); + } + if (application->is_media_application()) { + level = ConvertHmiLevelString(level_for_media_app); + LOG4CXX_DEBUG(logger_, + "media apps may have level " + << level + << " while high-bandwidth transport is not available."); + result_level = PickHigherHmiLevel(level, result_level); + } + + return result_level; +} + +static mobile_api::HMILevel::eType PickHigherHmiLevel( + mobile_api::HMILevel::eType val1, mobile_api::HMILevel::eType val2) { + using namespace mobile_apis; + + if (val1 == HMILevel::INVALID_ENUM) { + return val2; + } else if (val2 == HMILevel::INVALID_ENUM) { + return val1; + } + + // smaller enum value has higher level + return val1 < val2 ? val1 : val2; +} + +static mobile_api::HMILevel::eType PickLowerHmiLevel( + mobile_api::HMILevel::eType val1, mobile_api::HMILevel::eType val2) { + using namespace mobile_apis; + + if (val1 == HMILevel::INVALID_ENUM) { + return val2; + } else if (val2 == HMILevel::INVALID_ENUM) { + return val1; + } + + // bigger enum value has lower level + return val1 > val2 ? val1 : val2; +} + +static mobile_api::HMILevel::eType ConvertHmiLevelString( + const std::string str) { + using namespace mobile_apis; + + if ("BACKGROUND" == str) { + return HMILevel::HMI_BACKGROUND; + } else if ("FULL" == str) { + return HMILevel::HMI_FULL; + } else if ("LIMITED" == str) { + return HMILevel::HMI_LIMITED; + } else if ("NONE" == str) { + return HMILevel::HMI_NONE; + } else { + return HMILevel::HMI_NONE; + } +} + } // namespce resumption diff --git a/src/components/application_manager/src/resumption/resumption_data.cc b/src/components/application_manager/src/resumption/resumption_data.cc index bd5bdbddab..856fb03750 100644 --- a/src/components/application_manager/src/resumption/resumption_data.cc +++ b/src/components/application_manager/src/resumption/resumption_data.cc @@ -41,7 +41,7 @@ CREATE_LOGGERPTR_GLOBAL(logger_, "Resumption") ResumptionData::ResumptionData( const application_manager::ApplicationManager& application_manager) - : resumption_lock_(true), application_manager_(application_manager) {} + : application_manager_(application_manager) {} smart_objects::SmartObject ResumptionData::GetApplicationCommands( app_mngr::ApplicationConstSharedPtr application) const { @@ -159,16 +159,9 @@ smart_objects::SmartObject ResumptionData::GetApplicationSubscriptions( strings::application_buttons, subscriptions); - DataAccessor<VehicleInfoSubscriptions> vi_accessor = - application->SubscribedIVI(); - - const VehicleInfoSubscriptions& vi_subscription = vi_accessor.GetData(); - - LOG4CXX_DEBUG(logger_, "SubscribedIVI:" << vi_subscription.size()); - Append(vi_subscription.begin(), - vi_subscription.end(), - strings::application_vehicle_info, - subscriptions); + for (auto extension : application->Extensions()) { + extension->SaveResumptionData(subscriptions); + } return subscriptions; } diff --git a/src/components/application_manager/src/resumption/resumption_data_db.cc b/src/components/application_manager/src/resumption/resumption_data_db.cc index b031fd0afd..1591ce566a 100644 --- a/src/components/application_manager/src/resumption/resumption_data_db.cc +++ b/src/components/application_manager/src/resumption/resumption_data_db.cc @@ -163,11 +163,10 @@ void ResumptionDataDB::SaveApplication( } if (application->is_application_data_changed()) { - if (application_exist) { - if (!DeleteSavedApplication(policy_app_id, device_mac)) { - LOG4CXX_ERROR(logger_, "Deleting of application data is not finished"); - return; - } + if (application_exist && + !DeleteSavedApplication(policy_app_id, device_mac)) { + LOG4CXX_ERROR(logger_, "Deleting of application data is not finished"); + return; } if (!SaveApplicationToDB(application, policy_app_id, device_mac)) { @@ -176,23 +175,15 @@ void ResumptionDataDB::SaveApplication( } LOG4CXX_INFO(logger_, "All data from application were saved successfully"); application->set_is_application_data_changed(false); - } else { - if (application_exist) { - if (!UpdateApplicationData(application, policy_app_id, device_mac)) { - LOG4CXX_ERROR(logger_, "Updating application data is failed"); - return; - } - LOG4CXX_INFO(logger_, "Application data were updated successfully"); - } else { - if (Compare<HMILevel::eType, EQ, ONE>(application->hmi_level(), - HMILevel::HMI_FULL, - HMILevel::HMI_LIMITED)) { - if (!InsertApplicationData(application, policy_app_id, device_mac)) { - LOG4CXX_ERROR(logger_, "Saving data of application is failed"); - return; - } - } + } else if (application_exist) { + if (!UpdateApplicationData(application, policy_app_id, device_mac)) { + LOG4CXX_ERROR(logger_, "Updating application data is failed"); + return; } + LOG4CXX_INFO(logger_, "Application data were updated successfully"); + } else if (!InsertApplicationData(application, policy_app_id, device_mac)) { + LOG4CXX_ERROR(logger_, "Saving data of application is failed"); + return; } WriteDb(); } @@ -212,8 +203,6 @@ uint32_t ResumptionDataDB::GetHMIApplicationID( return hmi_app_id; } -DEPRECATED void ResumptionDataDB::OnSuspend() {} - void ResumptionDataDB::IncrementIgnOffCount() { LOG4CXX_AUTO_TRACE(logger_); @@ -240,7 +229,7 @@ void ResumptionDataDB::IncrementIgnOffCount() { } } - if (query_update_last_ign_off_time.Prepare(KUpdateLastIgnOffTime)) { + if (query_update_last_ign_off_time.Prepare(kUpdateLastIgnOffTime)) { query_update_last_ign_off_time.Bind(0, static_cast<int64_t>(time(NULL))); if (query_update_last_ign_off_time.Exec()) { LOG4CXX_INFO(logger_, "Data last_ign_off_time was updated"); @@ -293,8 +282,6 @@ bool ResumptionDataDB::GetHashId(const std::string& policy_app_id, return SelectHashId(policy_app_id, device_id, hash_id); } -DEPRECATED void ResumptionDataDB::OnAwake() {} - void ResumptionDataDB::DecrementIgnOffCount() { LOG4CXX_AUTO_TRACE(logger_); @@ -376,10 +363,61 @@ bool ResumptionDataDB::RemoveApplicationFromSaved( uint32_t ResumptionDataDB::GetIgnOffTime() const { LOG4CXX_AUTO_TRACE(logger_); - return SelectIgnOffTime(); } +uint32_t ResumptionDataDB::GetGlobalIgnOnCounter() const { + LOG4CXX_AUTO_TRACE(logger_); + sync_primitives::AutoLock autolock(resumption_lock_); + + utils::dbms::SQLQuery query(db()); + if (!query.Prepare(kSelectGlobalIgnOnCounter)) { + LOG4CXX_ERROR(logger_, + "Problem with prepare query : " << kSelectGlobalIgnOnCounter); + return 1; + } + + if (!query.Exec()) { + LOG4CXX_ERROR(logger_, + "Problem with exec query : " << kSelectGlobalIgnOnCounter); + return 1; + } + + const auto global_ign_on_counter = query.GetUInteger(0); + LOG4CXX_DEBUG(logger_, "Global Ign On Counter = " << global_ign_on_counter); + return global_ign_on_counter; +} + +void ResumptionDataDB::IncrementGlobalIgnOnCounter() { + LOG4CXX_AUTO_TRACE(logger_); + sync_primitives::AutoLock autolock(resumption_lock_); + + db_->BeginTransaction(); + utils::dbms::SQLQuery query_update_global_ign_on_count(db()); + if (query_update_global_ign_on_count.Prepare(kUpdateGlobalIgnOnCount)) { + if (query_update_global_ign_on_count.Exec()) { + LOG4CXX_DEBUG(logger_, + "Data query_update_global_ign_on_count was updated"); + } + } + db_->CommitTransaction(); + WriteDb(); +} + +void ResumptionDataDB::ResetGlobalIgnOnCount() { + LOG4CXX_AUTO_TRACE(logger_); + sync_primitives::AutoLock autolock(resumption_lock_); + + LOG4CXX_DEBUG(logger_, "Global IGN ON counter resetting"); + + utils::dbms::SQLQuery query_update_global_ign_on_count(db()); + if (query_update_global_ign_on_count.Prepare(kResetGlobalIgnOnCount)) { + if (query_update_global_ign_on_count.Exec()) { + LOG4CXX_DEBUG(logger_, "Data was updated"); + } + } +} + ssize_t ResumptionDataDB::IsApplicationSaved( const std::string& policy_app_id, const std::string& device_id) const { LOG4CXX_AUTO_TRACE(logger_); @@ -557,7 +595,6 @@ void ResumptionDataDB::SelectDataForLoadResumeData( using namespace app_mngr; using namespace smart_objects; LOG4CXX_AUTO_TRACE(logger_); - utils::dbms::SQLQuery select_data(db()); utils::dbms::SQLQuery count_application(db()); if (!select_data.Prepare(kSelectDataForLoadResumeData) || @@ -953,7 +990,7 @@ bool ResumptionDataDB::SelectSubscriptionsData( return false; } - saved_app[strings::application_subscribtions] = SmartObject(SmartType_Map); + saved_app[strings::application_subscriptions] = SmartObject(SmartType_Map); if (0 == count_item) { LOG4CXX_INFO(logger_, "Application does not contain subscriptions data"); @@ -972,8 +1009,8 @@ bool ResumptionDataDB::SelectSubscriptionsData( size_t buttons_idx = 0; size_t vi_idx = 0; /* Position of data in "select_subscriptions" : - field "vehicleValue" from table "applicationSubscribtionsArray" = 0 - field "ButtonNameValue" from table "applicationSubscribtionsArray" = 1*/ + field "vehicleValue" from table "applicationSubscriptionsArray" = 0 + field "ButtonNameValue" from table "applicationSubscriptionsArray" = 1*/ while (select_subscriptions.Next()) { if (!select_subscriptions.IsNull(0)) { application_vehicle_info[vi_idx++] = select_subscriptions.GetInteger(0); @@ -983,12 +1020,12 @@ bool ResumptionDataDB::SelectSubscriptionsData( } } if (!application_buttons.empty()) { - saved_app[strings::application_subscribtions] + saved_app[strings::application_subscriptions] [strings::application_buttons] = application_buttons; } if (!application_vehicle_info.empty()) { - saved_app[strings::application_subscribtions] + saved_app[strings::application_subscriptions] [strings::application_vehicle_info] = application_vehicle_info; } LOG4CXX_INFO(logger_, "Subscriptions were restored from DB successfully"); @@ -1504,9 +1541,9 @@ bool ResumptionDataDB::DeleteSavedSubscriptions( LOG4CXX_AUTO_TRACE(logger_); if (!ExecQueryToDeleteData( - policy_app_id, device_id, kDeleteApplicationSubscribtionsArray)) { + policy_app_id, device_id, kDeleteApplicationSubscriptionsArray)) { LOG4CXX_WARN(logger_, - "Incorrect delete from applicationSubscribtionsArray."); + "Incorrect delete from applicationSubscriptionsArray."); return false; } return true; @@ -1899,7 +1936,7 @@ bool ResumptionDataDB::SaveApplicationToDB( } if (!InsertSubscriptionsData(GetApplicationSubscriptions(application), application_primary_key)) { - LOG4CXX_WARN(logger_, "Incorrect insert subscribtions data to DB."); + LOG4CXX_WARN(logger_, "Incorrect insert subscriptions data to DB."); db_->RollbackTransaction(); return false; } @@ -1956,9 +1993,9 @@ bool ResumptionDataDB::SaveApplicationToDB( db_->RollbackTransaction(); return false; } - if (!InsertSubscriptionsData(application["subscribtions"], + if (!InsertSubscriptionsData(application["subscriptions"], application_primary_key)) { - LOG4CXX_WARN(logger_, "Incorrect insert subscribtions data to DB."); + LOG4CXX_WARN(logger_, "Incorrect insert subscriptions data to DB."); db_->RollbackTransaction(); return false; } @@ -2164,9 +2201,9 @@ bool ResumptionDataDB::InsertSubscriptionsData( return false; } /* Positions of binding data for "insert_subscriptions": - field "idApplication" from table "applicationSubscribtionsArray" = 0 - field "vehicleValue" from table "applicationSubscribtionsArray" = 1 - field "ButtonNameValue" from table "applicationSubscribtionsArray" = 2*/ + field "idApplication" from table "applicationSubscriptionsArray" = 0 + field "vehicleValue" from table "applicationSubscriptionsArray" = 1 + field "ButtonNameValue" from table "applicationSubscriptionsArray" = 2*/ for (size_t i = 0; i < max_length; ++i) { insert_subscriptions.Bind(0, application_primary_key); if (i < vi_sub_length) { diff --git a/src/components/application_manager/src/resumption/resumption_data_json.cc b/src/components/application_manager/src/resumption/resumption_data_json.cc index b8ce6a76c4..c8e9032dfa 100644 --- a/src/components/application_manager/src/resumption/resumption_data_json.cc +++ b/src/components/application_manager/src/resumption/resumption_data_json.cc @@ -41,7 +41,7 @@ namespace resumption { -namespace Formatters = NsSmartDeviceLink::NsJSONHandler::Formatters; +namespace formatters = ns_smart_device_link::ns_json_handler::formatters; CREATE_LOGGERPTR_GLOBAL(logger_, "Resumption") @@ -81,22 +81,22 @@ void ResumptionDataJson::SaveApplication( json_app[strings::hmi_level] = static_cast<int32_t>(hmi_level); json_app[strings::ign_off_count] = 0; json_app[strings::hash_id] = hash; - Formatters::CFormatterJsonBase::objToJsonValue( + formatters::CFormatterJsonBase::objToJsonValue( GetApplicationCommands(application), tmp); json_app[strings::application_commands] = tmp; - Formatters::CFormatterJsonBase::objToJsonValue( + formatters::CFormatterJsonBase::objToJsonValue( GetApplicationSubMenus(application), tmp); json_app[strings::application_submenus] = tmp; - Formatters::CFormatterJsonBase::objToJsonValue( + formatters::CFormatterJsonBase::objToJsonValue( GetApplicationInteractionChoiseSets(application), tmp); json_app[strings::application_choice_sets] = tmp; - Formatters::CFormatterJsonBase::objToJsonValue( + formatters::CFormatterJsonBase::objToJsonValue( GetApplicationGlobalProperties(application), tmp); json_app[strings::application_global_properties] = tmp; - Formatters::CFormatterJsonBase::objToJsonValue( + formatters::CFormatterJsonBase::objToJsonValue( GetApplicationSubscriptions(application), tmp); - json_app[strings::application_subscribtions] = tmp; - Formatters::CFormatterJsonBase::objToJsonValue( + json_app[strings::application_subscriptions] = tmp; + formatters::CFormatterJsonBase::objToJsonValue( GetApplicationFiles(application), tmp); json_app[strings::application_files] = tmp; json_app[strings::time_stamp] = time_stamp; @@ -143,8 +143,6 @@ uint32_t ResumptionDataJson::GetHMIApplicationID( return hmi_app_id; } -DEPRECATED void ResumptionDataJson::OnSuspend() {} - void ResumptionDataJson::IncrementIgnOffCount() { using namespace app_mngr; LOG4CXX_AUTO_TRACE(logger_); @@ -169,8 +167,6 @@ void ResumptionDataJson::IncrementIgnOffCount() { LOG4CXX_DEBUG(logger_, GetResumptionData().toStyledString()); } -DEPRECATED void ResumptionDataJson::OnAwake() {} - void ResumptionDataJson::DecrementIgnOffCount() { using namespace app_mngr; LOG4CXX_AUTO_TRACE(logger_); @@ -229,7 +225,7 @@ bool ResumptionDataJson::GetSavedApplication( return false; } const Json::Value& json_saved_app = GetSavedApplications()[idx]; - Formatters::CFormatterJsonBase::jsonValueToObj(json_saved_app, saved_app); + formatters::CFormatterJsonBase::jsonValueToObj(json_saved_app, saved_app); return true; } @@ -283,6 +279,51 @@ uint32_t ResumptionDataJson::GetIgnOffTime() const { return resumption[strings::last_ign_off_time].asUInt(); } +uint32_t ResumptionDataJson::GetGlobalIgnOnCounter() const { + using namespace app_mngr; + LOG4CXX_AUTO_TRACE(logger_); + sync_primitives::AutoLock autolock(resumption_lock_); + Json::Value& resumption = GetResumptionData(); + if (resumption.isMember(strings::global_ign_on_counter)) { + const uint32_t global_ign_on_counter = + resumption[strings::global_ign_on_counter].asUInt(); + LOG4CXX_DEBUG(logger_, "Global Ign On Counter = " << global_ign_on_counter); + return global_ign_on_counter; + } + return 1; +} + +void ResumptionDataJson::IncrementGlobalIgnOnCounter() { + using namespace app_mngr; + LOG4CXX_AUTO_TRACE(logger_); + sync_primitives::AutoLock autolock(resumption_lock_); + Json::Value& resumption = GetResumptionData(); + if (resumption.isMember(strings::global_ign_on_counter)) { + const uint32_t global_ign_on_counter = + resumption[strings::global_ign_on_counter].asUInt(); + LOG4CXX_DEBUG( + logger_, + "Global IGN ON counter in resumption data: " << global_ign_on_counter); + resumption[strings::global_ign_on_counter] = global_ign_on_counter + 1; + LOG4CXX_DEBUG(logger_, + "Global IGN ON counter new value: " + << resumption[strings::global_ign_on_counter].asUInt()); + } else { + resumption[strings::global_ign_on_counter] = 1; + } + last_state().SaveStateToFileSystem(); +} + +void ResumptionDataJson::ResetGlobalIgnOnCount() { + using namespace app_mngr; + LOG4CXX_AUTO_TRACE(logger_); + sync_primitives::AutoLock autolock(resumption_lock_); + Json::Value& resumption = GetResumptionData(); + + resumption[strings::global_ign_on_counter] = 0; + LOG4CXX_DEBUG(logger_, "Global IGN ON counter resetting"); +} + ssize_t ResumptionDataJson::IsApplicationSaved( const std::string& policy_app_id, const std::string& device_id) const { LOG4CXX_AUTO_TRACE(logger_); @@ -473,7 +514,7 @@ bool ResumptionDataJson::DropAppDataResumption(const std::string& device_id, application[strings::application_submenus].clear(); application[strings::application_choice_sets].clear(); application[strings::application_global_properties].clear(); - application[strings::application_subscribtions].clear(); + application[strings::application_subscriptions].clear(); application[strings::application_files].clear(); application.removeMember(strings::grammar_id); LOG4CXX_DEBUG(logger_, diff --git a/src/components/application_manager/src/resumption/resumption_sql_queries.cc b/src/components/application_manager/src/resumption/resumption_sql_queries.cc index 7a5008542c..4770bafef4 100644 --- a/src/components/application_manager/src/resumption/resumption_sql_queries.cc +++ b/src/components/application_manager/src/resumption/resumption_sql_queries.cc @@ -37,6 +37,7 @@ const std::string kCreateSchema = "BEGIN ; " "CREATE TABLE IF NOT EXISTS `resumption`( " " `idresumption` INTEGER PRIMARY KEY, " + " `global_ign_on_count` INTEGER, " " `last_ign_off_time` INTEGER " " ); " "CREATE TABLE IF NOT EXISTS `subscribedForWayPoints`( " @@ -298,8 +299,8 @@ const std::string kCreateSchema = "CREATE INDEX IF NOT EXISTS " "`applicationSubMenuArray.fk_Application_idx` " " ON `applicationSubMenuArray`(`idApplication`); " - "CREATE TABLE IF NOT EXISTS `applicationSubscribtionsArray`( " - " `idapplicationSubscribtionsArray` INTEGER PRIMARY KEY NOT NULL, " + "CREATE TABLE IF NOT EXISTS `applicationSubscriptionsArray`( " + " `idapplicationSubscriptionsArray` INTEGER PRIMARY KEY NOT NULL, " " `vehicleValue` INTEGER, " " `ButtonNameValue` INTEGER, " " `idApplication` INTEGER, " @@ -308,8 +309,8 @@ const std::string kCreateSchema = " REFERENCES `application`(`idApplication`) " " ); " "CREATE INDEX IF NOT EXISTS " - "`applicationSubscribtionsArray.fk_Application_idx` " - " ON `applicationSubscribtionsArray`(`idApplication`); " + "`applicationSubscriptionsArray.fk_Application_idx` " + " ON `applicationSubscriptionsArray`(`idApplication`); " "CREATE TABLE IF NOT EXISTS `_internal_data`( " " `db_version_hash` INTEGER " " ); " @@ -365,14 +366,16 @@ const std::string kDropSchema = "DROP TABLE IF EXISTS `applicationSubMenuArray`; " "DROP INDEX IF EXISTS `applicationSubMenuArray.fk_subMenu_idx`; " "DROP INDEX IF EXISTS `applicationSubMenuArray.fk_Application_idx`; " - "DROP TABLE IF EXISTS `applicationSubscribtionsArray`; " - "DROP INDEX IF EXISTS `applicationSubscribtionsArray.fk_Application_idx`; " + "DROP TABLE IF EXISTS `applicationSubscriptionsArray`; " + "DROP INDEX IF EXISTS `applicationSubscriptionsArray.fk_Application_idx`; " "DROP TABLE IF EXISTS `_internal_data`; " "COMMIT; " "VACUUM;"; const std::string kInsertInitData = - "INSERT OR IGNORE INTO `resumption` (`last_ign_off_time`) VALUES (0); " + "INSERT OR IGNORE INTO `resumption` " + "(`last_ign_off_time`, `global_ign_on_count`) " + "VALUES (0, 0); " "INSERT OR IGNORE INTO `_internal_data` (`db_version_hash`) VALUES(0); "; const std::string kChecksResumptionData = @@ -410,6 +413,17 @@ const std::string kSelectHashId = const std::string kSelectIgnOffTime = "SELECT `last_ign_off_time` FROM `resumption`"; +const std::string kSelectGlobalIgnOnCounter = + "SELECT `global_ign_on_count` FROM `resumption`"; + +const std::string kResetGlobalIgnOnCount = + "UPDATE `resumption` " + "SET `global_ign_on_count` = 0"; + +const std::string kUpdateGlobalIgnOnCount = + "UPDATE `resumption` " + "SET `global_ign_on_count` = `global_ign_on_count` + 1 "; + const std::string kCheckApplication = "SELECT COUNT (`deviceID`) FROM `application` " "WHERE `deviceID` = ? AND `appID` = ?"; @@ -445,7 +459,7 @@ const std::string kUpdateSuspendData = "UPDATE `application` " "SET `ign_off_count` = `ign_off_count` + 1"; -const std::string KUpdateLastIgnOffTime = +const std::string kUpdateLastIgnOffTime = "UPDATE `resumption` " "SET `last_ign_off_time` = ?"; @@ -477,8 +491,8 @@ const std::string kDeleteApplicationSubMenuArray = "FROM `application` " "WHERE `appID` = ? AND `deviceID` = ?)"; -const std::string kDeleteApplicationSubscribtionsArray = - "DELETE FROM `applicationSubscribtionsArray` " +const std::string kDeleteApplicationSubscriptionsArray = + "DELETE FROM `applicationSubscriptionsArray` " "WHERE `idApplication` = (SELECT `idApplication` " "FROM `application` " "WHERE `appID` = ? AND `deviceID` = ?)"; @@ -713,7 +727,7 @@ const std::string kInsertVrCommand = "(?, ?, ?);"; const std::string kInsertSubscriptions = - "INSERT INTO `applicationSubscribtionsArray` " + "INSERT INTO `applicationSubscriptionsArray` " "(`idApplication`, `vehicleValue`, `ButtonNameValue`) " "VALUES " "(?, ?, ?);"; @@ -851,14 +865,14 @@ const std::string kSelectCommands = const std::string kSelectCountSubscriptions = "SELECT COUNT (`idApplication`) " - "FROM `applicationSubscribtionsArray` " + "FROM `applicationSubscriptionsArray` " "WHERE `idApplication` = (SELECT `idApplication` " "FROM `application` " "WHERE `appID` = ? AND `deviceID` = ?);"; const std::string kSelectSubscriptions = "SELECT `vehicleValue`, `ButtonNameValue` " - "FROM `applicationSubscribtionsArray` " + "FROM `applicationSubscriptionsArray` " "WHERE `idApplication` = (SELECT `idApplication` " "FROM `application` " "WHERE `appID` = ? AND `deviceID` = ?);"; |