File indexing completed on 2024-05-05 05:36:39

0001 // SPDX-FileCopyrightText: 2021-2022 Devin Lin <espidev@gmail.com>
0002 // SPDX-License-Identifier: GPL-2.0-or-later
0003 
0004 #include "modem.h"
0005 
0006 #include <utility>
0007 
0008 #include <KLocalizedString>
0009 #include <KUser>
0010 #include <QDBusReply>
0011 
0012 #include <QCoroDBusPendingReply>
0013 
0014 Modem::Modem(QObject *parent)
0015     : QObject{parent}
0016 {
0017 }
0018 
0019 Modem::Modem(QObject *parent, ModemManager::ModemDevice::Ptr mmModem, ModemManager::Modem::Ptr mmInterface)
0020     : QObject{parent}
0021     , m_mmModem{mmModem}
0022     , m_nmModem{nullptr}
0023     , m_mmInterface{mmInterface}
0024 {
0025     // TODO multi-sim support
0026     m_sims = {new Sim{this, this, m_mmModem->sim(), m_mmInterface, m_mm3gppDevice}};
0027 
0028     connect(m_mmModem.data(), &ModemManager::ModemDevice::simAdded, this, &Modem::simsChanged);
0029     connect(m_mmModem.data(), &ModemManager::ModemDevice::simAdded, this, &Modem::hasSimChanged);
0030     connect(m_mmModem.data(), &ModemManager::ModemDevice::simRemoved, this, &Modem::simsChanged);
0031     connect(m_mmModem.data(), &ModemManager::ModemDevice::simRemoved, this, &Modem::hasSimChanged);
0032 
0033     if (m_mmModem->sim()) {
0034         connect(m_mmModem->sim().get(), &ModemManager::Sim::simIdentifierChanged, this, &Modem::hasSimChanged);
0035     }
0036 
0037     connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionAdded, this, &Modem::mobileDataEnabledChanged);
0038     connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionRemoved, this, &Modem::mobileDataEnabledChanged);
0039     connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionAdded, this, &Modem::mobileDataEnabledChanged);
0040     connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionRemoved, this, &Modem::mobileDataEnabledChanged);
0041     connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceAdded, this, &Modem::findNetworkManagerDevice);
0042     connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceRemoved, this, &Modem::findNetworkManagerDevice);
0043 
0044     // this is guaranteed to be a GSM modem
0045     m_mm3gppDevice = m_mmModem->interface(ModemManager::ModemDevice::GsmInterface).objectCast<ModemManager::Modem3gpp>();
0046 
0047     // if no sim is inserted, m_mm3gppDevice is nullptr
0048     if (m_mm3gppDevice) {
0049         m_mm3gppDevice->setTimeout(60000); // scanning networks likely takes longer than the default timeout
0050     }
0051 
0052     // find networkmanager modem, if it exists
0053     findNetworkManagerDevice();
0054 
0055     // we need to initialize it after m_mm3gppDevice has been set
0056     m_details = new ModemDetails(this, this);
0057 }
0058 
0059 void Modem::findNetworkManagerDevice()
0060 {
0061     m_nmModem = nullptr;
0062 
0063     // find networkmanager modem device
0064     for (NetworkManager::Device::Ptr nmDevice : NetworkManager::networkInterfaces()) {
0065         if (nmDevice->udi() == m_mmModem->uni()) {
0066             m_nmModem = nmDevice.objectCast<NetworkManager::ModemDevice>();
0067         }
0068     }
0069 
0070     if (m_nmModem) {
0071         connect(m_nmModem.data(), &NetworkManager::Device::autoconnectChanged, this, &Modem::mobileDataEnabledChanged);
0072         connect(m_nmModem.data(), &NetworkManager::Device::stateChanged, this, &Modem::mobileDataEnabledChanged);
0073         connect(m_nmModem.data(), &NetworkManager::Device::availableConnectionAppeared, this, &Modem::mobileDataEnabledChanged);
0074         connect(m_nmModem.data(), &NetworkManager::Device::availableConnectionDisappeared, this, &Modem::mobileDataEnabledChanged);
0075 
0076         connect(m_nmModem.data(), &NetworkManager::ModemDevice::availableConnectionChanged, this, [this]() -> void {
0077             refreshProfiles();
0078         });
0079         connect(m_nmModem.data(), &NetworkManager::ModemDevice::activeConnectionChanged, this, [this]() -> void {
0080             refreshProfiles();
0081             Q_EMIT activeConnectionUniChanged();
0082         });
0083         connect(m_nmModem.data(), &NetworkManager::ModemDevice::stateChanged, this, [this](auto newstate, auto oldstate, auto reason) -> void {
0084             qDebug() << QStringLiteral("Modem") << m_nmModem->uni() << QStringLiteral("changed state:") << nmDeviceStateStr(oldstate) << QStringLiteral("->")
0085                      << nmDeviceStateStr(newstate) << QStringLiteral("due to:") << reason;
0086         });
0087 
0088         // add connection profiles
0089         refreshProfiles();
0090     }
0091 
0092     Q_EMIT nmModemChanged();
0093     Q_EMIT mobileDataEnabledChanged();
0094     Q_EMIT mobileDataSupportedChanged();
0095 }
0096 
0097 ModemDetails *Modem::modemDetails() const
0098 {
0099     return m_details;
0100 }
0101 
0102 QString Modem::displayId() const
0103 {
0104     // in the form /org/freedesktop/ModemManager1/Modem/0
0105     QStringList uniSplit = uni().split("/");
0106     return uniSplit.count() == 0 ? QStringLiteral("(empty)") : QString(uniSplit[uniSplit.size() - 1]);
0107 }
0108 
0109 QString Modem::uni() const
0110 {
0111     return m_mmInterface->uni();
0112 }
0113 
0114 QString Modem::activeConnectionUni() const
0115 {
0116     if (m_nmModem && m_nmModem->activeConnection() && m_nmModem->activeConnection()->connection()) {
0117         return m_nmModem->activeConnection()->connection()->uuid();
0118     }
0119     return QString();
0120 }
0121 
0122 QCoro::Task<void> Modem::reset()
0123 {
0124     qDebug() << QStringLiteral("Resetting the modem...");
0125 
0126     QDBusReply<void> reply = co_await m_mmInterface->reset();
0127 
0128     if (!reply.isValid()) {
0129         qDebug() << QStringLiteral("Error resetting the modem:") << reply.error().message();
0130         CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error resetting the modem: %1", reply.error().message()));
0131     }
0132 }
0133 
0134 bool Modem::mobileDataSupported() const
0135 {
0136     return m_nmModem && hasSim();
0137 }
0138 
0139 bool Modem::needsAPNAdded() const
0140 {
0141     return m_nmModem && mobileDataSupported() && m_nmModem->availableConnections().count() == 0;
0142 }
0143 
0144 bool Modem::mobileDataEnabled() const
0145 {
0146     // no modem -> no mobile data -> report disabled
0147     if (!m_nmModem) {
0148         return false;
0149     }
0150 
0151     // mobile data already activated -> report enabled
0152     if (m_nmModem->state() == NetworkManager::Device::Activated) {
0153         return true;
0154     }
0155 
0156     // autoconnect disabled on the entire modem -> report disabled
0157     if (!m_nmModem->autoconnect()) {
0158         return false;
0159     }
0160 
0161     // at least one connection set to autoconnect -> report enabled
0162     for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) {
0163         if (con->settings()->autoconnect()) {
0164             return true;
0165         }
0166     }
0167 
0168     // modem, but no connection, set to autoconnect -> report disabled
0169     return false;
0170 }
0171 
0172 void Modem::setMobileDataEnabled(bool enabled)
0173 {
0174     if (!m_nmModem) {
0175         return;
0176     }
0177 
0178     if (!enabled) {
0179         m_nmModem->setAutoconnect(false);
0180         // we need to also set all connections to not autoconnect (#182)
0181         for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) {
0182             con->settings()->setAutoconnect(false);
0183             con->update(con->settings()->toMap());
0184         }
0185         m_nmModem->disconnectInterface();
0186     } else {
0187         m_nmModem->setAutoconnect(true);
0188         // activate the connection that was last used
0189         QDateTime latestTimestamp;
0190         NetworkManager::Connection::Ptr latestCon;
0191         for (NetworkManager::Connection::Ptr con : m_nmModem->availableConnections()) {
0192             QDateTime timestamp = con->settings()->timestamp();
0193             // if con was not used yet, skip it, otherwise:
0194             // if we have no latestTimestamp yet, con is the latest
0195             // otherwise, compare the timestamps
0196             // in case of a tie, use the first connection that was found
0197             if (!timestamp.isNull() && (latestTimestamp.isNull() || timestamp > latestTimestamp)) {
0198                 latestTimestamp = timestamp;
0199                 latestCon = con;
0200             }
0201         }
0202         // if we found the last used connection
0203         if (!latestCon.isNull()) {
0204             // set it to autoconnect and connect it immediately
0205             latestCon->settings()->setAutoconnect(true);
0206             latestCon->update(latestCon->settings()->toMap());
0207             NetworkManager::activateConnection(latestCon->path(), m_nmModem->uni(), "");
0208         }
0209     }
0210 }
0211 
0212 bool Modem::isRoaming() const
0213 {
0214     if (!m_nmModem || !m_nmModem->activeConnection() || !m_nmModem->activeConnection()->connection()) {
0215         return false;
0216     }
0217 
0218     auto connection = m_nmModem->activeConnection()->connection();
0219     NetworkManager::GsmSetting::Ptr gsmSetting = connection->settings()->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
0220 
0221     return gsmSetting ? !gsmSetting->homeOnly() : false;
0222 }
0223 
0224 QCoro::Task<void> Modem::setIsRoaming(bool roaming)
0225 {
0226     if (!m_nmModem || !m_nmModem->activeConnection() || !m_nmModem->activeConnection()->connection()) {
0227         co_return;
0228     }
0229 
0230     auto connection = m_nmModem->activeConnection()->connection();
0231 
0232     NetworkManager::GsmSetting::Ptr gsmSetting = connection->settings()->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
0233     if (gsmSetting) {
0234         gsmSetting->setHomeOnly(!roaming); // set roaming setting
0235 
0236         QDBusReply<void> reply = co_await connection->update(connection->settings()->toMap());
0237         if (!reply.isValid()) {
0238             qWarning() << QStringLiteral("Error updating connection settings for") << connection->uuid() << QStringLiteral(":") << reply.error().message()
0239                        << QStringLiteral(".");
0240             CellularNetworkSettings::instance()->addMessage(
0241                 InlineMessage::Error,
0242                 i18n("Error updating connection settings for %1: %2.", connection->uuid(), reply.error().message()));
0243         } else {
0244             qDebug() << QStringLiteral("Successfully updated connection settings") << connection->uuid() << QStringLiteral(".");
0245         }
0246     }
0247 
0248     // the connection uni has changed, refresh the profiles list
0249     refreshProfiles();
0250     Q_EMIT activeConnectionUniChanged();
0251 }
0252 
0253 bool Modem::hasSim() const
0254 {
0255     if (!m_mmModem) {
0256         return false;
0257     }
0258     return m_mmModem && m_mmModem->sim() && m_mmModem->sim()->uni() != QStringLiteral("/");
0259 }
0260 
0261 QList<ProfileSettings *> &Modem::profileList()
0262 {
0263     return m_profileList;
0264 }
0265 
0266 void Modem::refreshProfiles()
0267 {
0268     m_profileList.clear();
0269 
0270     if (!m_nmModem) {
0271         Q_EMIT profileListChanged();
0272         qWarning() << "No NetworkManager modem found, cannot refresh profiles.";
0273         return;
0274     }
0275 
0276     for (auto connection : m_nmModem->availableConnections()) {
0277         for (auto setting : connection->settings()->settings()) {
0278             if (setting.dynamicCast<NetworkManager::GsmSetting>()) {
0279                 m_profileList.append(new ProfileSettings(this, setting.dynamicCast<NetworkManager::GsmSetting>(), connection));
0280             }
0281         }
0282     }
0283     Q_EMIT profileListChanged();
0284 }
0285 
0286 QCoro::Task<void> Modem::activateProfile(const QString &connectionUni)
0287 {
0288     if (!m_nmModem) {
0289         qWarning() << "Cannot activate profile since there is no NetworkManager modem";
0290         co_return;
0291     }
0292 
0293     qDebug() << QStringLiteral("Activating profile on modem") << m_nmModem->uni() << QStringLiteral("for connection") << connectionUni << ".";
0294 
0295     // cache roaming setting
0296     bool roaming = isRoaming();
0297 
0298     NetworkManager::Connection::Ptr con;
0299 
0300     // disable autoconnect for all other connections
0301     for (auto connection : m_nmModem->availableConnections()) {
0302         if (connection->uuid() == connectionUni) {
0303             connection->settings()->setAutoconnect(true);
0304             con = connection;
0305         } else {
0306             connection->settings()->setAutoconnect(false);
0307         }
0308     }
0309 
0310     if (!con) {
0311         qDebug() << QStringLiteral("Connection") << connectionUni << QStringLiteral("not found.");
0312         co_return;
0313     }
0314 
0315     // activate connection manually
0316     // despite the documentation saying otherwise, activateConnection seems to need the DBus path, not uuid of the connection
0317     QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::activateConnection(con->path(), m_nmModem->uni(), "");
0318     if (!reply.isValid()) {
0319         qWarning() << QStringLiteral("Error activating connection:") << reply.error().message();
0320         CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error activating connection: %1", reply.error().message()));
0321         co_return;
0322     }
0323 
0324     // set roaming settings separately (since it changes the uni)
0325     co_await setIsRoaming(roaming);
0326 }
0327 
0328 QCoro::Task<void> Modem::addProfile(QString name, QString apn, QString username, QString password, QString networkType)
0329 {
0330     if (!m_nmModem) {
0331         qWarning() << "Cannot add profile since there is no NetworkManager modem";
0332         co_return;
0333     }
0334 
0335     NetworkManager::ConnectionSettings::Ptr settings{new NetworkManager::ConnectionSettings(NetworkManager::ConnectionSettings::Gsm)};
0336     settings->setId(name);
0337     settings->setUuid(NetworkManager::ConnectionSettings::createNewUuid());
0338     settings->setAutoconnect(true);
0339     settings->addToPermissions(KUser().loginName(), QString());
0340 
0341     NetworkManager::GsmSetting::Ptr gsmSetting = settings->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
0342     gsmSetting->setApn(apn);
0343     gsmSetting->setUsername(username);
0344     gsmSetting->setPassword(password);
0345     gsmSetting->setPasswordFlags(password.isEmpty() ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned);
0346     gsmSetting->setNetworkType(ProfileSettings::networkTypeFlag(networkType));
0347     gsmSetting->setHomeOnly(!isRoaming());
0348 
0349     gsmSetting->setInitialized(true);
0350 
0351     QDBusReply<QDBusObjectPath> reply = co_await NetworkManager::addAndActivateConnection(settings->toMap(), m_nmModem->uni(), "");
0352     if (!reply.isValid()) {
0353         qWarning() << QStringLiteral("Error adding connection:") << reply.error().message();
0354         CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error adding connection: %1", reply.error().message()));
0355     } else {
0356         qDebug() << QStringLiteral("Successfully added a new connection") << name << QStringLiteral("with APN") << apn << ".";
0357     }
0358 }
0359 
0360 QCoro::Task<void> Modem::removeProfile(const QString &connectionUni)
0361 {
0362     NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
0363     if (!con) {
0364         qWarning() << QStringLiteral("Could not find connection") << connectionUni << QStringLiteral("to update!");
0365         co_return;
0366     }
0367 
0368     QDBusReply<void> reply = co_await con->remove();
0369     if (!reply.isValid()) {
0370         qWarning() << QStringLiteral("Error removing connection") << reply.error().message();
0371         CellularNetworkSettings::instance()->addMessage(InlineMessage::Error, i18n("Error removing connection: %1", reply.error().message()));
0372     }
0373 }
0374 
0375 QCoro::Task<void> Modem::updateProfile(QString connectionUni, QString name, QString apn, QString username, QString password, QString networkType)
0376 {
0377     NetworkManager::Connection::Ptr con = NetworkManager::findConnectionByUuid(connectionUni);
0378     if (!con) {
0379         qWarning() << QStringLiteral("Could not find connection") << connectionUni << QStringLiteral("to update!");
0380         co_return;
0381     }
0382 
0383     NetworkManager::ConnectionSettings::Ptr conSettings = con->settings();
0384     if (!conSettings) {
0385         qWarning() << QStringLiteral("Could not find connection settings for") << connectionUni << QStringLiteral("to update!");
0386         co_return;
0387     }
0388 
0389     conSettings->setId(name);
0390 
0391     NetworkManager::GsmSetting::Ptr gsmSetting = conSettings->setting(NetworkManager::Setting::Gsm).dynamicCast<NetworkManager::GsmSetting>();
0392     gsmSetting->setApn(apn);
0393     gsmSetting->setUsername(username);
0394     gsmSetting->setPassword(password);
0395     gsmSetting->setPasswordFlags(password == "" ? NetworkManager::Setting::NotRequired : NetworkManager::Setting::AgentOwned);
0396     gsmSetting->setNetworkType(ProfileSettings::networkTypeFlag(networkType));
0397     gsmSetting->setHomeOnly(!isRoaming());
0398 
0399     gsmSetting->setInitialized(true);
0400 
0401     QDBusReply<void> reply = con->update(conSettings->toMap());
0402     if (!reply.isValid()) {
0403         qWarning() << QStringLiteral("Error updating connection settings for") << connectionUni << QStringLiteral(":") << reply.error().message()
0404                    << QStringLiteral(".");
0405         CellularNetworkSettings::instance()->addMessage(InlineMessage::Error,
0406                                                         i18n("Error updating connection settings for %1: %2.", connectionUni, reply.error().message()));
0407     } else {
0408         qDebug() << QStringLiteral("Successfully updated connection settings") << connectionUni << QStringLiteral(".");
0409     }
0410 }
0411 
0412 void Modem::addDetectedProfileSettings()
0413 {
0414     if (!m_mmModem) {
0415         qWarning() << "ModemManager device missing, cannot detect profile settings";
0416         return;
0417     }
0418 
0419     if (!hasSim()) {
0420         qWarning() << "No SIM found, cannot detect profile settings";
0421         return;
0422     }
0423 
0424     if (!m_mm3gppDevice) {
0425         qWarning() << "3gpp object not found, cannot detect profile settings";
0426         return;
0427     }
0428 
0429     bool found = false;
0430     static MobileProviders mobileProviders{};
0431 
0432     QString operatorCode = m_mm3gppDevice->operatorCode();
0433     qWarning() << QStringLiteral("Detecting profile settings. Using MCCMNC:") << operatorCode;
0434 
0435     // lookup apns with mccmnc codes
0436     for (QString &provider : mobileProviders.getProvidersFromMCCMNC(operatorCode)) {
0437         qWarning() << QStringLiteral("Provider:") << provider;
0438 
0439         for (auto apn : mobileProviders.getApns(provider)) {
0440             QVariantMap apnInfo = mobileProviders.getApnInfo(apn);
0441             qWarning() << QStringLiteral("Found gsm profile settings. Type:") << apnInfo[QStringLiteral("usageType")];
0442 
0443             // only add mobile data apns (not mms)
0444             if (apnInfo[QStringLiteral("usageType")].toString() == QStringLiteral("internet")) {
0445                 found = true;
0446 
0447                 QString name = provider;
0448                 if (!apnInfo[QStringLiteral("name")].isNull()) {
0449                     name += " - " + apnInfo[QStringLiteral("name")].toString();
0450                 }
0451 
0452                 addProfile(name,
0453                            apn,
0454                            apnInfo[QStringLiteral("username")].toString(),
0455                            apnInfo[QStringLiteral("password")].toString(),
0456                            QStringLiteral("4G/3G/2G"));
0457             }
0458 
0459             // TODO in the future for MMS settings, add else if here for == "mms"
0460         }
0461     }
0462 
0463     if (!found) {
0464         qDebug() << QStringLiteral("No profiles were found.");
0465         Q_EMIT couldNotAutodetectSettings();
0466     }
0467 }
0468 
0469 QList<Sim *> Modem::sims()
0470 {
0471     return m_sims;
0472 }
0473 
0474 ModemManager::ModemDevice::Ptr Modem::mmModemDevice()
0475 {
0476     return m_mmModem;
0477 }
0478 
0479 NetworkManager::ModemDevice::Ptr Modem::nmModemDevice()
0480 {
0481     return m_nmModem;
0482 }
0483 
0484 ModemManager::Modem::Ptr Modem::mmModemInterface()
0485 {
0486     return m_mmInterface;
0487 }
0488 
0489 QString Modem::nmDeviceStateStr(NetworkManager::Device::State state)
0490 {
0491     if (state == NetworkManager::Device::State::UnknownState)
0492         return i18n("Unknown");
0493     else if (state == NetworkManager::Device::State::Unmanaged)
0494         return i18n("Unmanaged");
0495     else if (state == NetworkManager::Device::State::Unavailable)
0496         return i18n("Unavailable");
0497     else if (state == NetworkManager::Device::State::Disconnected)
0498         return i18n("Disconnected");
0499     else if (state == NetworkManager::Device::State::Preparing)
0500         return i18n("Preparing");
0501     else if (state == NetworkManager::Device::State::ConfiguringHardware)
0502         return i18n("ConfiguringHardware");
0503     else if (state == NetworkManager::Device::State::NeedAuth)
0504         return i18n("NeedAuth");
0505     else if (state == NetworkManager::Device::State::ConfiguringIp)
0506         return i18n("ConfiguringIp");
0507     else if (state == NetworkManager::Device::State::CheckingIp)
0508         return i18n("CheckingIp");
0509     else if (state == NetworkManager::Device::State::WaitingForSecondaries)
0510         return i18n("WaitingForSecondaries");
0511     else if (state == NetworkManager::Device::State::Activated)
0512         return i18n("Activated");
0513     else if (state == NetworkManager::Device::State::Deactivating)
0514         return i18n("Deactivating");
0515     else if (state == NetworkManager::Device::State::Failed)
0516         return i18n("Failed");
0517     else
0518         return "";
0519 }