File indexing completed on 2024-04-28 16:42:51

0001 // SPDX-FileCopyrightText: 2020-2021 Bhushan Shah <bshah@kde.org>
0002 // SPDX-FileCopyrightText: 2020 Jonah BrĂ¼chert <jbb@kaidan.im>
0003 // SPDX-FileCopyrightText: 2021 Nicolas Fella <nicolas.fella@gmx.de>
0004 // SPDX-FileCopyrightText: 2021 Alexey Andreyev <aa13q@ya.ru>
0005 // SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 
0007 #include "notification-manager.h"
0008 #include "config.h"
0009 
0010 #include <KIO/ApplicationLauncherJob>
0011 #include <KLocalizedString>
0012 #include <QDBusConnection>
0013 #include <QTimer>
0014 
0015 static bool getScreenSaverActive()
0016 {
0017     bool active = false;
0018 #ifdef DIALER_BUILD_SHELL_OVERLAY
0019     QDBusMessage request = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.ScreenSaver"),
0020                                                           QStringLiteral("/ScreenSaver"),
0021                                                           QStringLiteral("org.freedesktop.ScreenSaver"),
0022                                                           QStringLiteral("GetActive"));
0023     const QDBusReply<bool> response = QDBusConnection::sessionBus().call(request);
0024     active = response.isValid() ? response.value() : false;
0025 #endif // DIALER_BUILD_SHELL_OVERLAY
0026     return active;
0027 }
0028 
0029 static void launchPlasmaDialerDesktopFile()
0030 {
0031     const auto desktopName = QStringLiteral("org.kde.phone.dialer");
0032     const KService::Ptr appService = KService::serviceByDesktopName(desktopName);
0033     if (!appService) {
0034         qWarning() << "Could not find" << desktopName;
0035         return;
0036     }
0037     KIO::ApplicationLauncherJob job(appService);
0038     job.start();
0039 }
0040 
0041 NotificationManager::NotificationManager(QObject *parent)
0042     : QObject(parent)
0043     , _ringingNotification(std::make_unique<KNotification>(QStringLiteral("ringing"), KNotification::Persistent | KNotification::LoopSound, nullptr))
0044     , _callStarted(false)
0045 
0046 #ifdef HAVE_QT5_FEEDBACK
0047     , _ringEffect(std::make_unique<QFeedbackHapticsEffect>())
0048 #endif // HAVE_QT5_FEEDBACK
0049 {
0050 #ifdef HAVE_QT5_FEEDBACK
0051     _ringEffect->setAttackIntensity(0.1);
0052     _ringEffect->setAttackTime(420);
0053     _ringEffect->setIntensity(0.7);
0054     _ringEffect->setDuration(5000);
0055     _ringEffect->setFadeTime(700);
0056     _ringEffect->setFadeIntensity(0.07);
0057     _ringEffect->setPeriod(1300);
0058 #endif // HAVE_QT5_FEEDBACK
0059 
0060     _databaseInterface = new org::kde::telephony::CallHistoryDatabase(QString::fromLatin1(_databaseInterface->staticInterfaceName()),
0061                                                                       QStringLiteral("/org/kde/telephony/CallHistoryDatabase/tel/mm"),
0062                                                                       QDBusConnection::sessionBus(),
0063                                                                       this);
0064 
0065     if (!_databaseInterface->isValid()) {
0066         qDebug() << Q_FUNC_INFO << "Could not initiate CallHistoryDatabase interface";
0067         return;
0068     }
0069     _ringingNotification->setAutoDelete(false);
0070 }
0071 
0072 void NotificationManager::setCallUtils(org::kde::telephony::CallUtils *callUtils)
0073 {
0074     qDebug() << Q_FUNC_INFO;
0075     _callUtils = callUtils;
0076 
0077     connect(_callUtils, &org::kde::telephony::CallUtils::callAdded, this, &NotificationManager::onCallAdded);
0078     connect(_callUtils, &org::kde::telephony::CallUtils::callStateChanged, this, &NotificationManager::onCallStateChanged);
0079     connect(_callUtils, &org::kde::telephony::CallUtils::callDeleted, this, &NotificationManager::onCallDeleted);
0080 }
0081 
0082 void NotificationManager::setContactUtils(ContactUtils *contactUtils)
0083 {
0084     _contactUtils = contactUtils;
0085 }
0086 
0087 void NotificationManager::onCallAdded(const QString &deviceUni,
0088                                       const QString &callUni,
0089                                       const DialerTypes::CallDirection &callDirection,
0090                                       const DialerTypes::CallState &callState,
0091                                       const DialerTypes::CallStateReason &callStateReason,
0092                                       const QString communicationWith)
0093 {
0094     qDebug() << Q_FUNC_INFO << "call added" << deviceUni << callUni << callDirection << callState << callStateReason;
0095     if (callDirection == DialerTypes::CallDirection::Incoming) {
0096         if (callState == DialerTypes::CallState::RingingIn) {
0097             handleIncomingCall(deviceUni, callUni, communicationWith);
0098             _callStarted = false;
0099         }
0100     }
0101 }
0102 
0103 void NotificationManager::onCallDeleted(const QString &deviceUni, const QString &callUni)
0104 {
0105     qDebug() << Q_FUNC_INFO << "call deleted" << deviceUni << callUni;
0106     handleCallInteraction();
0107 }
0108 
0109 void NotificationManager::onCallStateChanged(const DialerTypes::CallData &callData)
0110 {
0111     qDebug() << Q_FUNC_INFO << "call state changed:" << callData.state << callData.stateReason;
0112 
0113     const QString contactName = _contactUtils->displayString(callData.communicationWith);
0114     QString callerDisplay = (contactName == callData.communicationWith)
0115         ? callData.communicationWith
0116         : callData.communicationWith + QStringLiteral("<br>") + QStringLiteral("<b>%1</b>").arg(contactName);
0117     if (callerDisplay.isEmpty()) {
0118         callerDisplay = i18n("No Caller ID");
0119     }
0120 
0121     if (callData.direction == DialerTypes::CallDirection::Incoming) {
0122         if (callData.state == DialerTypes::CallState::Terminated) {
0123             handleCallInteraction();
0124 
0125             if (callData.stateReason == DialerTypes::CallStateReason::Unknown && !_callStarted) {
0126                 auto _missedCallNotification = new KNotification(QStringLiteral("callMissed"));
0127                 _missedCallNotification->setComponentName(QStringLiteral("plasma_dialer"));
0128                 _missedCallNotification->setTitle(i18n("Missed call"));
0129                 _missedCallNotification->setText(i18n("Missed call from %1", callerDisplay));
0130                 _missedCallNotification->sendEvent();
0131             }
0132         }
0133         if (callData.state == DialerTypes::CallState::Active) {
0134             handleCallInteraction();
0135             _callStarted = true;
0136         }
0137     }
0138 }
0139 
0140 void NotificationManager::openRingingNotification(const QString &deviceUni,
0141                                                   const QString &callUni,
0142                                                   const QString callerDisplay,
0143                                                   const QString notificationEvent)
0144 {
0145     _ringingNotification->setEventId(notificationEvent);
0146     _ringingNotification->setUrgency(KNotification::CriticalUrgency);
0147     _ringingNotification->setComponentName(QStringLiteral("plasma_dialer"));
0148 
0149     // _ringingNotification->setPixmap(person.photo());
0150     _ringingNotification->setTitle(i18n("Incoming call"));
0151     _ringingNotification->setText(callerDisplay);
0152     // this will be used by the notification applet to show custom notification UI
0153     // with swipe decision.
0154     _ringingNotification->setHint(QStringLiteral("category"), QStringLiteral("x-kde.incoming-call"));
0155 
0156     auto acceptAction = _ringingNotification->addAction(i18n("Accept"));
0157     connect(acceptAction, &KNotificationAction::activated, this, [this, deviceUni, callUni] {
0158         accept(deviceUni, callUni);
0159         launchPlasmaDialerDesktopFile();
0160     });
0161 
0162     auto rejectAction = _ringingNotification->addAction(i18n("Reject"));
0163     connect(rejectAction, &KNotificationAction::activated, this, [this, deviceUni, callUni] {
0164         hangUp(deviceUni, callUni);
0165     });
0166 
0167     _ringingNotification->sendEvent();
0168 }
0169 
0170 void NotificationManager::closeRingingNotification()
0171 {
0172     _ringingNotification->disconnect();
0173     _ringingNotification->close();
0174 }
0175 
0176 void NotificationManager::accept(const QString &deviceUni, const QString &callUni)
0177 {
0178     QDBusPendingReply<> reply = _callUtils->accept(deviceUni, callUni);
0179     reply.waitForFinished();
0180     if (reply.isError()) {
0181         qDebug() << Q_FUNC_INFO << reply.error();
0182     }
0183     closeRingingNotification();
0184 }
0185 
0186 void NotificationManager::hangUp(const QString &deviceUni, const QString &callUni)
0187 {
0188     QDBusPendingReply<> reply = _callUtils->hangUp(deviceUni, callUni);
0189     reply.waitForFinished();
0190     if (reply.isError()) {
0191         qDebug() << Q_FUNC_INFO << reply.error();
0192     }
0193     closeRingingNotification();
0194 }
0195 
0196 void NotificationManager::handleIncomingCall(const QString &deviceUni, const QString &callUni, const QString &communicationWith)
0197 {
0198     const QString contactName = _contactUtils->displayString(communicationWith);
0199 
0200     bool allowed = true;
0201 
0202     if (Config::self()->adaptiveBlocking() && contactName == communicationWith) {
0203         allowed = false;
0204 
0205         if (Config::self()->allowAnonymous() && communicationWith.isEmpty()) {
0206             allowed = true;
0207         }
0208 
0209         if (Config::self()->allowPreviousOutgoing()) {
0210             QString lastOutgoing = _databaseInterface->lastCall(communicationWith, static_cast<int>(DialerTypes::CallDirection::Outgoing));
0211             if (!lastOutgoing.isEmpty()) {
0212                 allowed = true;
0213             }
0214         }
0215 
0216         if (Config::self()->allowCallback()) {
0217             QString lastIncoming = _databaseInterface->lastCall(communicationWith, static_cast<int>(DialerTypes::CallDirection::Incoming));
0218             QDateTime lastTime = QDateTime::fromString(lastIncoming, QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"));
0219             qint64 diff = lastTime.msecsTo(QDateTime::currentDateTime());
0220             if (diff / 1000 / 60 < Config::self()->callbackInterval()) {
0221                 allowed = true;
0222             }
0223         }
0224 
0225         if (Config::self()->allowedPatterns().length() > 0) {
0226             for (const auto &pattern : Config::self()->allowedPatterns()) {
0227                 if (communicationWith.indexOf(pattern) >= 0) {
0228                     allowed = true;
0229                     break;
0230                 }
0231             }
0232         }
0233     }
0234 
0235     QString notificationEvent = QStringLiteral("ringing");
0236 
0237     bool silent = false;
0238     if (!allowed) {
0239         if (Config::self()->ringOption() == 0) {
0240             hangUp(deviceUni, callUni);
0241             return;
0242         } else if (Config::self()->ringOption() == 1) {
0243             return;
0244         }
0245         notificationEvent = QStringLiteral("ringing-silent");
0246         silent = true;
0247     }
0248 
0249     bool screenLocked = getScreenSaverActive();
0250 
0251     bool skipNotification = false;
0252     if (screenLocked) {
0253         notificationEvent = QStringLiteral("ringing-without-popup");
0254         if (silent) {
0255             skipNotification = true;
0256         }
0257     }
0258 
0259     if (allowed) {
0260         startHapticsFeedback();
0261     }
0262 
0263     QString callerDisplay =
0264         (contactName == communicationWith) ? communicationWith : communicationWith + QStringLiteral("<br>") + QStringLiteral("<b>%1</b>").arg(contactName);
0265 
0266     if (callerDisplay.isEmpty()) {
0267         callerDisplay = i18n("No Caller ID");
0268     }
0269 
0270     if (!skipNotification) {
0271         openRingingNotification(deviceUni, callUni, callerDisplay, notificationEvent);
0272     }
0273 
0274     if (screenLocked) {
0275         launchPlasmaDialerDesktopFile();
0276     }
0277 }
0278 
0279 void NotificationManager::handleCallInteraction()
0280 {
0281     stopHapticsFeedback();
0282     closeRingingNotification();
0283 }
0284 
0285 void NotificationManager::startHapticsFeedback()
0286 {
0287 #ifdef HAVE_QT5_FEEDBACK
0288     _ringEffect->start();
0289 #endif // HAVE_QT5_FEEDBACK
0290 }
0291 
0292 void NotificationManager::stopHapticsFeedback()
0293 {
0294 #ifdef HAVE_QT5_FEEDBACK
0295     _ringEffect->stop();
0296 #endif // HAVE_QT5_FEEDBACK
0297 }