// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "ServiceCellularPriv.hpp" #include "messages.hpp" #include "SMSPartsHandler.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkSmsCenter.hpp" using service::name::service_time; namespace cellular::internal { ServiceCellularPriv::ServiceCellularPriv(ServiceCellular *owner) : owner{owner}, simCard{std::make_unique()}, state{std::make_unique(owner)}, networkTime{std::make_unique()}, simContacts{std::make_unique()}, imeiGetHandler{std::make_unique()}, tetheringHandler{std::make_unique()}, volteHandler{std::make_unique>()}, modemResetHandler{std::make_unique()}, csqHandler{ std::make_unique(), }, volteCapability{ std::make_unique(std::make_unique(), std::make_unique())}, ussdHandler{std::make_unique()} { initSimCard(); initSMSSendHandler(); initTetheringHandler(); initModemResetHandler(); initUSSDHandler(); } void ServiceCellularPriv::initSimCard() { using namespace cellular::msg; simCard->onSimReady = [this]() { state->set(State::ST::Ready); auto channel = owner->cmux->get(CellularMux::Channel::Commands); auto permitVolte = volteCapability->isVolteAllowed(*channel); auto enableVolte = owner->settings->getValue(settings::Cellular::volteEnabled, settings::SettingsScope::Global) == "1" ? true : false; bool volteNeedReset = false; try { volteNeedReset = !volteHandler->switchVolte(*channel, permitVolte, enableVolte); auto notification = std::make_shared(volteHandler->getVolteState()); owner->bus.sendMulticast(std::move(notification), sys::BusChannel::ServiceCellularNotifications); } catch (std::runtime_error const &exc) { LOG_ERROR("%s", exc.what()); } if (volteNeedReset) { modemResetHandler->performHardReset(); return; } owner->bus.sendMulticast(); }; simCard->onNeedPin = [this](unsigned int attempts) { owner->bus.sendMulticast(attempts); }; simCard->onNeedPuk = [this](unsigned int attempts) { owner->bus.sendMulticast(attempts); }; simCard->onSimBlocked = [this]() { owner->bus.sendMulticast(); }; simCard->onSimEvent = [this]() { owner->bus.sendMulticast(); }; simCard->onUnhandledCME = [this](unsigned int code) { owner->bus.sendMulticast(code); }; simCard->onSimNotPresent = [this]() { owner->bus.sendMulticast(); }; simCard->onSimSelected = [this]() { owner->connectionManager->onPhoneModeChange(owner->phoneModeObserver->getCurrentPhoneMode()); requestNetworkTimeSettings(); }; } void ServiceCellularPriv::connectSimCard() { using namespace cellular::msg; /** * Request message handlers */ owner->connect(typeid(request::sim::SetActiveSim), [&](sys::Message *request) -> sys::MessagePointer { auto msg = static_cast(request); auto result = simCard->handleSetActiveSim(msg->sim); owner->simTimer.start(); simCard->handleSimCardSelected(); return std::make_shared(result); }); owner->connect(typeid(request::sim::GetPinSettings), [&](sys::Message *) -> sys::MessagePointer { if (!simCard->isSimCardInserted()) { owner->bus.sendMulticast(); return sys::MessageNone{}; } auto simLockState = simCard->handleIsPinNeeded(); if (!simLockState.has_value()) { return sys::MessageNone{}; } return std::make_shared(simLockState.value()); }); owner->connect(typeid(request::sim::ChangePin), [&](sys::Message *request) -> sys::MessagePointer { auto msg = static_cast(request); if (!simCard->isSimCardInserted()) { owner->bus.sendMulticast(); return sys::MessageNone{}; } return std::make_shared(simCard->handleChangePin(msg->oldPin, msg->pin)); }); owner->connect(typeid(request::sim::UnblockWithPuk), [&](sys::Message *request) -> sys::MessagePointer { auto msg = static_cast(request); if (!simCard->isSimCardInserted()) { owner->bus.sendMulticast(); return sys::MessageNone{}; } return std::make_shared( simCard->handleUnblockWithPuk(msg->puk, msg->pin)); }); owner->connect(typeid(request::sim::SetPinLock), [&](sys::Message *request) -> sys::MessagePointer { auto msg = static_cast(request); if (!simCard->isSimCardInserted()) { owner->bus.sendMulticast(); return sys::MessageNone{}; } return std::make_shared( simCard->handleSetPinLock(msg->pin, msg->pinLock), msg->pinLock); }); owner->connect(typeid(request::sim::PinUnlock), [&](sys::Message *request) -> sys::MessagePointer { auto msg = static_cast(request); if (!simCard->isSimCardInserted()) { owner->bus.sendMulticast(); return sys::MessageNone{}; } return std::make_shared(simCard->handlePinUnlock(msg->pin)); }); /** * Notification message handlers */ owner->connect(typeid(sevm::SIMTrayMessage), [&](sys::Message *request) -> sys::MessagePointer { simCard->handleTrayState(); return sys::MessageNone{}; }); owner->connect(typeid(cellular::SimInsertedNotication), [&](sys::Message *request) -> sys::MessagePointer { auto message = static_cast(request); if (simCard->isSimSelectInProgress()) { return sys::MessageNone{}; } simCard->handleSimInsertionNotification(message->getInsertedStatus()); return sys::MessageNone{}; }); /** * Internal message handlers */ owner->connect(typeid(internal::msg::HandleATSimStateChange), [&](sys::Message *request) -> sys::MessagePointer { auto msg = static_cast(request); simCard->handleATSimStateChange(msg->state); return sys::MessageNone{}; }); } void ServiceCellularPriv::connectNetworkTime() { owner->connect(typeid(stm::message::AutomaticDateAndTimeChangedMessage), [&](sys::Message *request) -> sys::MessagePointer { auto message = static_cast(request); networkTime->processSettings( message->getValue(), owner->phoneModeObserver->isInMode(sys::phone_modes::PhoneMode::Offline)); return sys::MessageNone{}; }); owner->connect( typeid(stm::message::GetAutomaticDateAndTimeResponse), [&](sys::Message *request) -> sys::MessagePointer { auto message = static_cast(request); networkTime->processSettings(message->isAutomaticDateAndTime(), owner->phoneModeObserver->isInMode(sys::phone_modes::PhoneMode::Offline)); return sys::MessageNone{}; }); } void ServiceCellularPriv::requestNetworkTimeSettings() { owner->bus.sendUnicast(networkTime->createSettingsRequest(), service_time); } void ServiceCellularPriv::initSMSSendHandler() { outSMSHandler.onSendQuery = [this](db::Interface::Name database, std::unique_ptr query) -> bool { return DBServiceAPI::GetQuery(owner, database, std::move(query)).first; }; outSMSHandler.onSend = [this](SMSRecord &record) -> bool { return this->onSendSMS(record); }; outSMSHandler.onSIMNotInitialized = [this]() -> bool { bool ret = false; if (!simCard->initialized()) { owner->bus.sendUnicast(std::make_shared(), ::service::name::appmgr); ret = true; } return ret; }; outSMSHandler.onGetOfflineMode = [this]() -> bool { bool ret = false; if (owner->phoneModeObserver->isInMode(sys::phone_modes::PhoneMode::Offline)) { owner->bus.sendUnicast(std::make_shared(), ::service::name::appmgr); ret = true; } return ret; }; outSMSHandler.onGetModemResetInProgress = [this]() -> bool { return modemResetHandler->isResetInProgress(); }; } bool ServiceCellularPriv::onSendSMS(SMSRecord &record) { auto commandTimeout = at::factory(at::AT::CMGS).getTimeout(); bool result = false; auto channel = owner->cmux->get(CellularMux::Channel::Commands); auto receiver = record.number.getEntered(); bool channelSetup = false; if (channel) { channelSetup = true; if (!channel->cmd(at::AT::SET_SMS_TEXT_MODE_UCS2)) { LOG_ERROR("Could not set UCS2 mode for SMS"); channelSetup = false; } if (!channel->cmd(at::AT::SMS_UCSC2)) { LOG_ERROR("Could not set UCS2 charset mode for TE"); channelSetup = false; } } else { LOG_ERROR("No channel provided! SMS sending failed"); record.type = SMSType::FAILED; return false; } if (channelSetup) { auto partHandler = sms::SMSPartsHandler(record.body); if (partHandler.isSinglePartSMS()) { std::string command = std::string(at::factory(at::AT::CMGS)); std::string body = UCS2(UTF8(receiver)).str(); std::string suffix = "\""; std::string command_data = command + body + suffix; if (owner->cmux->checkATCommandPrompt( channel->sendCommandPrompt(command_data.c_str(), 1, commandTimeout))) { if (channel->cmd((partHandler.getNextSmsPart() + "\032").c_str(), commandTimeout)) { result = true; } else { result = false; } if (!result) { LOG_ERROR("Message send failure"); } } } else { if (partHandler.isPartsCountAboveLimit()) { LOG_ERROR("Message too long."); result = false; } else { saveNewMultiPartSMSUIDCallback(multiPartSMSUID); for (unsigned i = 0; i < partHandler.getPartsCount(); i++) { std::string command(at::factory(at::AT::QCMGS) + UCS2(UTF8(receiver)).str() + "\"," + std::to_string(multiPartSMSUID) + "," + std::to_string(i + 1) + "," + std::to_string(partHandler.getPartsCount())); if (owner->cmux->checkATCommandPrompt( channel->sendCommandPrompt(command.c_str(), 1, commandTimeout))) { // prompt sign received, send data ended by "Ctrl+Z" if (channel->cmd(partHandler.getNextSmsPart() + "\032", commandTimeout, 2)) { result = true; } else { result = false; LOG_ERROR("Message send failure"); break; } } else { result = false; LOG_ERROR("Message send failure"); break; } } multiPartSMSUID++; } } } if (result) { LOG_INFO("SMS sent ok."); record.type = SMSType::OUTBOX; } else { LOG_ERROR("SMS sending failed."); record.type = SMSType::FAILED; if (!checkSmsCenter(*channel)) { LOG_ERROR("SMS center check failed"); } // Perform soft reset because sometimes the modem is not able to send next messages modemResetHandler->performSoftReset(); } DBServiceAPI::GetQuery( owner, db::Interface::Name::SMS, std::make_unique(std::move(record))); if (channel && !channel->cmd(at::AT::SMS_GSM)) { LOG_ERROR("Could not set GSM (default) charset mode for TE"); return false; } return result; } void ServiceCellularPriv::connectSimContacts() { owner->connect(typeid(cellular::GetSimContactsRequest), [&](sys::Message *request) -> sys::MessagePointer { std::vector contacts; if (simContacts->getContacts(contacts)) { return std::make_shared( std::make_shared>(contacts)); } return std::make_shared(); }); } void ServiceCellularPriv::setInitialMultiPartSMSUID(std::uint8_t uid) { multiPartSMSUID = uid + 1; } void ServiceCellularPriv::connectImeiGetHandler() { owner->connect(typeid(cellular::GetImeiRequest), [&](sys::Message *request) -> sys::MessagePointer { std::string imei; if (imeiGetHandler->getImei(imei)) { return std::make_shared(std::make_shared(imei)); } return std::make_shared(); }); } void ServiceCellularPriv::initTetheringHandler() { tetheringHandler->onIsRndisEnabled = [this]() -> std::optional { auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel == nullptr) { return std::nullopt; } auto command = at::cmd::QCFGUsbnet(at::cmd::Modifier::None); auto response = channel->cmd(command); auto result = command.parseQCFGUsbnet(response); if (result) { return result.net; } return std::nullopt; }; tetheringHandler->onEnableRndis = [this]() -> bool { auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel == nullptr) { return false; } auto response = channel->cmd(at::AT::SET_RNDIS); if (response) { return true; } return false; }; tetheringHandler->onEnableUrc = [this]() -> bool { return owner->tetheringTurnOnURC(); }; tetheringHandler->onDisableUrc = [this]() -> bool { return owner->tetheringTurnOffURC(); }; tetheringHandler->onSetPassthrough = [](bool enable) { using bsp::cellular::USB::setPassthrough; using bsp::cellular::USB::PassthroughState; setPassthrough(enable ? PassthroughState::ENABLED : PassthroughState::DISABLED); }; tetheringHandler->onReadMessages = [this]() -> bool { return owner->receiveAllMessages(); }; } void ServiceCellularPriv::initModemResetHandler() { modemResetHandler->onCellularStateSet = [this](cellular::service::State::ST newState) { state->set(newState); }; modemResetHandler->onTurnModemOn = [this]() { owner->cmux->turnOnModem(); }; modemResetHandler->onTurnModemOff = [this]() { owner->cmux->turnOffModem(); }; modemResetHandler->onHardReset = [this]() { owner->cmux->resetModem(); }; modemResetHandler->onSoftReset = [this]() -> bool { auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel == nullptr) { return false; } auto response = channel->cmd(at::AT::CFUN_RESET); if (response) { return true; } return false; }; modemResetHandler->onFunctionalityReset = [this]() -> bool { auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel == nullptr) { return false; } auto succeed = false; auto response = channel->cmd(at::AT::CFUN_DISABLE_TRANSMITTING); if (response) { succeed = true; } constexpr auto delay = 200; vTaskDelay(pdMS_TO_TICKS(delay)); response = channel->cmd(at::AT::CFUN_FULL_FUNCTIONALITY); if (response) { succeed |= true; } return succeed; }; modemResetHandler->onAnyReset = [this]() { owner->callStateTimer.stop(); }; } void ServiceCellularPriv::initCSQHandler() { csqHandler->onEnableCsqReporting = [this]() { if (owner->cpuSentinel) { owner->cpuSentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_2); } auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel) { auto result = channel->cmd(at::AT::CSQ_URC_ON); if (result) { owner->csqCounter.clearCounter(); return true; } } return false; }; csqHandler->onDisableCsqReporting = [this]() { if (owner->cpuSentinel) { owner->cpuSentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_2); } auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel) { auto result = channel->cmd(at::AT::CSQ_URC_OFF); if (result) { return true; } } return false; }; csqHandler->onGetCsq = [this]() -> std::optional { if (owner->cpuSentinel) { owner->cpuSentinel->HoldMinimumFrequency(bsp::CpuFrequencyMHz::Level_2); } auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (!channel) { return std::nullopt; } auto command = at::cmd::CSQ(); auto response = channel->cmd(command); if (response.code == at::Result::Code::OK) { auto result = command.parseCSQ(response); if (result) { return result; } } return std::nullopt; }; csqHandler->onPropagateCSQ = [this](uint32_t csq) { SignalStrength signalStrength(static_cast(csq)); Store::GSM::get()->setSignalStrength(signalStrength.data); auto message = std::make_shared(""); owner->bus.sendMulticast(message, sys::BusChannel::ServiceCellularNotifications); }; csqHandler->onInvalidCSQ = [this]() { AntennaServiceAPI::InvalidCSQNotification(owner); }; csqHandler->onRetrySwitchMode = [this](service::CSQMode newMode) { if (state->get() != State::ST::URCReady) { return; } switch (newMode) { case service::CSQMode::PermanentReporting: owner->bus.sendUnicast( std::make_shared(RetrySwitchCSQMode::Mode::PermanentReporting), ::service::name::cellular); break; case service::CSQMode::HybridReporting: owner->bus.sendUnicast(std::make_shared(RetrySwitchCSQMode::Mode::HybridReporting), ::service::name::cellular); break; case service::CSQMode::HybridPolling: owner->bus.sendUnicast(std::make_shared(RetrySwitchCSQMode::Mode::HybridPolling), ::service::name::cellular); break; } }; csqHandler->onRetryGetCSQ = [this]() { owner->bus.sendUnicast(std::make_shared(), ::service::name::cellular); }; owner->csqTimer.start(); } void ServiceCellularPriv::initUSSDHandler() { ussdHandler->onOpenPushSession = [this]() -> bool { auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel == nullptr) { LOG_ERROR("No cmux channel provided!"); return false; } auto result = channel->cmd(at::AT::CUSD_OPEN_SESSION); return result.code == at::Result::Code::OK; }; ussdHandler->onAbortSession = [this]() -> bool { auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel == nullptr) { LOG_ERROR("No cmux channel provided!"); return false; } auto result = channel->cmd(at::AT::CUSD_CLOSE_SESSION); return result.code == at::Result::Code::OK; }; ussdHandler->onSendUssdCode = [this](const std::string &request) -> bool { const std::string commandDcs(",15"); auto channel = owner->cmux->get(CellularMux::Channel::Commands); if (channel == nullptr) { LOG_ERROR("No cmux channel provided!"); return false; } channel->cmd(at::AT::SMS_GSM); std::string command = at::factory(at::AT::CUSD_SEND) + request + commandDcs; const auto result = channel->cmd(command, std::chrono::seconds(120)); if (result.code == at::Result::Code::OK) { owner->bus.sendUnicast(std::make_shared(), ::service::name::appmgr); return true; } return false; }; ussdHandler->onRequestAbortSession = [this]() { auto message = std::make_shared(cellular::USSDMessage::RequestType::abortSession); owner->bus.sendUnicast(std::move(message), ::service::name::cellular); }; ussdHandler->onRequestOpenPushSession = [this]() { auto message = std::make_shared(cellular::USSDMessage::RequestType::pushSessionRequest); owner->bus.sendUnicast(std::move(message), ::service::name::cellular); }; } void ServiceCellularPriv::connectCSQHandler() { owner->bus.channels.push_back(sys::BusChannel::PhoneLockChanges); owner->bus.channels.push_back(sys::BusChannel::BluetoothModeChanges); owner->connect(typeid(URCCounterMessage), [&](sys::Message *request) -> sys::MessagePointer { auto message = dynamic_cast(request); csqHandler->handleURCCounterMessage(message->getCounter()); return sys::MessageNone{}; }); owner->connect(typeid(RetrySwitchCSQMode), [&](sys::Message *request) -> sys::MessagePointer { auto message = dynamic_cast(request); switch (message->getNewMode()) { case cellular::RetrySwitchCSQMode::Mode::PermanentReporting: csqHandler->switchToPermanentReportMode(); break; case cellular::RetrySwitchCSQMode::Mode::HybridReporting: csqHandler->switchToHybridReportMode(); break; case cellular::RetrySwitchCSQMode::Mode::HybridPolling: csqHandler->switchToHybridPollMode(); break; } return sys::MessageNone{}; }); owner->connect(typeid(RetryGetCSQ), [&](sys::Message *request) -> sys::MessagePointer { csqHandler->getCSQ(); return sys::MessageNone{}; }); owner->connect(typeid(locks::UnlockedPhone), [&](sys::Message *request) -> sys::MessagePointer { csqHandler->handleUnlockPhone(); csqHandler->checkConditionToChangeMode(); return sys::MessageNone{}; }); owner->connect(typeid(locks::LockedPhone), [&](sys::Message *request) -> sys::MessagePointer { csqHandler->handleLockPhone(); csqHandler->checkConditionToChangeMode(); return sys::MessageNone{}; }); owner->connect(typeid(sys::bluetooth::BluetoothModeChanged), [&](sys::Message *request) -> sys::MessagePointer { auto data = static_cast(request); auto btMode = data->getBluetoothMode(); if (btMode == sys::bluetooth::BluetoothMode::ConnectedVoice || btMode == sys::bluetooth::BluetoothMode::ConnectedBoth) { csqHandler->handleBluetoothCarKitConnect(); } else { csqHandler->handleBluetoothCarKitDisconnect(); } csqHandler->checkConditionToChangeMode(); return sys::MessageNone{}; }); owner->connect(typeid(sevm::BatteryStatusChangeMessage), [&](sys::Message *request) -> sys::MessagePointer { csqHandler->checkConditionToChangeMode(); return sys::MessageNone{}; }); } void ServiceCellularPriv::privInit(at::BaseChannel *channel) { simCard->setChannel(channel); networkTime->setChannel(channel); simContacts->setChannel(channel); imeiGetHandler->setChannel(channel); } void ServiceCellularPriv::privDeinit() { simCard->setChannel(nullptr); networkTime->setChannel(nullptr); simContacts->setChannel(nullptr); imeiGetHandler->setChannel(nullptr); } } // namespace cellular::internal