diff options
| author | Javier S. Pedro <maemo@javispedro.com> | 2013-06-15 20:35:33 +0200 | 
|---|---|---|
| committer | Javier S. Pedro <maemo@javispedro.com> | 2013-06-15 20:35:33 +0200 | 
| commit | 378611f629abc146eaf0b13301f119d826edb86b (patch) | |
| tree | 3f2447b75bfe155bbb013b07e08f2922d23a0f3a /liveview | |
| parent | a3797790c7da7a5c88005735619dc56a96264930 (diff) | |
| download | sowatch-378611f629abc146eaf0b13301f119d826edb86b.tar.gz sowatch-378611f629abc146eaf0b13301f119d826edb86b.zip | |
add some support for notifications in liveview
Diffstat (limited to 'liveview')
| -rw-r--r-- | liveview/liveview.cpp | 339 | ||||
| -rw-r--r-- | liveview/liveview.h | 48 | ||||
| -rw-r--r-- | liveview/liveviewpaintengine.cpp | 6 | 
3 files changed, 327 insertions, 66 deletions
| diff --git a/liveview/liveview.cpp b/liveview/liveview.cpp index f293bd9..450e802 100644 --- a/liveview/liveview.cpp +++ b/liveview/liveview.cpp @@ -8,21 +8,24 @@ QTM_USE_NAMESPACE  #define PROTOCOL_DEBUG 1 +const int LiveView::MaxBitmapSize = 64; +QMap<LiveView::MessageType, LiveView::MessageType> LiveView::_ackMap; +QList<LiveView::RootMenuNotificationItem> LiveView::_rootNotificationItems; +  LiveView::LiveView(ConfigKey* settings, QObject* parent) :  	BluetoothWatch(QBluetoothAddress(settings->value("address").toString()), parent),  	_settings(settings->getSubkey(QString(), this)), -    _watchlets(0), +    _watchlets(0), _notifications(0),      _24hMode(settings->value("24h-mode", false).toBool()),      _screenWidth(0), _screenHeight(0),      _mode(RootMenuMode),      _paintEngine(0),      _rootMenuFirstWatchlet(0), -    _sendTimer(new QTimer(this)) +    _waitingForAck(NoMessage)  { -	_sendTimer->setInterval(DelayBetweenMessages); -	connect(_sendTimer, SIGNAL(timeout()), SLOT(handleSendTimerTick())); - +	initializeAckMap();  	_buttons << "Select" << "Up" << "Down" << "Left" << "Right"; +	initializeRootNotificationItems();  }  LiveView::~LiveView() @@ -82,7 +85,7 @@ bool LiveView::busy() const  {  	return !_connected ||  			_socket->state() != QBluetoothSocket::ConnectedState || -			_sendingMsgs.size() > 20; +			_sendingMsgs.size() > 1;  }  void LiveView::setDateTime(const QDateTime& dateTime) @@ -126,6 +129,7 @@ bool LiveView::charging() const  void LiveView::displayIdleScreen()  { +	qDebug() << "LiveView display idle screen (cur mode=" << _mode << ")";  	if (_mode != RootMenuMode) {  		_mode = RootMenuMode;  		refreshMenu(); @@ -134,7 +138,7 @@ void LiveView::displayIdleScreen()  void LiveView::displayNotification(Notification *notification)  { - +	qDebug() << "LiveView display notification" << notification->title();  }  void LiveView::displayApplication() @@ -160,6 +164,19 @@ void LiveView::setWatchletsModel(WatchletsModel *model)  	}  } +void LiveView::setNotificationsModel(NotificationsModel *model) +{ +	qDebug() << Q_FUNC_INFO; +	if (_notifications) { +		disconnect(_notifications, 0, this, 0); +	} +	_notifications = model; +	if (_notifications) { +		connect(_notifications, SIGNAL(modelChanged()), SLOT(handleNotificationsChanged())); +		handleNotificationsChanged(); +	} +} +  QImage* LiveView::image()  {  	return &_image; @@ -167,11 +184,25 @@ QImage* LiveView::image()  void LiveView::renderImage(int x, int y, const QImage &image)  { +	const QSize image_size = image.size();  	qDebug() << "Rendering image at" << x << 'x' << y << "of size" << image.size(); -	QByteArray data = encodeImage(image.copy(0,0,64,64)); -	if (!data.isEmpty()) { -		displayBitmap(x, y, data); +	QByteArray data; +	if (image.size().isEmpty()) { +		return; // Empty image  	} + +	if (image_size.width() > MaxBitmapSize || image_size.height() > MaxBitmapSize) { +		const QRect new_size(0, 0, +		                     qMin(MaxBitmapSize, image_size.width()), +		                     qMin(MaxBitmapSize, image_size.height())); +		data = encodeImage(image.copy(new_size)); +	} else { +		data = encodeImage(image); +	} + +	Q_ASSERT(!data.isEmpty()); + +	displayBitmap(x, y, data);  }  void LiveView::clear() @@ -179,9 +210,53 @@ void LiveView::clear()  	displayClear();  } +void LiveView::initializeAckMap() +{ +	if (_ackMap.empty()) { +		_ackMap[GetDisplayProperties] = GetDisplayPropertiesResponse; +		_ackMap[DeviceStatusChange] = DeviceStatusChangeResponse; +		_ackMap[DisplayBitmap] = DisplayBitmapResponse; +		_ackMap[DisplayClear] = DisplayClearResponse; +		//_ackMap[SetMenuSize] = SetMenuSizeResponse; // fw does not send this, for some reason. +		_ackMap[EnableLed] = EnableLedResponse; +	} +} + +LiveView::MessageType LiveView::ackForMessage(MessageType type) +{ +	QMap<MessageType, MessageType>::const_iterator i = _ackMap.constFind(type); +	if (i != _ackMap.constEnd()) { +		return i.value(); +	} +	return NoMessage; +} + +void LiveView::initializeRootNotificationItems() +{ +	if (_rootNotificationItems.empty()) { +		RootMenuNotificationItem item; +		item.icon = encodeImage(QUrl::fromLocalFile(SOWATCH_RESOURCES_DIR "/liveview/graphics/menu_missed_calls.png")); +		item.title = tr("Missed calls"); +		item.notificationTypes.append(Notification::MissedCallNotification); +		_rootNotificationItems.append(item); + +		item.icon = encodeImage(QUrl::fromLocalFile(SOWATCH_RESOURCES_DIR "/liveview/graphics/menu_notifications.png")); +		item.title = tr("Events"); +		// All other notifications +		item.notificationTypes.append(Notification::OtherNotification); +		item.notificationTypes.append(Notification::SmsNotification); +		item.notificationTypes.append(Notification::MmsNotification); +		item.notificationTypes.append(Notification::ImNotification); +		item.notificationTypes.append(Notification::EmailNotification); +		item.notificationTypes.append(Notification::CalendarNotification); +		_rootNotificationItems.append(item); +	} +} +  void LiveView::setupBluetoothWatch()  {  	_mode = RootMenuMode; +	_waitingForAck = NoMessage;  	connect(_socket, SIGNAL(readyRead()), SLOT(handleDataReceived()));  	updateDisplayProperties(); @@ -190,10 +265,38 @@ void LiveView::setupBluetoothWatch()  void LiveView::desetupBluetoothWatch()  { -	_sendTimer->stop();  	_sendingMsgs.clear();  } +void LiveView::recreateNotificationsMenu() +{ +	// Erase all current notifications from the menu +	QList<RootMenuItem>::iterator it = _rootMenu.begin(); +	it += _rootMenuFirstWatchlet; +	_rootMenu.erase(_rootMenu.begin(), it); + +	_rootMenuFirstWatchlet = 0; + +	if (_notifications) { +		foreach (const RootMenuNotificationItem& nitem, _rootNotificationItems) { +			int count = 0; +			foreach (Notification::Type type, nitem.notificationTypes) { +				count += _notifications->countByType(type); +			} +			if (count > 0) { +				RootMenuItem item; +				item.type = MenuNotificationList; +				item.title = nitem.title; +				item.icon = nitem.icon; +				item.unread = count; +				item.notificationItem = &nitem; +				_rootMenu.insert(_rootMenuFirstWatchlet, item); +				_rootMenuFirstWatchlet++; +			} +		} +	} +} +  void LiveView::recreateWatchletsMenu()  {  	// Erase all current watchlets frm the menu @@ -208,6 +311,9 @@ void LiveView::recreateWatchletsMenu()  			QModelIndex index = _watchlets->index(i);  			item.type = MenuOther;  			item.title = _watchlets->data(index, WatchletsModel::TitleRole).toString(); +			if (item.title.isEmpty()) { +				qWarning() << "Watchlet" << _watchlets->at(i)->id() << "without title"; +			}  			item.icon = encodeImage(_watchlets->data(index, WatchletsModel::IconRole).toUrl());  			item.unread = 0;  			item.watchletId = _watchlets->at(i)->id(); @@ -223,7 +329,7 @@ void LiveView::refreshMenu()  	}  } -QByteArray LiveView::encodeImage(const QImage& image) const +QByteArray LiveView::encodeImage(const QImage& image)  {  	QBuffer buffer;  	buffer.open(QIODevice::WriteOnly); @@ -235,7 +341,7 @@ QByteArray LiveView::encodeImage(const QImage& image) const  	}  } -QByteArray LiveView::encodeImage(const QUrl& url) const +QByteArray LiveView::encodeImage(const QUrl& url)  {  	if (url.encodedPath().endsWith(".png")) {  		// Just load the image @@ -257,8 +363,10 @@ void LiveView::send(const Message &msg)  {  	Q_ASSERT(_connected);  	_sendingMsgs.enqueue(msg); -	if (!_sendTimer->isActive()) { -		_sendTimer->start(); +	if (_waitingForAck == NoMessage) { +		sendMessageFromQueue(); +	} else { +		qDebug() << "Enqueing message while waiting for ack" << _waitingForAck;  	}  } @@ -323,6 +431,36 @@ void LiveView::sendMenuItem(unsigned char id, MenuItemType type, unsigned short  	send(Message(MenuItemResponse, data));  } +void LiveView::sendNotification(unsigned short id, unsigned short unread, unsigned short count, const QString &date, const QString &header, const QString &body, const QByteArray &image) +{ +	QByteArray data; +	QDataStream ds(&data, QIODevice::WriteOnly); + +	ds << quint8(0); // Unknown +	ds << quint16(count); +	ds << quint16(unread); +	ds << quint16(id); +	ds << quint8(0) << quint8(0); // Unknown + +	QByteArray str = date.toLatin1(); +	ds << quint16(str.length()); +	ds.writeRawData(str.constData(), str.length()); + +	str = header.toLatin1(); +	ds << quint16(str.length()); +	ds.writeRawData(str.constData(), str.length()); + +	str = body.toLatin1(); +	ds << quint16(str.length()); +	ds.writeRawData(str.constData(), str.length()); + +	ds << quint8(0) << quint8(0) << quint8(0); // Unknown +	ds << quint16(image.length()); +	ds.writeRawData(image.constData(), image.size()); + +	send(Message(NotificationResponse, data)); +} +  void LiveView::enableLed(const QColor& color, unsigned short delay, unsigned short time)  {  	QByteArray data; @@ -345,6 +483,11 @@ void LiveView::enableLed(const QColor& color, unsigned short delay, unsigned sho  void LiveView::handleMessage(const Message &msg)  {  	send(Message(Ack, QByteArray(1, msg.type))); +	if (msg.type == _waitingForAck) { +		qDebug() << "Got ack to" << _waitingForAck; +		_waitingForAck = NoMessage; +		sendMessageFromQueue(); +	}  	switch (msg.type) {  	case DeviceStatusChange:  		handleDeviceStatusChange(msg); @@ -400,8 +543,79 @@ void LiveView::handleMenuItemRequest(const Message &msg)  void LiveView::handleNotificationRequest(const Message &msg)  { -	// TODO -	sendResponse(NotificationResponse, ResponseError); // TODO Crashes the watch +	if (msg.data.size() < 4) { +		// Packet too small +		sendResponse(NotificationResponse, ResponseError); +		return; +	} + +	int menu_id = static_cast<unsigned char>(msg.data[0]); +	NotificationAction action = static_cast<NotificationAction>(msg.data[1]); +	unsigned int max_size = static_cast<unsigned char>(msg.data[2]) << 8; +	max_size |= static_cast<unsigned char>(msg.data[3]); +	qDebug() << "notification rq" << action << menu_id << "(" << max_size << ")"; + +	if (menu_id >= _rootMenu.size()) { +		sendNotification(0, 0, 0, "", "", "", QByteArray()); +		return; +	} + +	const RootMenuNotificationItem *notification_item = _rootMenu[menu_id].notificationItem; +	if (!notification_item) { +		sendNotification(0, 0, 0, "", "", "", QByteArray()); +		return; +	} + +	const QList<Notification::Type> ¬ification_types = notification_item->notificationTypes; +	const int num_notifications = _notifications->size(notification_types); + +	if (_mode != NotificationListMode) { +		_mode = NotificationListMode; +		_curNotificationIndex = 0; +	} + +	switch (action) { +	case NotificationShowFirst: +		_curNotificationIndex = 0; +		break; +	case NotificationShowLast: +		_curNotificationIndex = num_notifications - 1; +		break; +	case NotificationShowPrev: +		if (_curNotificationIndex > 0) { +			_curNotificationIndex--; +		} else { +			_curNotificationIndex = 0; +		} +		break; +	case NotificationShowNext: +		if (_curNotificationIndex < num_notifications - 1) { +			_curNotificationIndex++; +		} else { +			_curNotificationIndex = num_notifications - 1; +		} +		break; +	case NotificationShowCurrent: +		if (_curNotificationIndex >= num_notifications - 1 || +		        _curNotificationIndex < 0) { +			_curNotificationIndex = 0; +		} +		break; +	} + +	const Notification *notification = _notifications->at(notification_types, +	                                                      _curNotificationIndex); +	if (!notification) { +		sendNotification(_curNotificationIndex, num_notifications, num_notifications, +		                 "", "", "", QByteArray()); +		return; +	} + +	sendNotification(_curNotificationIndex, num_notifications, num_notifications, +	                 notification->dateTime().toString(Qt::SystemLocaleShortDate), +	                 notification->title(), +	                 notification->body(), +	                 QByteArray());  }  void LiveView::handleNavigation(const Message &msg) @@ -438,6 +652,10 @@ void LiveView::handleNavigation(const Message &msg)  			sendResponse(NavigationResponse, ResponseCancel);  			emit closeWatchledRequested();  			return; +		} else if (_mode == NotificationListMode) { +			sendResponse(NavigationResponse, ResponseCancel); +			_mode = RootMenuMode; +			return;  		}  		break;  	case SelectMenu: @@ -446,7 +664,7 @@ void LiveView::handleNavigation(const Message &msg)  			switch(_rootMenu[item_id].type) {  			case MenuNotificationList: -				Q_ASSERT(false); // TODO +				// This should not happen!  				break;  			case MenuOther:  				emit watchletRequested(_rootMenu[item_id].watchletId); @@ -467,9 +685,12 @@ void LiveView::handleNavigation(const Message &msg)  void LiveView::handleMenuItemsRequest(const Message &msg)  {  	qDebug() << "Sending menu items"; +	if (_mode == NotificationListMode) { +		_mode = RootMenuMode; // Switch to the root menu +	}  	if (_mode == RootMenuMode) {  		for (int i = 0; i < _rootMenu.size(); i++) { -			qDebug() << "Sending one menu item"; +			qDebug() << "Sending one menu item" << _rootMenu[i].title;  			sendMenuItem(i, _rootMenu[i].type, _rootMenu[i].unread,  			                _rootMenu[i].title, _rootMenu[i].icon);  		} @@ -522,6 +743,43 @@ void LiveView::handleSoftwareVersion(const Message &msg)  	         << QString::fromAscii(msg.data.constData(), msg.data.size());  } + +void LiveView::sendMessageFromQueue() +{ +	static const int HEADER_SIZE = 6; +	Q_ASSERT(_waitingForAck == NoMessage); + +	// If there are packets to be sent... +	while (!_sendingMsgs.empty()) { +		// Send a message to the watch +		Message msg = _sendingMsgs.dequeue(); +		const quint32 data_size = msg.data.size(); +		QByteArray packet; + +		Q_ASSERT(_connected && _socket); + +		packet.resize(HEADER_SIZE + data_size); +		packet[0] = msg.type; +		packet[1] = HEADER_SIZE - 2; +		packet[2] = (data_size & 0xFF000000U) >> 24; +		packet[3] = (data_size & 0x00FF0000U) >> 16; +		packet[4] = (data_size & 0x0000FF00U) >>  8; +		packet[5] = (data_size & 0x000000FFU); +		packet.replace(HEADER_SIZE, data_size, msg.data); + +#if PROTOCOL_DEBUG +		qDebug() << "sending" << msg.type << packet.mid(6, 24).toHex(); +#endif + +		_socket->write(packet); + +		_waitingForAck = ackForMessage(msg.type); +		if (_waitingForAck != NoMessage) { +			break; // Wait for that ack before sending more messages. +		} +	} +} +  void LiveView::handleDataReceived()  {  #pragma pack(push) @@ -597,48 +855,17 @@ void LiveView::handleDataReceived()  	} while (_socket->bytesAvailable() > 0);  } -void LiveView::handleSendTimerTick() +void LiveView::handleWatchletsChanged()  { -	static const int HEADER_SIZE = 6; - -	// If there are packets to be sent... -	if (!_sendingMsgs.empty()) { -		// Send a message to the watch -		Message msg = _sendingMsgs.dequeue(); -		const quint32 data_size = msg.data.size(); -		QByteArray packet; - -		Q_ASSERT(_connected && _socket); - -		packet.resize(HEADER_SIZE + data_size); -		packet[0] = msg.type; -		packet[1] = HEADER_SIZE - 2; -		packet[2] = (data_size & 0xFF000000U) >> 24; -		packet[3] = (data_size & 0x00FF0000U) >> 16; -		packet[4] = (data_size & 0x0000FF00U) >>  8; -		packet[5] = (data_size & 0x000000FFU); -		packet.replace(HEADER_SIZE, data_size, msg.data); - -#if PROTOCOL_DEBUG -		if (packet.size() < 20) { -			qDebug() << "sending" << packet.toHex(); -		} else { -			qDebug() << "sending" << packet.left(20).toHex() << "..."; -		} -#endif - -		_socket->write(packet); -	} -	// If we just finished sending all packets... -	if (_sendingMsgs.empty()) { -		// Stop the send timer to save battery -		_sendTimer->stop(); +	recreateWatchletsMenu(); +	if (_connected) { +		refreshMenu();  	}  } -void LiveView::handleWatchletsChanged() +void LiveView::handleNotificationsChanged()  { -	recreateWatchletsMenu(); +	recreateNotificationsMenu();  	if (_connected) {  		refreshMenu();  	} diff --git a/liveview/liveview.h b/liveview/liveview.h index aba5b32..b85a574 100644 --- a/liveview/liveview.h +++ b/liveview/liveview.h @@ -42,6 +42,9 @@ public:  	void vibrate(int msecs);  	void setWatchletsModel(WatchletsModel *model); +	void setNotificationsModel(NotificationsModel *model); + +	static const int MaxBitmapSize;  	// Only for application mode  	QImage* image(); @@ -51,7 +54,7 @@ public:  	void clear();  protected: -	static const int DelayBetweenMessages = 10; +	static const int DelayBetweenMessages = 200;  	enum MessageType {  		NoMessage = 0, @@ -102,7 +105,8 @@ protected:  	enum Mode {  		RootMenuMode = 0, -		ApplicationMode +		ApplicationMode, +		NotificationListMode  	};  	enum NavigationEvent { @@ -116,6 +120,14 @@ protected:  		SelectMenu = 32  	}; +	enum NotificationAction { +		NotificationShowCurrent = 0, +		NotificationShowFirst = 1, +		NotificationShowLast = 2, +		NotificationShowNext = 3, +		NotificationShowPrev = 4 +	}; +  	struct Message {  		MessageType type;  		QByteArray data; @@ -124,24 +136,40 @@ protected:  		{ }  	}; +	struct RootMenuNotificationItem { +		QByteArray icon; +		QString title; +		QList<Notification::Type> notificationTypes; +	}; +  	struct RootMenuItem {  		MenuItemType type;  		QByteArray icon;  		QString title;  		int unread;  		QString watchletId; +		const RootMenuNotificationItem *notificationItem;  	}; +	static QMap<MessageType, MessageType> _ackMap; +	static void initializeAckMap(); +	static MessageType ackForMessage(MessageType type); + +	static QList<RootMenuNotificationItem> _rootNotificationItems; +	static void initializeRootNotificationItems(); +  	void setupBluetoothWatch();  	void desetupBluetoothWatch(); + +	void recreateNotificationsMenu();  	/** Recreate the device menu (after watchlets change) */  	void recreateWatchletsMenu();  	/** Update the device menu (after a power on, etc.) */  	void refreshMenu(); -	QByteArray encodeImage(const QImage& image) const; -	QByteArray encodeImage(const QUrl& url) const; +	static QByteArray encodeImage(const QImage& image); +	static QByteArray encodeImage(const QUrl& url);  protected:  	void send(const Message& msg); @@ -153,6 +181,7 @@ protected:  	void displayClear();  	void setMenuSize(unsigned char size);  	void sendMenuItem(unsigned char id, MenuItemType type, unsigned short unread, const QString& text, const QByteArray& image); +	void sendNotification(unsigned short id, unsigned short unread, unsigned short count, const QString& date, const QString& header, const QString& body, const QByteArray& image);  	void enableLed(const QColor& color, unsigned short delay, unsigned short time);  	void handleMessage(const Message& msg); @@ -165,14 +194,18 @@ protected:  	void handleDisplayProperties(const Message& msg);  	void handleSoftwareVersion(const Message& msg); +private: +	void sendMessageFromQueue(); +  private slots:  	void handleDataReceived(); -	void handleSendTimerTick();  	void handleWatchletsChanged(); +	void handleNotificationsChanged();  private:  	ConfigKey *_settings;  	WatchletsModel *_watchlets; +	NotificationsModel *_notifications;  	bool _24hMode : 1; @@ -181,6 +214,7 @@ private:  	QStringList _buttons;  	Mode _mode; +	int _curNotificationIndex;  	// Required by QPaintDevice  	mutable LiveViewPaintEngine* _paintEngine; @@ -190,9 +224,9 @@ private:  	/** Keeps the index of the first watchlet. */  	int _rootMenuFirstWatchlet; -	/** Message outbox queue. */ +	/** Outgoing message queue. */  	QQueue<Message> _sendingMsgs; -	QTimer* _sendTimer; +	MessageType _waitingForAck;  	/** Incomplete message that is being received. */  	Message _receivingMsg;  }; diff --git a/liveview/liveviewpaintengine.cpp b/liveview/liveviewpaintengine.cpp index 2370477..c073901 100644 --- a/liveview/liveviewpaintengine.cpp +++ b/liveview/liveviewpaintengine.cpp @@ -22,13 +22,13 @@ bool LiveViewPaintEngine::end()  	if (ret) {  		QRect rect = _damaged.boundingRect(); -		const int tile_size = 64; +		const int tile_size = LiveView::MaxBitmapSize;  		// Have to make tiles  		for (int x = rect.left(); x < rect.right(); x += tile_size) {  			for (int y = rect.top(); y < rect.bottom(); y += tile_size) {  				QRect tile(x, y, -						   qMin(x + tile_size, rect.right()), -						   qMin(y + tile_size, rect.bottom())); +						   qMin(tile_size, rect.width()), +						   qMin(tile_size, rect.height()));  				QImage sub_image = _watch->image()->copy(tile);  				_watch->renderImage(tile.x(), tile.y(), sub_image);  			} | 
