#include #include "notificationprovider.h" #include "watch.h" #include "watchlet.h" #include "watchserver.h" using namespace sowatch; WatchServer::WatchServer(Watch* watch, QObject* parent) : QObject(parent), _watch(watch), _nextWatchletButton(-1), _oldNotificationThreshold(300), _notifications(new NotificationsModel(this)), _currentWatchlet(0), _currentWatchletActive(false), _currentWatchletIndex(-1), _syncTimeTimer(new QTimer(this)) { connect(_watch, SIGNAL(connected()), SLOT(handleWatchConnected())); connect(_watch, SIGNAL(disconnected()), SLOT(handleWatchDisconnected())); connect(_watch, SIGNAL(idling()), SLOT(handleWatchIdling())); connect(_watch, SIGNAL(buttonPressed(int)), SLOT(handleWatchButtonPress(int))); connect(_syncTimeTimer, SIGNAL(timeout()), SLOT(syncTime())); _syncTimeTimer->setSingleShot(true); _syncTimeTimer->setInterval(24 * 3600 * 1000); // Once a day } Watch* WatchServer::watch() { return _watch; } const Watch* WatchServer::watch() const { return _watch; } QString WatchServer::nextWatchletButton() const { if (_nextWatchletButton >= 0) { return _watch->buttons().at(_nextWatchletButton); } else { return QString(); } } void WatchServer::setNextWatchletButton(const QString& value) { if (value.isEmpty()) { _nextWatchletButton = -1; return; } _nextWatchletButton = _watch->buttons().indexOf(value); if (_nextWatchletButton < 0) { qWarning() << "Invalid watch button" << value; } } void WatchServer::addWatchlet(Watchlet *watchlet) { Q_ASSERT(watchlet); insertWatchlet(_watchlets.size(), watchlet); } void WatchServer::insertWatchlet(int position, Watchlet *watchlet) { Q_ASSERT(watchlet); Q_ASSERT(watchlet->watch() == _watch); const QString id = watchlet->id(); Q_ASSERT(!_watchletIds.contains(id)); setWatchletProperties(watchlet); _watchlets.insert(position, watchlet); _watchletIds[id] = watchlet; } void WatchServer::moveWatchlet(const Watchlet *watchlet, int to) { const QString id = watchlet->id(); int index = _watchlets.indexOf(const_cast(watchlet)); Q_ASSERT(watchlet->watch() == _watch); Q_ASSERT(_watchletIds.contains(id)); Q_ASSERT(index >= 0); _watchlets.move(index, to); } void WatchServer::removeWatchlet(const Watchlet *watchlet) { const QString id = watchlet->id(); Q_ASSERT(watchlet->watch() == _watch); Q_ASSERT(_watchletIds.contains(id)); if (_currentWatchlet == watchlet) { closeWatchlet(); } unsetWatchletProperties(const_cast(watchlet)); _watchlets.removeAll(const_cast(watchlet)); _watchletIds.remove(id); } void WatchServer::addProvider(NotificationProvider *provider) { connect(provider, SIGNAL(incomingNotification(Notification*)), this, SLOT(postNotification(Notification*))); } void WatchServer::removeProvider(const NotificationProvider *provider) { disconnect(provider, SIGNAL(incomingNotification(Notification*)), this, SLOT(postNotification(Notification*))); } const NotificationsModel * WatchServer::notifications() const { return _notifications; } void WatchServer::postNotification(Notification *notification) { const Notification::Type type = notification->type(); // Add notification to model _notifications->add(notification); _notificationCounts[notification] = notification->count(); connect(notification, SIGNAL(changed()), SLOT(handleNotificationChanged())); connect(notification, SIGNAL(dismissed()), SLOT(handleNotificationDismissed())); connect(notification, SIGNAL(destroyed()), SLOT(handleNotificationDestroyed())); qDebug() << "notification received" << notification->title() << "(" << notification->count() << ")"; _watch->updateNotificationCount(type, getNotificationCount(type)); if (type == Notification::WeatherNotification) { // Weather notifications, we handle differently. WeatherNotification* weather = static_cast(notification); _weather = weather; _watch->updateWeather(weather); return; // And do not display it the usual way } QDateTime oldThreshold = QDateTime::currentDateTime().addSecs(-_oldNotificationThreshold); if (notification->dateTime() < oldThreshold) { return; // Do not care about notifications that old... } if (_pendingNotifications.isEmpty()) { _pendingNotifications.enqueue(notification); nextNotification(); } else if (type == Notification::CallNotification) { // Oops, priority!!!! _pendingNotifications.prepend(notification); nextNotification(); } else { _pendingNotifications.enqueue(notification); } } void WatchServer::nextNotification() { if (!_watch->isConnected()) return; if (!_pendingNotifications.empty()) { Notification *n = _pendingNotifications.head(); if (_currentWatchlet && _currentWatchletActive) { deactivateCurrentWatchlet(); } _watch->displayNotification(n); } else if (_currentWatchlet) { reactivateCurrentWatchlet(); } else { goToIdle(); } } void WatchServer::runWatchlet(Watchlet *watchlet) { Q_ASSERT(watchlet->watch() == _watch); if (_currentWatchlet && _currentWatchletActive) { deactivateCurrentWatchlet(); } _currentWatchlet = watchlet; if (_watch->isConnected()) { reactivateCurrentWatchlet(); } } void WatchServer::runWatchlet(const QString& id) { Q_ASSERT(_watchletIds.contains(id)); runWatchlet(_watchletIds[id]); } void WatchServer::closeWatchlet() { if (_currentWatchlet) { if (_currentWatchletActive) { deactivateCurrentWatchlet(); } _currentWatchlet = 0; if (_watch->isConnected() && _pendingNotifications.empty()) { goToIdle(); } } } void WatchServer::deactivateCurrentWatchlet() { Q_ASSERT(_currentWatchlet != 0); Q_ASSERT(_currentWatchletActive); qDebug() << "deactivating watchlet" << _currentWatchlet->id(); _currentWatchlet->deactivate(); _currentWatchletActive = false; } void WatchServer::reactivateCurrentWatchlet() { Q_ASSERT(_currentWatchlet != 0); Q_ASSERT(!_currentWatchletActive); qDebug() << "activating watchlet" << _currentWatchlet->id(); _watch->displayApplication(); _currentWatchlet->activate(); _currentWatchletActive = true; } void WatchServer::nextWatchlet() { qDebug() << "current watchlet index" << _currentWatchletIndex; _currentWatchletIndex++; if (_currentWatchletIndex >= _watchlets.size() || _currentWatchletIndex < 0) { _currentWatchletIndex = -1; closeWatchlet(); } else { Watchlet* watchlet = _watchlets.at(_currentWatchletIndex); runWatchlet(watchlet); } } void WatchServer::syncTime() { if (_watch->isConnected()) { qDebug() << "syncing watch time"; _watch->setDateTime(QDateTime::currentDateTime()); _syncTimeTimer->start(); } } uint WatchServer::getNotificationCount(Notification::Type type) { return _notifications->fullCountByType(type); } void WatchServer::removeNotification(Notification::Type type, Notification *n) { // Warning: This function might be called with n being deleted. _notifications->remove(type, n); _notificationCounts.remove(n); _watch->updateNotificationCount(type, getNotificationCount(type)); if (!_pendingNotifications.isEmpty() && _pendingNotifications.head() == n) { qDebug() << "removing top notification"; _pendingNotifications.removeAll(n); nextNotification(); } else { _pendingNotifications.removeAll(n); } if (type == Notification::WeatherNotification) { WeatherNotification* w = static_cast(n); if (_weather == w) { _weather = 0; } } // No longer interested in this notification disconnect(n, 0, this, 0); } void WatchServer::setWatchletProperties(Watchlet *watchlet) { Q_ASSERT(watchlet->watch() == _watch); watchlet->setNotificationsModel(_notifications); } void WatchServer::unsetWatchletProperties(Watchlet *watchlet) { Q_ASSERT(watchlet->watch() == _watch); watchlet->setNotificationsModel(0); } void WatchServer::goToIdle() { Q_ASSERT(!_currentWatchletActive); _watch->displayIdleScreen(); } void WatchServer::handleWatchConnected() { syncTime(); if (!_pendingNotifications.isEmpty()) { nextNotification(); } else if (_currentWatchlet) { reactivateCurrentWatchlet(); } else { goToIdle(); } emit watchConnected(); } void WatchServer::handleWatchDisconnected() { _syncTimeTimer->stop(); if (_currentWatchlet && _currentWatchletActive) { deactivateCurrentWatchlet(); } _pendingNotifications.clear(); emit watchDisconnected(); } void WatchServer::handleWatchIdling() { qDebug() << "watch idling"; if (!_pendingNotifications.empty()) { _pendingNotifications.dequeue(); nextNotification(); } } void WatchServer::handleWatchButtonPress(int button) { if (button == _nextWatchletButton) { qDebug() << "next watchlet button pressed"; if (_pendingNotifications.empty()) { // No notifications: either app or idle mode. nextWatchlet(); } else { // Skip to next notification if any _pendingNotifications.dequeue(); nextNotification(); } } } void WatchServer::handleNotificationChanged() { QObject *obj = sender(); if (obj) { Notification* n = static_cast(obj); const Notification::Type type = n->type(); const uint lastCount = _notificationCounts[n]; _notificationCounts[n] = n->count(); qDebug() << "notification changed" << n->title() << "(" << n->count() << ")"; _watch->updateNotificationCount(type, getNotificationCount(type)); if (type == Notification::WeatherNotification) { WeatherNotification* w = static_cast(n); if (!_weather || _weather->dateTime() < w->dateTime()) { // Prefer showing the most recent data _weather = w; } if (_weather == w) { // This is the weather notification we are currently displaying on the watch // Therefore, update the displayed information _watch->updateWeather(w); } return; // Do not display it the usual way } if (!_pendingNotifications.isEmpty() && _pendingNotifications.head() == n) { // This is the notification that is being currently signaled on the watch // Therefore, show it again no matter what. nextNotification(); } else if (n->count() > lastCount) { // This notification now contains an additional "item"; redisplay it. if (_pendingNotifications.isEmpty()) { _pendingNotifications.enqueue(n); nextNotification(); } else { _pendingNotifications.enqueue(n); } } } } void WatchServer::handleNotificationDismissed() { QObject *obj = sender(); if (obj) { Notification* n = static_cast(obj); const Notification::Type type = n->type(); qDebug() << "notification dismissed" << n->title() << "(" << n->count() << ")"; removeNotification(type, n); } } void WatchServer::handleNotificationDestroyed() { QObject *obj = sender(); if (obj) { Notification* n = static_cast(obj); // Cannot call any methods of n; it is a dangling pointer now. if (_notificationCounts.contains(n)) { qWarning() << "Notification destroyed without being dismissed!"; Notification::Type type = _notifications->getTypeOfDeletedNotification(n); removeNotification(type, n); } } }