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..51d6f3e96 100644 --- a/panels/notification/bubble/bubbleitem.cpp +++ b/panels/notification/bubble/bubbleitem.cpp @@ -91,6 +91,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..463fedda5 100644 --- a/panels/notification/bubble/bubbleitem.h +++ b/panels/notification/bubble/bubbleitem.h @@ -32,6 +32,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..fb338b7eb 100644 --- a/panels/notification/bubble/bubblemodel.cpp +++ b/panels/notification/bubble/bubblemodel.cpp @@ -20,6 +20,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 +43,9 @@ BubbleModel::BubbleModel(QObject *parent) m_processPendingTimer->start(); } }); + connect(&m_expireTimer, &ExpireTimer::expired, this, [this](qint64 id, const QVariant &data) { + Q_EMIT bubbleExpired(id, data.toUInt()); + }); connect(NotifySetting::instance(), &NotifySetting::contentRowCountChanged, this, &BubbleModel::updateContentRowCount); connect(NotifySetting::instance(), &NotifySetting::bubbleCountChanged, this, &BubbleModel::updateBubbleCount); @@ -82,6 +87,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. + m_expireTimer.start(bubble->id(), effectiveTimeout(bubble->urgency(), bubble->timeout()), bubble->bubbleId()); } bool BubbleModel::isReplaceBubble(const BubbleItem *bubble) const @@ -95,9 +104,17 @@ BubbleItem *BubbleModel::replaceBubble(BubbleItem *bubble) const auto replaceIndex = replaceBubbleIndex(bubble); const auto oldBubble = m_bubbles[replaceIndex]; + m_expireTimer.stop(oldBubble->id()); + m_bubbles.replace(replaceIndex, bubble); Q_EMIT dataChanged(index(replaceIndex), index(replaceIndex)); + m_expireTimer.start(bubble->id(), effectiveTimeout(bubble->urgency(), bubble->timeout()), bubble->bubbleId()); + + // If the replaced bubble was the hovered one, keep blocking the new one. + if (m_blockedId == oldBubble->id()) + m_expireTimer.pause(bubble->id()); + return oldBubble; } @@ -109,6 +126,9 @@ void BubbleModel::clear() qDeleteAll(m_pendingBubbles); m_pendingBubbles.clear(); + m_blockedId = NotifyEntity::InvalidId; + m_expireTimer.clear(); + if (m_bubbles.count() <= 0) return; beginResetModel(); @@ -129,11 +149,12 @@ void BubbleModel::remove(int index) if (index < 0 || index >= m_bubbles.size()) return; - beginRemoveRows(QModelIndex(), index, index); auto bubble = m_bubbles.takeAt(index); + m_expireTimer.stop(bubble->id()); + + beginRemoveRows(QModelIndex(), index, index); bubble->deleteLater(); endRemoveRows(); - } void BubbleModel::remove(const BubbleItem *bubble) @@ -298,4 +319,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) + m_expireTimer.resume(m_blockedId, BlockItemTimeout); + + m_blockedId = id; + + if (id != NotifyEntity::InvalidId) + m_expireTimer.pause(id); } + +} // notification diff --git a/panels/notification/bubble/bubblemodel.h b/panels/notification/bubble/bubblemodel.h index b9b8f6203..dc18fdaad 100644 --- a/panels/notification/bubble/bubblemodel.h +++ b/panels/notification/bubble/bubblemodel.h @@ -6,8 +6,10 @@ #include "dsglobal.h" #include "notifyentity.h" +#include "expiretimer.h" #include +#include #include class QTimer; @@ -38,6 +40,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); @@ -51,6 +57,10 @@ class BubbleModel : public QAbstractListModel 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; int rowCount(const QModelIndex &parent) const override; @@ -68,11 +78,12 @@ class BubbleModel : public QAbstractListModel void updateBubbleTimeTip(); void updateContentRowCount(int rowCount); -private: QTimer *m_updateTimeTipTimer = nullptr; QTimer *m_processPendingTimer = nullptr; + ExpireTimer m_expireTimer; 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..f93fd7c4f 100644 --- a/panels/notification/center/notifystagingmodel.cpp +++ b/panels/notification/center/notifystagingmodel.cpp @@ -25,12 +25,18 @@ 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(&m_expireTimer, &ExpireTimer::expired, this, [this](qint64 id, const QVariant &) { + closeNotify(id, NotifyEntity::Expired); + }); } void NotifyStagingModel::close() { qDebug(notifyLog) << "close"; + m_expireTimer.clear(); + beginResetModel(); qDeleteAll(m_appNotifies); m_appNotifies.clear(); @@ -63,6 +69,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. + m_expireTimer.start(entity.id(), effectiveTimeout(entity.urgency(), entity.timeout())); + if (m_refreshTimer < 0) { m_refreshTimer = startTimer(std::chrono::milliseconds(1000)); } @@ -90,6 +100,8 @@ void NotifyStagingModel::remove(qint64 id) { qDebug(notifyLog) << "Remove notify by id" << id; + m_expireTimer.stop(id); + int row = -1; for (int i = 0; i < m_appNotifies.size(); i++) { auto item = m_appNotifies[i]; @@ -146,6 +158,7 @@ void NotifyStagingModel::remove(qint64 id) auto notify = new AppNotifyItem(newEntity); m_appNotifies.insert(insertedIndex, notify); endInsertRows(); + m_expireTimer.start(newEntity.id(), effectiveTimeout(newEntity.urgency(), newEntity.timeout())); } } updateOverlapCount(entities.size()); @@ -155,6 +168,8 @@ void NotifyStagingModel::open() { qDebug(notifyLog) << "Open staging model"; + m_expireTimer.clear(); + auto entities = m_accessor->fetchEntities(DataAccessor::AllApp(), NotifyEntity::NotProcessed, BubbleMaxCount + OverlayMaxCount); qDebug(notifyLog) << "Fetched staging size" << entities.size(); @@ -172,6 +187,9 @@ void NotifyStagingModel::open() auto notify = new AppNotifyItem(entities.at(i)); m_appNotifies << notify; } + for (const auto &entity : entities) { + m_expireTimer.start(entity.id(), effectiveTimeout(entity.urgency(), entity.timeout())); + } updateOverlapCount(entities.size()); endResetModel(); @@ -251,7 +269,9 @@ 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()) { + m_expireTimer.stop(entity.bubbleId()); item->setEntity(entity); + m_expireTimer.start(entity.id(), effectiveTimeout(entity.urgency(), entity.timeout())); const auto index = this->index(i, 0, {}); dataChanged(index, index); break; @@ -333,9 +353,9 @@ void NotifyStagingModel::updateContentRowCount(int rowCount) return; m_contentRowCount = rowCount; - if (!m_appNotifies.isEmpty()) { dataChanged(index(0), index(m_appNotifies.size() - 1), {NotifyRole::NotifyContentRowCount}); } } + } diff --git a/panels/notification/center/notifystagingmodel.h b/panels/notification/center/notifystagingmodel.h index 7a71112fb..c5281c801 100644 --- a/panels/notification/center/notifystagingmodel.h +++ b/panels/notification/center/notifystagingmodel.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2024-2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,6 +10,7 @@ #include "notifyitem.h" #include "dataaccessor.h" +#include "expiretimer.h" namespace notifycenter { /** @@ -73,5 +74,9 @@ private slots: DataAccessor *m_accessor = nullptr; int m_overlapCount = 0; int m_contentRowCount = 6; + // Notifications shown in the staging area (notification center) need their + // own expire timeout because the bubble panel is disabled while the center + // window is open, so the bubble-side timers are not running. + ExpireTimer m_expireTimer; }; } diff --git a/panels/notification/common/expiretimer.cpp b/panels/notification/common/expiretimer.cpp new file mode 100644 index 000000000..d9f0bc917 --- /dev/null +++ b/panels/notification/common/expiretimer.cpp @@ -0,0 +1,130 @@ +// 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; + +ExpireTimer::ExpireTimer(QObject *parent) + : QObject(parent) + , m_timer(new QTimer(this)) +{ + m_timer->setSingleShot(true); + connect(m_timer, &QTimer::timeout, this, &ExpireTimer::onTimeout); +} + +void ExpireTimer::start(qint64 key, int interval, const QVariant &data) +{ + if (interval <= 0) { + // Never expire: cancel any pending or paused countdown. + m_deadlines.remove(key); + m_paused.remove(key); + m_data.remove(key); + schedule(); + return; + } + + m_paused.remove(key); + m_data.insert(key, data); + 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); + m_data.remove(key); + if (removed) + schedule(); +} + +void ExpireTimer::clear() +{ + m_deadlines.clear(); + m_paused.clear(); + m_data.clear(); + m_timer->stop(); +} + +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) { + const auto data = m_data.take(key); + Q_EMIT expired(key, data); + } + } + + schedule(); +} + +int effectiveTimeout(int urgency, int expireTimeout) +{ + // Critical notifications never expire. + if (urgency == NotifyEntity::Critical) + return 0; + + if (expireTimeout == 0) + return 0; + + return expireTimeout == -1 ? DefaultTimeoutMSecs : expireTimeout; +} + +} diff --git a/panels/notification/common/expiretimer.h b/panels/notification/common/expiretimer.h new file mode 100644 index 000000000..f40b635d5 --- /dev/null +++ b/panels/notification/common/expiretimer.h @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class QTimer; + +namespace notification { + +/** + * @brief Tracks expire deadlines for a set of keys using one shared QTimer. + * + * Each key keeps its own deadline, the caller data associated with it and, while + * blocked, its remaining time. The nearest deadline drives a single-shot QTimer; + * when a deadline passes, expired() is emitted once for that key and the key is + * forgotten. All bookkeeping lives inside this class, so callers only describe + * what to do (start, pause, resume, stop, clear) and never have to compose + * lower-level operations. No QTimer is allocated per key and nothing leaks when + * a key expires or is stopped. + */ +class ExpireTimer : public QObject +{ + Q_OBJECT +public: + explicit ExpireTimer(QObject *parent = nullptr); + + // Starts a countdown for key. A non-positive interval cancels any pending + // countdown, i.e. the key never expires. data is stored with the key and + // handed back by expired() when the countdown finishes. + void start(qint64 key, int interval, const QVariant &data = QVariant()); + // 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); + // Forgets every key and stops the timer. + void clear(); + +Q_SIGNALS: + // Emitted once when the deadline of key passes, with the data passed to start(). + void expired(qint64 key, const QVariant &data); + +private: + void schedule(); + void onTimeout(); + + QTimer *m_timer = nullptr; + QHash m_deadlines; + QHash m_paused; + QHash m_data; +}; + +// 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. +int effectiveTimeout(int urgency, int expireTimeout); + +} 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"));