File indexing completed on 2025-03-23 13:45:11
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 }