// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "ApplicationMessages.hpp" #include "MessagesMainWindow.hpp" #include "NewMessage.hpp" #include "OptionsMessages.hpp" #include "SearchResults.hpp" #include "SearchStart.hpp" #include "SMSTemplatesWindow.hpp" #include "SMSTextToSearch.hpp" #include "SMSThreadViewWindow.hpp" #include "ThreadWindowOptions.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace app { static constexpr auto messagesStackDepth = 1024 * 6; // 6Kb stack size ApplicationMessages::ApplicationMessages(std::string name, std::string parent, StatusIndicators statusIndicators, StartInBackground startInBackground) : Application(std::move(name), std::move(parent), statusIndicators, startInBackground, messagesStackDepth), AsyncCallbackReceiver{this} { bus.channels.push_back(sys::BusChannel::ServiceDBNotifications); bus.channels.push_back(sys::BusChannel::PhoneLockChanges); addActionReceiver(manager::actions::CreateSms, [this](auto &&data) { return handleCreateSmsAction(std::move(data)); }); addActionReceiver(manager::actions::ShowSmsTemplates, [this](auto &&data) { switchWindow(gui::name::window::call_sms_templates, std::move(data)); return actionHandled(); }); addActionReceiver(manager::actions::SmsRejectNoSim, [this](auto &&data) { auto action = [this]() { returnToPreviousWindow(); return true; }; const auto notification = utils::translate("app_messages_no_sim"); showNotification(action, notification); return actionHandled(); }); addActionReceiver(manager::actions::SMSRejectedByOfflineNotification, [this](auto &&data) { auto action = [=]() -> bool { returnToPreviousWindow(); return true; }; const auto notification = utils::translate("app_sms_offline"); showNotification(action, notification); return actionHandled(); }); connect(typeid(cellular::CallMissedNotification), [&](sys::Message *request) { if (getCurrentWindow()->getName() == gui::name::window::call_sms_templates) { app::manager::Controller::switchBack(this); } return sys::MessageNone{}; }); connect(typeid(locks::UnlockedPhone), [&](sys::Message *msg) -> sys::MessagePointer { phoneLockState = PhoneLockState::Unlocked; return sys::MessageNone{}; }); connect(typeid(locks::LockedPhone), [&](sys::Message *msg) -> sys::MessagePointer { phoneLockState = PhoneLockState::Locked; return sys::MessageNone{}; }); } // Invoked upon receiving data message sys::MessagePointer ApplicationMessages::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) { auto retMsg = Application::DataReceivedHandler(msgl); // if message was handled by application's template there is no need to process further. if (reinterpret_cast(retMsg.get())->retCode == sys::ReturnCodes::Success) { return retMsg; } if (msgl->messageType == MessageType::DBServiceNotification) { auto msg = dynamic_cast(msgl); if (msg != nullptr) { userInterfaceDBNotification(msgl, [&]([[maybe_unused]] sys::Message *, [[maybe_unused]] const std::string &) { if (phoneLockState == PhoneLockState::Locked) { return false; } return msg->interface == db::Interface::Name::SMSThread || msg->interface == db::Interface::Name::SMS || msg->interface == db::Interface::Name::SMSTemplate; }); return std::make_shared(); } } return handleAsyncResponse(resp); } // Invoked during initialization sys::ReturnCodes ApplicationMessages::InitHandler() { auto ret = Application::InitHandler(); if (ret != sys::ReturnCodes::Success) { return ret; } createUserInterface(); return ret; } void ApplicationMessages::createUserInterface() { windowsFactory.attach(gui::name::window::main_window, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app); }); windowsFactory.attach(gui::name::window::thread_view, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app); }); windowsFactory.attach(gui::name::window::new_sms, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app); }); windowsFactory.attach(window::name::option_window, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app, name); }); windowsFactory.attach(gui::name::window::thread_options, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app, name); }); windowsFactory.attach(gui::name::window::sms_options, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app, name); }); windowsFactory.attach(gui::name::window::dialog, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app, name); }); windowsFactory.attach(gui::name::window::dialog_confirm, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app, name); }); windowsFactory.attach(gui::name::window::dialog_yes_no, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app, name); }); windowsFactory.attach( gui::name::window::thread_sms_search, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app); }); windowsFactory.attach(gui::name::window::sms_templates, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app, name); }); windowsFactory.attach(gui::name::window::call_sms_templates, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app, name); }); windowsFactory.attach(gui::name::window::search_results, [](ApplicationCommon *app, const std::string &name) { return std::make_unique(app); }); attachPopups({gui::popup::ID::Volume, gui::popup::ID::Tethering, gui::popup::ID::BluetoothAuthenticate, gui::popup::ID::PhoneModes, gui::popup::ID::PhoneLock, gui::popup::ID::SimLock, gui::popup::ID::Alarm}); } void ApplicationMessages::destroyUserInterface() {} bool ApplicationMessages::markSmsThreadAsRead(const ThreadRecord *record) { using namespace db::query; if (record == nullptr) { LOG_ERROR("Trying to mark as read a null SMS thread!"); return false; } DBServiceAPI::GetQuery( this, db::Interface::Name::SMSThread, std::make_unique(record->ID, MarkAsRead::Read::True)); if (record->unreadMsgCount) { DBServiceAPI::GetQuery( this, db::Interface::Name::Notifications, std::make_unique(NotificationsRecord::Key::Sms, record->unreadMsgCount)); } return true; } bool ApplicationMessages::markSmsThreadAsUnread(const uint32_t id) { using namespace db::query; DBServiceAPI::GetQuery( this, db::Interface::Name::SMSThread, std::make_unique(id, MarkAsRead::Read::False)); return true; } bool ApplicationMessages::removeSmsThread(const ThreadRecord *record) { using db::query::ContactGetByNumberID; using db::query::ContactGetByNumberIDResult; if (record == nullptr) { LOG_ERROR("Trying to remove a null SMS thread!"); return false; } LOG_DEBUG("Removing thread: %" PRIu32, record->ID); auto query = std::make_unique(record->numberID); auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Contact); task->setCallback([this, record](auto response) { auto result = dynamic_cast(response); if (result != nullptr) { const auto &contact = result->getResult(); auto metaData = std::make_unique( gui::DialogMetadata{contact.getFormattedName(), "delete_128px_W_G", utils::translate("app_messages_thread_delete_confirmation"), "", [this, record]() { return onRemoveSmsThreadConfirmed(*record); }}); switchWindow(gui::name::window::dialog_yes_no, gui::ShowMode::GUI_SHOW_INIT, std::move(metaData)); return true; } return false; }); task->execute(this, this); return true; } bool ApplicationMessages::onRemoveSmsThreadConfirmed(const ThreadRecord &record) { using db::query::ThreadRemove; using db::query::ThreadRemoveResult; auto query = std::make_unique(record.ID); auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread); task->setCallback([this, threadId = record.ID, unreadMsgCount = record.unreadMsgCount](auto response) { const auto result = dynamic_cast(response); if ((result != nullptr) && result->success()) { if (unreadMsgCount) { DBServiceAPI::GetQuery(this, db::Interface::Name::Notifications, std::make_unique( NotificationsRecord::Key::Sms, unreadMsgCount)); } switchWindow(gui::name::window::main_window); return true; } LOG_ERROR("ThreadRemove id=%" PRIu32 " failed", threadId); return false; }); task->execute(this, this); return true; } bool ApplicationMessages::removeSms(const SMSRecord &record) { LOG_DEBUG("Removing sms: %" PRIu32, record.ID); auto metaData = std::make_unique( gui::DialogMetadata{record.body, "delete_128px_W_G", utils::translate("app_messages_message_delete_confirmation"), "", [this, record] { return onRemoveSmsConfirmed(record); }}); switchWindow(gui::name::window::dialog_yes_no, gui::ShowMode::GUI_SHOW_INIT, std::move(metaData)); return true; } bool ApplicationMessages::onRemoveSmsConfirmed(const SMSRecord &record) { using db::query::SMSRemove; using db::query::SMSRemoveResult; using db::query::ThreadGetByID; using db::query::ThreadGetByIDResult; auto query = std::make_unique(record.ID); auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMS); task->setCallback([this, record](auto response) { auto result = dynamic_cast(response); if (result != nullptr && result->getResults()) { auto query = std::make_unique(record.threadID); auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread); task->setCallback([this](auto response) { const auto result = dynamic_cast(response); if (result != nullptr) { const auto thread = result->getRecord(); if (thread.has_value()) { this->switchWindow(gui::name::window::thread_view); } else { this->switchWindow(gui::name::window::main_window); } return true; } return false; }); task->execute(this, this); return true; } LOG_ERROR("sSMSRemove id=%" PRIu32 " failed", record.ID); return false; }); task->execute(this, this); return true; } bool ApplicationMessages::searchEmpty(const std::string &query) { gui::DialogMetadata meta; meta.icon = "search_128px_W_G"; meta.text = utils::translate("app_messages_thread_no_result"); meta.title = utils::translate("common_results_prefix") + query; auto data = std::make_unique(meta); data->ignoreCurrentWindowOnStack = true; switchWindow(gui::name::window::dialog, std::make_unique(meta)); return true; } bool ApplicationMessages::showSearchResults(const UTF8 &title, const UTF8 &search_text) { switchWindow(gui::name::window::search_results, std::make_unique(search_text, title)); return true; } bool ApplicationMessages::showNotification(std::function action, const std::string ¬ification, bool ignoreCurrentWindowOnStack) { gui::DialogMetadata meta; meta.icon = "info_128px_W_G"; meta.text = notification; meta.action = std::move(action); auto switchData = std::make_unique(meta); switchData->ignoreCurrentWindowOnStack = ignoreCurrentWindowOnStack; switchWindow(gui::name::window::dialog_confirm, std::move(switchData)); return true; } bool ApplicationMessages::updateDraft(SMSRecord &record, const UTF8 &body) { assert(!body.empty()); // precondition check. record.body = body; record.date = std::time(nullptr); using db::query::SMSUpdate; const auto [succeed, _] = DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique(record)); return succeed; } void ApplicationMessages::createDraft(const utils::PhoneNumber::View &number, const UTF8 &body, std::function onSuccess) { assert(!body.empty()); // precondition check. SMSRecord record; record.number = number; record.body = body; record.type = SMSType::DRAFT; record.date = std::time(nullptr); using db::query::SMSAdd; auto query = std::make_unique(record); auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMS); task->setCallback([onSuccess](auto response) { using db::query::SMSAddResult; if (auto result = static_cast(response); result->succeed()) { if (onSuccess) { onSuccess(result->record); } return true; } LOG_ERROR("Failed to add an SMS record to the DB."); return false; }); task->execute(this, this); } bool ApplicationMessages::removeDraft(const SMSRecord &record) { using db::query::SMSRemove; if (!record.isValid()) { LOG_ERROR("Draft SMS is invalid."); return false; } const auto [succeed, _] = DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique(record.ID)); return succeed; } bool ApplicationMessages::sendSms(const utils::PhoneNumber::View &number, const UTF8 &body) { if (number.getEntered().empty() || body.length() == 0) { LOG_WARN("Number or sms body is empty"); return false; } SMSRecord record; record.number = number; record.body = body; record.type = SMSType::QUEUED; record.date = std::time(nullptr); using db::query::SMSAdd; const auto [succeed, _] = DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique(record)); return succeed; } bool ApplicationMessages::resendSms(const SMSRecord &record) { auto resendRecord = record; resendRecord.type = SMSType::QUEUED; resendRecord.date = std::time(nullptr); // update date sent - it will display an old, failed sms at // the the bottom, but this is correct using db::query::SMSUpdate; const auto [succeed, _] = DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique(resendRecord)); return succeed; } bool ApplicationMessages::handleSendSmsFromThread(const utils::PhoneNumber::View &number, const UTF8 &body) { return sendSms(number, body); } bool ApplicationMessages::newMessageOptions(const std::string &requestingWindow, gui::Text *text) { LOG_INFO("New message options for %s", requestingWindow.c_str()); auto opts = std::make_unique(newMessageWindowOptions(this, requestingWindow, text)); switchWindow(window::name::option_window, std::move(opts)); return true; } ActionResult ApplicationMessages::handleCreateSmsAction(std::unique_ptr data) { if (auto sendRequest = dynamic_cast(data.get()); sendRequest != nullptr) { const auto phoneNumber = sendRequest->getPhoneNumber(); auto query = std::make_unique(phoneNumber); auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread); auto queryCallback = [this, capturedData = std::move(data)](auto response) mutable { const auto result = dynamic_cast(response); if (result == nullptr) { switchWindow(gui::name::window::new_sms, std::move(capturedData)); return false; } const auto &thread = result->getThread(); if (!thread.isValid()) { switchWindow(gui::name::window::new_sms, std::move(capturedData)); } else { auto switchData = std::make_unique(std::make_unique(thread)); switchData->ignoreCurrentWindowOnStack = true; switchData->nameOfSenderApplication = capturedData->nameOfSenderApplication; switchWindow(gui::name::window::thread_view, std::move(switchData)); } return true; }; task->setCallback([callback = std::make_shared(std::move(queryCallback))]( auto response) { return (*callback)(response); }); task->execute(this, this); return actionHandled(); } return actionNotHandled(); } } /* namespace app */