/* * @file Application.cpp * @author Robert Borzecki (robert.borzecki@mudita.com) * @date 1 cze 2019 * @brief * @copyright Copyright (C) 2019 mudita.com * @details */ #include #include //module-utils #include "log/log.hpp" //module-services #include "service-gui/messages/DrawMessage.hpp" #include "service-appmgr/messages/APMMessage.hpp" #include "service-evtmgr/messages/EVMessages.hpp" #include "service-appmgr/ApplicationManager.hpp" #include "service-db/api/DBServiceAPI.hpp" #include "service-cellular/ServiceCellular.hpp" #include "service-cellular/api/CellularServiceAPI.hpp" //module-gui #include "gui/core/DrawCommand.hpp" #include "gui/input/InputEvent.hpp" //module-sys #include "SystemManager/SystemManager.hpp" #include "Application.hpp" #include "MessageType.hpp" #include "messages/AppMessage.hpp" #include "windows/AppWindow.hpp" #include namespace app { const char *Application::stateStr(Application::State st) { switch (st) { case State::NONE: return "NONE"; case State::DEACTIVATED: return "DEACTIVATED"; case State::INITIALIZING: return "INITIALIZING"; case State::ACTIVATING: return "ACTIVATING"; case State::ACTIVE_FORGROUND: return "ACTIVE_FORGROUND"; case State::ACTIVE_BACKGROUND: return "ACTIVE_BACKGROUND"; case State::DEACTIVATING: return "DEACTIVATING"; default: return "FixIt"; } } Application::Application(std::string name, std::string parent,bool startBackground, uint32_t stackDepth,sys::ServicePriority priority) : Service(name, parent, stackDepth, priority), startBackground{startBackground} { keyTranslator = std::make_unique(); busChannels.push_back(sys::BusChannels::ServiceCellularNotifications); if (startBackground) { setState(State::ACTIVE_BACKGROUND); } longPressTimer = CreateAppTimer( key_timer_ms, true, [&]() { longPressTimerCallback(); }, "longPressTimer"); longPressTimer.restart(); busChannels.push_back(sys::BusChannels::ServiceCellularNotifications); } Application::~Application() { for (auto it = windows.begin(); it != windows.end(); it++) { delete it->second; } windows.clear(); } Application::State Application::getState() { return state; } void Application::setState(State st) { #ifdef DEBUG_APPLICATION_MANAGEMENT LOG_DEBUG("[%s] (%s) -> (%s)", GetName().c_str(), stateStr(state), stateStr(st)); #endif state = st; } void Application::TickHandler(uint32_t id) { auto appTimer = std::find(appTimers.begin(), appTimers.end(), id); if (appTimer != appTimers.end()) { appTimer->runCallback(); } else{ LOG_ERROR("Requested timer doesn't exist here (ID: %s)\n", std::to_string(id).c_str()); // either timer was deleted or this id should not arrive. } } void Application::DeleteTimer(AppTimer &timer) { Service::DeleteTimer(timer.getID()); // remove the real FreeRTOS timer auto timerOnTheList = std::find(appTimers.begin(), appTimers.end(), timer); if (timerOnTheList != appTimers.end()) { appTimers.erase(timerOnTheList); } } [[deprecated("only for compatibility")]] void Application::DeleteTimer(uint32_t id) { auto found = std::find(appTimers.begin(), appTimers.end(), id); if (found != appTimers.end()){ DeleteTimer(*found); } } void Application::longPressTimerCallback() { // TODO if(check widget type long press trigger) uint32_t time = xTaskGetTickCount(); if (keyTranslator->timeout(time)) { // previous key press was over standard keypress timeout - send long press gui::InputEvent iev = keyTranslator->translate(time); messageInputEventApplication(this, this->GetName(), iev); // clean previous key keyTranslator->prev_key_press = {}; } } void Application::render( gui::RefreshModes mode ) { if( currentWindow == nullptr ){ LOG_ERROR("Current window is not defined"); return; } //send drawing commands only when if application is in active and visible. if( state == State::ACTIVE_FORGROUND ) { std::list commandsList = currentWindow->buildDrawList(); if( shutdownInProgress ) { auto msg = std::make_shared(commandsList, mode, sgui::DrawMessage::DrawCommand::SHUTDOWN ); sys::Bus::SendUnicast(msg, "ServiceGUI", this); } else if( suspendInProgress ) { auto msg = std::make_shared(commandsList, mode, sgui::DrawMessage::DrawCommand::SUSPEND ); sys::Bus::SendUnicast(msg, "ServiceGUI", this); } else { auto msg = std::make_shared(commandsList, mode, sgui::DrawMessage::DrawCommand::NORMAL ); sys::Bus::SendUnicast(msg, "ServiceGUI", this); } } if( suspendInProgress ) suspendInProgress = false; } void Application::blockEvents(bool isBlocked ) { acceptInput = isBlocked; } int Application::switchWindow( const std::string& windowName, gui::ShowMode cmd, std::unique_ptr data ) { std::string window; #ifdef DEBUG_APPLICATION_MANAGEMENT LOG_INFO("switching [%s] to window: %s", GetName().c_str(), windowName.length() ? windowName.c_str() : "MainWindow"); #endif //case to handle returning to previous application if( windowName == "LastWindow" ) { window = currentWindow->getName(); auto ret = dynamic_cast(data.get()); auto msg = std::make_shared(window, currentWindow->getName(), ret ? std::move(data) : std::make_unique("LastWindow"), cmd); sys::Bus::SendUnicast(msg, this->GetName(), this ); } else { window = windowName.empty() ? "MainWindow" : windowName; auto msg = std::make_shared( window, currentWindow->getName(), std::move(data), cmd ); sys::Bus::SendUnicast(msg, this->GetName(), this ); } return 0; } // TODO: this one seems to be unused int Application::switchBackWindow( const std::string& windowName, uint32_t cmd, std::unique_ptr data ) { auto msg = std::make_shared( MessageType::AppSwitchWindowBack ); sys::Bus::SendUnicast(msg, this->GetName(), this ); return 0; } int Application::refreshWindow(gui::RefreshModes mode) { auto msg = std::make_shared( mode ); sys::Bus::SendUnicast(msg, this->GetName(), this ); return 0; } sys::Message_t Application::DataReceivedHandler(sys::DataMessage* msgl) { bool handled = false; if( msgl->messageType == static_cast(MessageType::CellularNotification) ) { CellularNotificationMessage *msg = reinterpret_cast(msgl); if( msg->type == CellularNotificationMessage::Type::SignalStrengthUpdate ) { if( ( state == State::ACTIVE_FORGROUND ) && (currentWindow->updateSignalStrength( msg->signalStrength) ) ) { //loop and update all widnows for ( auto it = windows.begin(); it != windows.end(); it++ ) { it->second->updateSignalStrength( msg->signalStrength); } handled = true; refreshWindow( gui::RefreshModes::GUI_REFRESH_FAST ); } } } if(msgl->messageType == static_cast(MessageType::AppInputEvent) ) { AppInputEventMessage* msg = reinterpret_cast( msgl ); if( currentWindow != nullptr ) currentWindow->onInput( msg->getEvent() ); // LOG_INFO( "Key event :%s", msg->getEvent().to_string().c_str()); handled = true; } else if(msgl->messageType == static_cast(MessageType::KBDKeyEvent) ) { sevm::KbdMessage* msg = static_cast(msgl); gui::InputEvent iev = keyTranslator->translate(msg->key); if(iev.keyCode != gui::KeyCode::KEY_UNDEFINED ) { messageInputEventApplication( this, this->GetName(), iev ); } handled = true; } else if(msgl->messageType == static_cast(MessageType::EVMBatteryLevel) ) { sevm::BatteryLevelMessage* msg = static_cast(msgl); LOG_INFO("Application battery level: %d", msg->levelPercents ); if( currentWindow ) { if( currentWindow->updateBatteryLevel( msg->levelPercents ) ) refreshWindow( gui::RefreshModes::GUI_REFRESH_FAST ); } handled = true; } else if(msgl->messageType == static_cast(MessageType::EVMChargerPlugged) ) { sevm::BatteryPlugMessage* msg = static_cast(msgl); if(msg->plugged == true) { //TODO show plug icon LOG_INFO("Application charger connected" ); } else { //hide plug icon LOG_INFO("Application charger disconnected" ); } refreshWindow( gui::RefreshModes::GUI_REFRESH_FAST ); handled = true; } else if(msgl->messageType == static_cast(MessageType::EVMMinuteUpdated)) { sevm::RtcMinuteAlarmMessage* msg = static_cast(msgl); LOG_INFO("Application time updated"); currentWindow->updateTime( msg->timestamp, !settings.timeFormat12 ); if( state == State::ACTIVE_FORGROUND ) refreshWindow( gui::RefreshModes::GUI_REFRESH_FAST ); handled = true; } else if(msgl->messageType == static_cast(MessageType::AppSwitch) ) { AppSwitchMessage* msg = reinterpret_cast( msgl ); //Application is starting or it is in the background. Upon switch command if name if correct it goes foreground if ((state == State::ACTIVATING) || (state == State::INITIALIZING) || (state == State::ACTIVE_BACKGROUND)) { if( msg->getTargetApplicationName() == this->GetName()) { if( sapm::ApplicationManager::messageConfirmSwitch(this) ) { setState(State::ACTIVE_FORGROUND); switchWindow( msg->getTargetWindowName(), std::move( msg->getData())); handled = true; } else { //TODO send to itself message to close LOG_ERROR("Failed to communicate "); } } else { LOG_ERROR("Received switch message outside of activation flow"); } } else if( state == State::ACTIVE_FORGROUND ) { if( msg->getTargetApplicationName() == this->GetName()) { //if window name and data are null pointers this is a message informing //that application should go to background mode if( (msg->getTargetWindowName() == "") && (msg->getData() == nullptr ) ) { if( sapm::ApplicationManager::messageConfirmSwitch(this) ) { setState(State::ACTIVE_BACKGROUND); handled = true; } else { //TODO send to itself message to close LOG_ERROR("Failed to communicate "); } } //if application is in front and receives message with defined window it should //change to that window. else { switchWindow( msg->getTargetWindowName(), std::move( msg->getData())); handled = true; } } else { LOG_ERROR("Received switch message outside of activation flow"); } } else { LOG_ERROR("Wrong internal application %s switch to ACTIVE state form %s", msg->getTargetApplicationName().c_str(), stateStr(state)); } } else if(msgl->messageType == static_cast(MessageType::AppSwitchWindow ) ) { AppSwitchWindowMessage* msg = reinterpret_cast( msgl ); //check if specified window is in the application auto it = windows.find( msg->getWindowName() ); if( it != windows.end() ) { setActiveWindow( msg->getWindowName() ); currentWindow->handleSwitchData( msg->getData().get() ); if( currentWindow->getPrevWindow().empty() ) { currentWindow->setPrevWindow( msg->getSenderWindowName() ); } //check if this is case where application is returning to the last visible window. if( (msg->getData() != nullptr) && (msg->getData()->getDescription() == "LastWindow") ) { } else { currentWindow->onBeforeShow( msg->getCommand(), msg->getData().get() ); auto ret = dynamic_cast(msg->getData().get()); if (ret != nullptr && msg->getData() != nullptr) { auto text = dynamic_cast(currentWindow->getFocusItem()); if (text) { if (text->handleChar(ret->getDescription()[0])) { text->updateCursor(); } } } } refreshWindow( gui::RefreshModes::GUI_REFRESH_DEEP ); } else { LOG_ERROR("No such window: %s", msg->getWindowName().c_str()); } handled = true; } else if( msgl->messageType == static_cast(MessageType::AppClose)) { setState(State::DEACTIVATING); sapm::ApplicationManager::messageConfirmClose(this); //here should go all the cleaning handled = true; } else if( msgl->messageType == static_cast(MessageType::AppRebuild )) { LOG_INFO("Application %s rebuilding gui", GetName().c_str() ); //for all windows call rebuild method for (auto it = windows.begin(); it != windows.end(); it++) { LOG_DEBUG("Rebuild: %s", it->first.c_str()); if (!it->second) { LOG_ERROR("NO SUCH WINDOW"); } else { it->second->rebuild(); } } //if application has focus call deep refresh LOG_INFO("Refresh app with focus!"); if (state == State::ACTIVE_FORGROUND) { refreshWindow(gui::RefreshModes::GUI_REFRESH_DEEP); } handled = true; LOG_INFO("App rebuild done"); } else if( msgl->messageType == static_cast(MessageType::AppRefresh)) { AppRefreshMessage* msg = reinterpret_cast( msgl ); //currentWindow->onBeforeShow( gui::ShowMode::GUI_SHOW_RETURN, 0, nullptr ); render( msg->getMode() ); handled = true; } if( handled) return std::make_shared(); else return std::make_shared(sys::ReturnCodes::Unresolved); } sys::ReturnCodes Application::InitHandler() { bool initState= true; setState(State::INITIALIZING); // uint32_t start = xTaskGetTickCount(); settings = DBServiceAPI::SettingsGet(this); // uint32_t stop = xTaskGetTickCount(); // LOG_INFO("DBServiceAPI::SettingsGet %d", stop-start); initState = (settings.dbID == 1); //send response to application manager true if successful, false otherwise. sapm::ApplicationManager::messageRegisterApplication( this, initState, startBackground ); sys::ReturnCodes retCode = (initState?sys::ReturnCodes::Success:sys::ReturnCodes::Failure); return retCode; } void Application::setActiveWindow( const std::string& windowName ) { auto it = windows.find(windowName); //if there is a window with specified name set it as active window //and unlock accepting keyboard events if( it!=windows.end() ) { previousWindow = currentWindow; currentWindow = it->second; acceptInput = true; } } bool Application::messageSwitchApplication( sys::Service* sender, std::string application, std::string window, std::unique_ptr data ) { auto msg = std::make_shared( application, window, std::move(data) ); sys::Bus::SendUnicast(msg, application, sender ); return true; } bool Application::messageRefreshApplication( sys::Service* sender, std::string application, std::string window, gui::SwitchData* data ) { auto msg = std::make_shared( MessageType::AppRefresh ); sys::Bus::SendUnicast(msg, application, sender ); return true; } bool Application::messageCloseApplication( sys::Service* sender, std::string application ) { auto msg = std::make_shared( MessageType::AppClose ); sys::Bus::SendUnicast(msg, application, sender ); return true; } bool Application::messageRebuildApplication( sys::Service* sender, std::string application ) { auto msg = std::make_shared(); sys::Bus::SendUnicast(msg, application, sender ); return true; } bool Application::messageInputEventApplication( sys::Service* sender, std::string application , const gui::InputEvent& event ) { auto msg = std::make_shared( event ); sys::Bus::SendUnicast(msg, application, sender ); return true; } AppTimer Application::CreateAppTimer(TickType_t interval, bool isPeriodic, std::function callback, const std::string &name) { auto id = CreateTimer(interval, isPeriodic, name); auto timer = AppTimer(this, id, callback, name); appTimers.push_back(timer); return timer; // return ptr to the timer on the list } AppTimer::AppTimer(Application * parent, uint32_t id, std::function callback, const std::string &name) : parent(parent) { this->id = id; registerCallback(callback); } AppTimer::AppTimer() = default; AppTimer::~AppTimer() { callback = nullptr; } void AppTimer::registerCallback(std::function callback) { this->callback = callback; } void AppTimer::runCallback() { callback(); } uint32_t AppTimer::getID() { return id; } void AppTimer::stop() { if (parent) { parent->stopTimer(getID()); } } void AppTimer::restart() { if (parent) { parent->ReloadTimer(getID()); } } bool AppTimer::operator==(const AppTimer &rhs) const { return this->id == rhs.id; } bool AppTimer::operator==(const uint32_t &rhs) const { return this->id == rhs; } } /* namespace app */