diff --git a/panels/notification/CMakeLists.txt b/panels/notification/CMakeLists.txt index b7c6414eb..f7a60ff03 100644 --- a/panels/notification/CMakeLists.txt +++ b/panels/notification/CMakeLists.txt @@ -22,6 +22,8 @@ add_library(ds-notification-shared SHARED ${CMAKE_SOURCE_DIR}/panels/notification/common/dbaccessor.cpp ${CMAKE_SOURCE_DIR}/panels/notification/common/notifysetting.h ${CMAKE_SOURCE_DIR}/panels/notification/common/notifysetting.cpp + ${CMAKE_SOURCE_DIR}/panels/notification/common/expiretimer.h + ${CMAKE_SOURCE_DIR}/panels/notification/common/expiretimer.cpp ) set_target_properties(ds-notification-shared PROPERTIES diff --git a/panels/notification/bubble/bubbleitem.cpp b/panels/notification/bubble/bubbleitem.cpp index 60d48a6d7..f838b7008 100644 --- a/panels/notification/bubble/bubbleitem.cpp +++ b/panels/notification/bubble/bubbleitem.cpp @@ -51,6 +51,11 @@ qint64 BubbleItem::id() const return m_entity.id(); } +const NotifyEntity &BubbleItem::entity() const +{ + return m_entity; +} + uint BubbleItem::bubbleId() const { return m_entity.bubbleId(); @@ -91,6 +96,11 @@ int BubbleItem::urgency() const return m_urgency; } +int BubbleItem::timeout() const +{ + return m_entity.timeout(); +} + QString BubbleItem::bodyImagePath() const { return m_entity.bodyIcon(); diff --git a/panels/notification/bubble/bubbleitem.h b/panels/notification/bubble/bubbleitem.h index 1b28f0aa8..49a3662e5 100644 --- a/panels/notification/bubble/bubbleitem.h +++ b/panels/notification/bubble/bubbleitem.h @@ -21,6 +21,7 @@ class BubbleItem : public QObject public: void setEntity(const NotifyEntity &entity); + const NotifyEntity &entity() const; public: qint64 id() const; @@ -32,6 +33,7 @@ class BubbleItem : public QObject uint replacesId() const; bool isReplace() const; int urgency() const; + int timeout() const; QString bodyImagePath() const; qint64 ctime() const; diff --git a/panels/notification/bubble/bubblemodel.cpp b/panels/notification/bubble/bubblemodel.cpp index c9bbee6fb..30400f8d9 100644 --- a/panels/notification/bubble/bubblemodel.cpp +++ b/panels/notification/bubble/bubblemodel.cpp @@ -7,6 +7,8 @@ #include #include "bubbleitem.h" +#include "dataaccessorproxy.h" +#include "expiretimer.h" #include #include @@ -20,6 +22,8 @@ Q_DECLARE_LOGGING_CATEGORY(notifyLog) namespace notification { +static const int BlockItemTimeout = 1000; + BubbleModel::BubbleModel(QObject *parent) : QAbstractListModel(parent) , m_updateTimeTipTimer(new QTimer(this)) @@ -41,6 +45,12 @@ BubbleModel::BubbleModel(QObject *parent) m_processPendingTimer->start(); } }); + connect(ExpireTimer::instance(), &ExpireTimer::expired, this, [this](qint64 id) { + // A bubble that has been pushed off the display (overflow) is no longer + // in the model, but its countdown still finishes and the notification + // should still be closed, so fall back to the persisted bubble id. + Q_EMIT bubbleExpired(id, DataAccessorProxy::instance()->fetchEntity(id).bubbleId()); + }); connect(NotifySetting::instance(), &NotifySetting::contentRowCountChanged, this, &BubbleModel::updateContentRowCount); connect(NotifySetting::instance(), &NotifySetting::bubbleCountChanged, this, &BubbleModel::updateBubbleCount); @@ -82,6 +92,10 @@ void BubbleModel::insertBubble(BubbleItem *bubble) beginInsertRows(QModelIndex(), 0, 0); m_bubbles.prepend(bubble); endInsertRows(); + + // A non-positive interval (Critical urgency or expireTimeout 0) means the + // bubble never expires on its own. + ExpireTimer::instance()->start(bubble->id(), bubble->entity()); } bool BubbleModel::isReplaceBubble(const BubbleItem *bubble) const @@ -95,28 +109,18 @@ BubbleItem *BubbleModel::replaceBubble(BubbleItem *bubble) const auto replaceIndex = replaceBubbleIndex(bubble); const auto oldBubble = m_bubbles[replaceIndex]; + ExpireTimer::instance()->stop(oldBubble->id()); + m_bubbles.replace(replaceIndex, bubble); Q_EMIT dataChanged(index(replaceIndex), index(replaceIndex)); - return oldBubble; -} + ExpireTimer::instance()->start(bubble->id(), bubble->entity()); -void BubbleModel::clear() -{ - if (m_processPendingTimer) { - m_processPendingTimer->stop(); - } - qDeleteAll(m_pendingBubbles); - m_pendingBubbles.clear(); - - if (m_bubbles.count() <= 0) - return; - beginResetModel(); - qDeleteAll(m_bubbles); - m_bubbles.clear(); - endResetModel(); + // If the replaced bubble was the hovered one, keep blocking the new one. + if (m_blockedId == oldBubble->id()) + ExpireTimer::instance()->pause(bubble->id()); - m_updateTimeTipTimer->stop(); + return oldBubble; } QList BubbleModel::items() const @@ -129,11 +133,12 @@ void BubbleModel::remove(int index) if (index < 0 || index >= m_bubbles.size()) return; - beginRemoveRows(QModelIndex(), index, index); auto bubble = m_bubbles.takeAt(index); + ExpireTimer::instance()->stop(bubble->id()); + + beginRemoveRows(QModelIndex(), index, index); bubble->deleteLater(); endRemoveRows(); - } void BubbleModel::remove(const BubbleItem *bubble) @@ -298,4 +303,19 @@ void BubbleModel::updateContentRowCount(int rowCount) Q_EMIT dataChanged(index(0), index(m_bubbles.size() - 1), {BubbleModel::ContentRowCount}); } } + +void BubbleModel::setBlockedId(qint64 id) +{ + if (id == m_blockedId) + return; + + if (m_blockedId != NotifyEntity::InvalidId) + ExpireTimer::instance()->resume(m_blockedId, BlockItemTimeout); + + m_blockedId = id; + + if (id != NotifyEntity::InvalidId) + ExpireTimer::instance()->pause(id); } + +} // notification diff --git a/panels/notification/bubble/bubblemodel.h b/panels/notification/bubble/bubblemodel.h index b9b8f6203..c538644ec 100644 --- a/panels/notification/bubble/bubblemodel.h +++ b/panels/notification/bubble/bubblemodel.h @@ -8,6 +8,7 @@ #include "notifyentity.h" #include +#include #include class QTimer; @@ -38,6 +39,10 @@ class BubbleModel : public QAbstractListModel explicit BubbleModel(QObject *parent = nullptr); ~BubbleModel() override; +Q_SIGNALS: + // Emitted when a bubble reaches its expire timeout and should be closed. + void bubbleExpired(qint64 id, uint bubbleId); + public: void push(BubbleItem *bubble); @@ -49,7 +54,10 @@ class BubbleModel : public QAbstractListModel Q_INVOKABLE void remove(int index); void remove(const BubbleItem *bubble); BubbleItem *removeById(qint64 id); - void clear(); + + // Pause/resume the expire timer of the hovered bubble so hovering + // keeps the bubble on screen (0 clears the blocked bubble). + void setBlockedId(qint64 id); BubbleItem *bubbleItem(int bubbleIndex) const; @@ -68,11 +76,11 @@ class BubbleModel : public QAbstractListModel void updateBubbleTimeTip(); void updateContentRowCount(int rowCount); -private: QTimer *m_updateTimeTipTimer = nullptr; QTimer *m_processPendingTimer = nullptr; QList m_bubbles; QQueue m_pendingBubbles; + qint64 m_blockedId = NotifyEntity::InvalidId; int m_maxKeep{5}; int m_contentRowCount{6}; }; diff --git a/panels/notification/bubble/bubblepanel.cpp b/panels/notification/bubble/bubblepanel.cpp index 469fe67f7..61073138e 100644 --- a/panels/notification/bubble/bubblepanel.cpp +++ b/panels/notification/bubble/bubblepanel.cpp @@ -53,6 +53,14 @@ bool BubblePanel::init() connect(m_bubbles, &BubbleModel::rowsInserted, this, &BubblePanel::onBubbleCountChanged); connect(m_bubbles, &BubbleModel::rowsRemoved, this, &BubblePanel::onBubbleCountChanged); + // The bubble model runs one expire timer per shown bubble. When a bubble + // times out, close it and notify the server so it moves the notification + // from the in-memory store to the center database and emits the signals. + connect(m_bubbles, &BubbleModel::bubbleExpired, this, [this](qint64 id, uint bubbleId) { + closeBubble(id); + QMetaObject::invokeMethod(m_notificationServer, "notificationClosed", Qt::DirectConnection, + Q_ARG(qint64, id), Q_ARG(uint, bubbleId), Q_ARG(uint, NotifyEntity::Expired)); + }); return true; } @@ -217,7 +225,7 @@ void BubblePanel::setEnabled(bool newEnabled) void BubblePanel::setHoveredId(qint64 id) { - QMetaObject::invokeMethod(m_notificationServer, "setBlockClosedId", Qt::DirectConnection, Q_ARG(qint64, id)); + m_bubbles->setBlockedId(id); } } diff --git a/panels/notification/center/notifystagingmodel.cpp b/panels/notification/center/notifystagingmodel.cpp index 303bfc75c..80c665534 100644 --- a/panels/notification/center/notifystagingmodel.cpp +++ b/panels/notification/center/notifystagingmodel.cpp @@ -8,6 +8,7 @@ #include #include "dataaccessorproxy.h" +#include "expiretimer.h" #include "notifyaccessor.h" #include "notifyentity.h" #include "notifyitem.h" @@ -25,6 +26,10 @@ NotifyStagingModel::NotifyStagingModel(QObject *parent) connect(NotifyAccessor::instance(), &NotifyAccessor::stagingEntityReceived, this, &NotifyStagingModel::doEntityReceived); connect(NotifyAccessor::instance(), &NotifyAccessor::stagingEntityClosed, this, &NotifyStagingModel::onEntityClosed); connect(NotifySetting::instance(), &NotifySetting::contentRowCountChanged, this, &NotifyStagingModel::updateContentRowCount); + + connect(ExpireTimer::instance(), &ExpireTimer::expired, this, [this](qint64 id) { + closeNotify(id, NotifyEntity::Expired); + }); } void NotifyStagingModel::close() @@ -63,6 +68,10 @@ void NotifyStagingModel::push(const NotifyEntity &entity) updateOverlapCount(count); } + // A non-positive interval (Critical urgency or expireTimeout 0) means the + // notification never expires on its own. + ExpireTimer::instance()->start(entity.id(), entity); + if (m_refreshTimer < 0) { m_refreshTimer = startTimer(std::chrono::milliseconds(1000)); } @@ -90,6 +99,8 @@ void NotifyStagingModel::remove(qint64 id) { qDebug(notifyLog) << "Remove notify by id" << id; + ExpireTimer::instance()->stop(id); + int row = -1; for (int i = 0; i < m_appNotifies.size(); i++) { auto item = m_appNotifies[i]; @@ -146,6 +157,7 @@ void NotifyStagingModel::remove(qint64 id) auto notify = new AppNotifyItem(newEntity); m_appNotifies.insert(insertedIndex, notify); endInsertRows(); + ExpireTimer::instance()->start(newEntity.id(), newEntity); } } updateOverlapCount(entities.size()); @@ -172,6 +184,9 @@ void NotifyStagingModel::open() auto notify = new AppNotifyItem(entities.at(i)); m_appNotifies << notify; } + for (const auto &entity : entities) { + ExpireTimer::instance()->start(entity.id(), entity); + } updateOverlapCount(entities.size()); endResetModel(); @@ -250,8 +265,10 @@ void NotifyStagingModel::replace(const NotifyEntity &entity) { for (int i = 0; i < m_appNotifies.size(); i++) { auto item = m_appNotifies[i]; - if (item->id() == entity.bubbleId()) { + if (item->id() == entity.id()) { + ExpireTimer::instance()->stop(entity.id()); item->setEntity(entity); + ExpireTimer::instance()->start(entity.id(), entity); const auto index = this->index(i, 0, {}); dataChanged(index, index); break; diff --git a/panels/notification/common/expiretimer.cpp b/panels/notification/common/expiretimer.cpp new file mode 100644 index 000000000..d9e29f5eb --- /dev/null +++ b/panels/notification/common/expiretimer.cpp @@ -0,0 +1,128 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "expiretimer.h" + +#include +#include + +#include + +#include "notifyentity.h" + +namespace notification { + +static const int DefaultTimeoutMSecs = 5000; + +// Effective expire timeout in milliseconds for a notification. +// Returns 0 for "never expire" (Critical urgency or expireTimeout == 0) and +// falls back to the server default of 5000 ms for expireTimeout == -1. +static int effectiveTimeout(const NotifyEntity &entity) +{ + if (entity.urgency() == NotifyEntity::Critical || entity.timeout() == 0) + return 0; + + return entity.timeout() == -1 ? DefaultTimeoutMSecs : entity.timeout(); +} + +ExpireTimer::ExpireTimer(QObject *parent) + : QObject(parent) + , m_timer(new QTimer(this)) +{ + m_timer->setSingleShot(true); + connect(m_timer, &QTimer::timeout, this, &ExpireTimer::onTimeout); +} + +ExpireTimer *ExpireTimer::instance() +{ + static ExpireTimer expireTimer; + return &expireTimer; +} + +void ExpireTimer::start(qint64 key, const NotifyEntity &entity) +{ + const int interval = effectiveTimeout(entity); + if (interval <= 0) { + // Never expire: cancel any pending or paused countdown. + m_deadlines.remove(key); + m_paused.remove(key); + schedule(); + return; + } + + // A notification displayed in both the bubble and the staging area shares + // one deadline: starting an already tracked key keeps the original + // countdown instead of restarting it. + if (m_deadlines.contains(key) || m_paused.contains(key)) + return; + + m_deadlines.insert(key, QDateTime::currentMSecsSinceEpoch() + interval); + schedule(); +} + +void ExpireTimer::pause(qint64 key) +{ + const auto it = m_deadlines.constFind(key); + if (it == m_deadlines.cend()) + return; + + m_paused.insert(key, static_cast(qMax(0, it.value() - QDateTime::currentMSecsSinceEpoch()))); + m_deadlines.erase(it); + schedule(); +} + +void ExpireTimer::resume(qint64 key, int minRemaining) +{ + const auto it = m_paused.find(key); + if (it == m_paused.end()) + return; + + const int remaining = qMax(it.value(), minRemaining); + m_paused.erase(it); + m_deadlines.insert(key, QDateTime::currentMSecsSinceEpoch() + remaining); + schedule(); +} + +void ExpireTimer::stop(qint64 key) +{ + const bool removed = m_deadlines.remove(key) > 0; + m_paused.remove(key); + if (removed) + schedule(); +} + +void ExpireTimer::schedule() +{ + if (m_deadlines.isEmpty()) { + m_timer->stop(); + return; + } + + auto it = std::min_element(m_deadlines.cbegin(), m_deadlines.cend(), + [](const qint64 &lhs, const qint64 &rhs) { return lhs < rhs; }); + const qint64 remaining = qMax(0, it.value() - QDateTime::currentMSecsSinceEpoch()); + m_timer->start(static_cast(remaining)); +} + +void ExpireTimer::onTimeout() +{ + const auto now = QDateTime::currentMSecsSinceEpoch(); + const QList expiredKeys = [this, now] { + QList keys; + for (auto it = m_deadlines.cbegin(); it != m_deadlines.cend(); ++it) { + if (it.value() <= now) + keys.append(it.key()); + } + return keys; + }(); + + for (const auto &key : expiredKeys) { + if (m_deadlines.remove(key) > 0) + Q_EMIT expired(key); + } + + schedule(); +} + +} diff --git a/panels/notification/common/expiretimer.h b/panels/notification/common/expiretimer.h new file mode 100644 index 000000000..2017cf23d --- /dev/null +++ b/panels/notification/common/expiretimer.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +class QTimer; + +namespace notification { + +class NotifyEntity; + +/** + * @brief Process-wide singleton that tracks the expire deadline of every + * displayed notification (bubble or staging area) with one shared + * single-shot QTimer. + * + * Each notification id keeps its own deadline and, while blocked, its remaining + * time. The nearest deadline drives the shared QTimer; when a deadline passes, + * expired() is emitted once for that key and the key is forgotten. Starting an + * already tracked key keeps the original deadline, so a notification shown in + * both the bubble and the staging area never restarts its countdown. The + * effective timeout is derived internally from the entity, so callers only + * describe what to do (start, pause, resume, stop) and never have to compute or + * query timeouts. No QTimer is allocated per key and nothing leaks when a key + * expires or is stopped. + */ +class ExpireTimer : public QObject +{ + Q_OBJECT +public: + static ExpireTimer *instance(); + + // Starts (or keeps) a countdown for key based on the entity's urgency and + // expire timeout. A non-positive timeout (Critical urgency or expireTimeout + // 0) cancels any pending countdown, i.e. the notification never expires. + void start(qint64 key, const NotifyEntity &entity); + // Blocks key from expiring, keeping its remaining time for resume(). + void pause(qint64 key); + // Resumes a paused key with at least minRemaining milliseconds left. + void resume(qint64 key, int minRemaining = 0); + // Forgets key so it never expires. + void stop(qint64 key); + +Q_SIGNALS: + // Emitted once when the deadline of key passes. + void expired(qint64 key); + +private: + explicit ExpireTimer(QObject *parent = nullptr); + + void schedule(); + void onTimeout(); + + QTimer *m_timer = nullptr; + QHash m_deadlines; + QHash m_paused; +}; + +} diff --git a/panels/notification/common/notifyentity.cpp b/panels/notification/common/notifyentity.cpp index 7c55cdc82..a7c832096 100644 --- a/panels/notification/common/notifyentity.cpp +++ b/panels/notification/common/notifyentity.cpp @@ -236,6 +236,16 @@ bool NotifyEntity::isReplace() const return d->replacesId != NoReplaceId; } +int NotifyEntity::timeout() const +{ + return d->expireTimeout; +} + +int NotifyEntity::urgency() const +{ + return d->hints.value("urgency").toInt(); +} + qint64 NotifyEntity::cTime() const { return d->cTime; diff --git a/panels/notification/common/notifyentity.h b/panels/notification/common/notifyentity.h index 967280ddb..f3f99200f 100644 --- a/panels/notification/common/notifyentity.h +++ b/panels/notification/common/notifyentity.h @@ -81,6 +81,10 @@ class NotifyEntity void setReplacesId(uint replacesId); bool isReplace() const; + // Expire timeout in milliseconds passed in by the client (-1 means server default). + int timeout() const; + int urgency() const; + qint64 cTime() const; void setCTime(qint64 cTime); diff --git a/panels/notification/server/notificationmanager.cpp b/panels/notification/server/notificationmanager.cpp index 74c564197..7edca3aa2 100644 --- a/panels/notification/server/notificationmanager.cpp +++ b/panels/notification/server/notificationmanager.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -37,8 +36,6 @@ Q_DECLARE_LOGGING_CATEGORY(notifyLog) namespace notification { static const uint NoReplacesId = 0; -static const int DefaultTimeOutMSecs = 5000; -static const int BlockItemTimeout = 1000; static const QString NotificationsDBusService = "org.freedesktop.Notifications"; static const QString NotificationsDBusPath = "/org/freedesktop/Notifications"; static const QString DDENotifyDBusServer = "org.deepin.dde.Notification1"; @@ -50,11 +47,7 @@ NotificationManager::NotificationManager(QObject *parent) : QObject(parent) , m_persistence(DataAccessorProxy::instance()) , m_setting(new NotificationSetting(this)) - , m_pendingTimeout(new QTimer(this)) { - m_pendingTimeout->setSingleShot(true); - connect(m_pendingTimeout, &QTimer::timeout, this, &NotificationManager::onHandingPendingEntities); - DataAccessorProxy::instance()->setSource(DBAccessor::instance()); DAppletBridge bridge("org.deepin.ds.dde-apps"); @@ -164,6 +157,20 @@ void NotificationManager::actionInvoked(qint64 id, uint bubbleId, const QString void NotificationManager::notificationClosed(qint64 id, uint bubbleId, uint reason) { qDebug(notifyLog) << "Close notification id" << id << ", reason" << reason; + + const auto entity = m_persistence->fetchEntity(id); + // A notification can be tracked by more than one expire timer (the bubble + // frontend and the notification center staging model both schedule a timeout + // for the same id), so it may already be closed or removed by the time this + // is reached. Report the close only once to avoid emitting NotificationClosed + // twice for a single notification. + if (!entity.isValid()) + return; + + // Critical notifications must not disappear on their own. + if (reason == NotifyEntity::Expired && entity.urgency() == NotifyEntity::Critical) + return; + updateEntityProcessed(id, reason); Q_EMIT NotificationClosed(bubbleId, reason); @@ -296,22 +303,9 @@ uint NotificationManager::Notify(const QString &appName, uint replacesId, const return 0; } - if (entity.isReplace() && m_persistence->fetchLastEntity(entity.bubbleId()).isValid()) { - removePendingEntity(entity); - } - emitRecordCountChanged(); Q_EMIT NotificationStateChanged(entity.id(), entity.processedType()); - - bool critical = false; - if (auto iter = hints.find("urgency"); iter != hints.end()) { - critical = iter.value().toUInt() == NotifyEntity::Critical; - } - // 0: never expire. -1: DefaultTimeOutMSecs - if (expireTimeout != 0 && !critical) { - pushPendingEntity(entity, expireTimeout); - } } tryPlayNotificationSound(entity, appId, dndMode); @@ -372,29 +366,6 @@ QVariant NotificationManager::GetSystemInfo(uint configItem) return m_setting->systemValue(static_cast(configItem)); } -void NotificationManager::setBlockClosedId(qint64 id) -{ - if (id == m_blockClosedId) { - return; - } - - if(m_blockClosedId != NotifyEntity::InvalidId) { - auto findIter = std::find_if(m_pendingTimeoutEntities.begin(), m_pendingTimeoutEntities.end(), [this](const NotifyEntity &entity) { - return entity.id() == m_blockClosedId; - }); - - const auto current = QDateTime::currentMSecsSinceEpoch(); - if (findIter != m_pendingTimeoutEntities.end()) { - if (current > findIter.key() - BlockItemTimeout) { - qDebug(notifyLog) << "Delay close bubble id:" << m_blockClosedId << "for the new block bubble id:" << id; - m_pendingTimeoutEntities.insert(current + BlockItemTimeout, findIter.value()); - m_pendingTimeoutEntities.erase(findIter); - } - } - } - m_blockClosedId = id; - onHandingPendingEntities(); -} bool NotificationManager::isDoNotDisturb() const { @@ -498,21 +469,6 @@ void NotificationManager::emitRecordCountChanged() emit RecordCountChanged(count); } -void NotificationManager::pushPendingEntity(const NotifyEntity &entity, int expireTimeout) -{ - const int interval = expireTimeout == -1 ? DefaultTimeOutMSecs : expireTimeout; - - qint64 point = QDateTime::currentMSecsSinceEpoch() + interval; - m_pendingTimeoutEntities.insert(point, entity); - - if (m_lastTimeoutPoint > point) { - m_lastTimeoutPoint = point; - auto newInterval = m_lastTimeoutPoint - QDateTime::currentMSecsSinceEpoch(); - m_pendingTimeout->setInterval(newInterval); - m_pendingTimeout->start(); - } -} - void NotificationManager::updateEntityProcessed(qint64 id, uint reason) { auto entity = m_persistence->fetchEntity(id); @@ -546,7 +502,6 @@ void NotificationManager::updateEntityProcessed(const NotifyEntity &entity) Q_EMIT NotificationStateChanged(entity.id(), entity.processedType()); - removePendingEntity(entity); emitRecordCountChanged(); } @@ -707,69 +662,6 @@ void NotificationManager::initScreenLockedState() "Visible", this, SLOT(onScreenLockedChanged(bool))); } -void NotificationManager::onHandingPendingEntities() -{ - QList timeoutEntities; - - const auto current = QDateTime::currentMSecsSinceEpoch(); - for (auto iter = m_pendingTimeoutEntities.begin(); iter != m_pendingTimeoutEntities.end();) { - const auto point = iter.key(); - if (point > current) { - iter++; - continue; - } - - const auto entity = iter.value();; - timeoutEntities << entity; - iter = m_pendingTimeoutEntities.erase(iter); - } - - // update pendingTimeout to deal with m_pendingTimeoutEntities - if (!m_pendingTimeoutEntities.isEmpty()) { - auto points = m_pendingTimeoutEntities.keys(); - std::sort(points.begin(), points.end()); - // find last point to restart pendingTimeout - m_lastTimeoutPoint = points.first(); - auto newInterval = m_lastTimeoutPoint - current; - // let timer start in main thread - QMetaObject::invokeMethod(m_pendingTimeout, "start", Qt::QueuedConnection, Q_ARG(int, newInterval)); - } else { - // reset m_lastTimeoutPoint - m_lastTimeoutPoint = std::numeric_limits::max(); - } - - for (const auto &item : timeoutEntities) { - // Validate entity before processing timeout to prevent race conditions - if (!item.isValid()) { - qWarning(notifyLog) << "Skipping timeout processing for invalid entity id:" << item.id() << "appName:" << item.appName() - << "cTime:" << item.cTime(); - continue; - } - - if (item.id() == m_blockClosedId) { - qDebug(notifyLog) << "bubble id:" << item.bubbleId() << "entity id:" << item.id(); - m_pendingTimeoutEntities.insert(current, item); - continue; - } - - qDebug(notifyLog) << "Expired for the notification " << item.id() << item.appName(); - notificationClosed(item.id(), item.bubbleId(), NotifyEntity::Expired); - } -} - -void NotificationManager::removePendingEntity(const NotifyEntity &entity) -{ - for (auto iter = m_pendingTimeoutEntities.begin(); iter != m_pendingTimeoutEntities.end();) { - const auto item = iter.value(); - if (item == entity || (entity.isReplace() && item.bubbleId() == entity.bubbleId())) { - m_pendingTimeoutEntities.erase(iter); - onHandingPendingEntities(); - break; - } - ++iter; - } -} - void NotificationManager::onScreenLockedChanged(bool screenLocked) { m_screenLocked = screenLocked; diff --git a/panels/notification/server/notificationmanager.h b/panels/notification/server/notificationmanager.h index f2756669d..1d95b4530 100644 --- a/panels/notification/server/notificationmanager.h +++ b/panels/notification/server/notificationmanager.h @@ -7,7 +7,6 @@ #include #include -class QTimer; namespace notification { class NotifyEntity; @@ -68,14 +67,12 @@ public Q_SLOTS: void SetSystemInfo(uint configItem, const QVariant &value); QVariant GetSystemInfo(uint configItem); - void setBlockClosedId(qint64 id); private: bool isDoNotDisturb() const; bool recordNotification(NotifyEntity &entity); void tryPlayNotificationSound(const NotifyEntity &entity, const QString &appId, bool dndMode) const; void emitRecordCountChanged(); - void pushPendingEntity(const NotifyEntity &entity, int expireTimeout); void updateEntityProcessed(qint64 id, uint reason); void updateEntityProcessed(const NotifyEntity &entity); @@ -86,8 +83,6 @@ public Q_SLOTS: void initScreenLockedState(); private slots: - void onHandingPendingEntities(); - void removePendingEntity(const NotifyEntity &entity); void onScreenLockedChanged(bool); private: @@ -96,13 +91,9 @@ private slots: DataAccessor *m_persistence = nullptr; NotificationSetting *m_setting = nullptr; - QTimer *m_pendingTimeout = nullptr; - qint64 m_lastTimeoutPoint = std::numeric_limits::max(); - QMultiHash m_pendingTimeoutEntities; QStringList m_systemApps; QMap m_appNamesMap; int m_cleanupDays = 7; - qint64 m_blockClosedId = 0; }; } // notification diff --git a/panels/notification/server/notifyserverapplet.cpp b/panels/notification/server/notifyserverapplet.cpp index b4de43dfc..cd426cbf9 100644 --- a/panels/notification/server/notifyserverapplet.cpp +++ b/panels/notification/server/notifyserverapplet.cpp @@ -104,11 +104,6 @@ void NotifyServerApplet::removeExpiredNotifications() m_manager->removeExpiredNotifications(); } -void NotifyServerApplet::setBlockClosedId(qint64 id) -{ - m_manager->setBlockClosedId(id); -} - D_APPLET_CLASS(NotifyServerApplet) } diff --git a/panels/notification/server/notifyserverapplet.h b/panels/notification/server/notifyserverapplet.h index 20975e91d..ff8ea57e9 100644 --- a/panels/notification/server/notifyserverapplet.h +++ b/panels/notification/server/notifyserverapplet.h @@ -31,7 +31,6 @@ public Q_SLOTS: void removeNotifications(const QString &appName); void removeNotifications(); void removeExpiredNotifications(); - void setBlockClosedId(qint64 id); private: NotificationManager *m_manager = nullptr; diff --git a/tests/panels/notification/server/notifyserverapplet_test.cpp b/tests/panels/notification/server/notifyserverapplet_test.cpp index 9a0463167..64ba2b46d 100644 --- a/tests/panels/notification/server/notifyserverapplet_test.cpp +++ b/tests/panels/notification/server/notifyserverapplet_test.cpp @@ -34,7 +34,6 @@ class MockNotificationManager : public NotificationManager { MOCK_METHOD(void, removeNotifications, (const QString &appName)); MOCK_METHOD(void, removeNotifications, ()); MOCK_METHOD(void, removeExpiredNotifications, ()); - MOCK_METHOD(void, setBlockClosedId, (qint64 id)); }; // Test fixture for NotifyServerApplet @@ -244,17 +243,6 @@ TEST_F(NotifyServerAppletTest, RemoveExpiredNotificationsTest) { EXPECT_NO_THROW(applet->removeExpiredNotifications()); } -// Test setBlockClosedId -TEST_F(NotifyServerAppletTest, SetBlockClosedIdTest) { - // Initialize applet first - applet->init(); - - qint64 testId = 12345; - - // Test that setBlockClosedId doesn't crash - EXPECT_NO_THROW(applet->setBlockClosedId(testId)); -} - // Test notificationStateChanged signal TEST_F(NotifyServerAppletTest, NotificationStateChangedSignalTest) { // Initialize applet first @@ -315,16 +303,6 @@ TEST_F(NotifyServerAppletTest, NotificationClosedEdgeCasesTest) { EXPECT_NO_THROW(applet->notificationClosed(999999999, 999999, 3)); } -// Test edge cases for setBlockClosedId -TEST_F(NotifyServerAppletTest, SetBlockClosedIdEdgeCasesTest) { - applet->init(); - - // Test with various ID values - EXPECT_NO_THROW(applet->setBlockClosedId(0)); - EXPECT_NO_THROW(applet->setBlockClosedId(-1)); - EXPECT_NO_THROW(applet->setBlockClosedId(9223372036854775807LL)); // max qint64 -} - // Test that applet properly inherits from DApplet TEST_F(NotifyServerAppletTest, InheritanceTest) { EXPECT_TRUE(applet->inherits("ds::DApplet"));