summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichele Bini <michele.bini@gmail.com>2022-04-13 18:53:16 (GMT)
committerMichele Bini <michele.bini@gmail.com>2022-04-13 18:53:16 (GMT)
commit531c1172a2357d53b214aaf7b29efee47d9b32e5 (patch)
treee9c008f13900e1d3c63332a3bbc305754f8ae35c
parent27fa6bba08766831fe143fe5ca13767bedcd9072 (diff)
Revert "Sans notification (notification manager retained as it seems to be used by the dfu manager)"ultraredux2
This reverts commit 569e6fea41c13f33ad1374bb80ca489aaf4a7037.
-rw-r--r--src/CMakeLists.txt12
-rw-r--r--src/components/ble/AlertNotificationClient.cpp186
-rw-r--r--src/components/ble/AlertNotificationClient.h70
-rw-r--r--src/components/ble/AlertNotificationService.cpp124
-rw-r--r--src/components/ble/AlertNotificationService.h68
-rw-r--r--src/components/ble/ImmediateAlertService.cpp75
-rw-r--r--src/components/ble/ImmediateAlertService.h39
-rw-r--r--src/components/ble/NimbleController.cpp9
-rw-r--r--src/components/ble/NimbleController.h10
-rw-r--r--src/components/motor/MotorController.cpp12
-rw-r--r--src/components/motor/MotorController.h2
-rw-r--r--src/displayapp/DisplayApp.cpp27
-rw-r--r--src/displayapp/screens/NotificationIcon.cpp10
-rw-r--r--src/displayapp/screens/NotificationIcon.h12
-rw-r--r--src/displayapp/screens/Notifications.cpp290
-rw-r--r--src/displayapp/screens/Notifications.h84
-rw-r--r--src/systemtask/Messages.h2
-rw-r--r--src/systemtask/SystemTask.cpp8
18 files changed, 1038 insertions, 2 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e0e334a..c47cda7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -389,10 +389,12 @@ list(APPEND SOURCE_FILES
displayapp/screens/Tile.cpp
displayapp/screens/BatteryIcon.cpp
displayapp/screens/BleIcon.cpp
+ displayapp/screens/NotificationIcon.cpp
displayapp/screens/Label.cpp
displayapp/screens/FirmwareUpdate.cpp
displayapp/screens/FirmwareValidation.cpp
displayapp/screens/ApplicationList.cpp
+ displayapp/screens/Notifications.cpp
displayapp/screens/List.cpp
displayapp/screens/Error.cpp
displayapp/screens/Styles.cpp
@@ -429,9 +431,12 @@ list(APPEND SOURCE_FILES
components/ble/NimbleController.cpp
components/ble/DeviceInformationService.cpp
components/ble/CurrentTimeClient.cpp
+ components/ble/AlertNotificationClient.cpp
components/ble/DfuService.cpp
components/ble/CurrentTimeService.cpp
+ components/ble/AlertNotificationService.cpp
components/ble/BatteryInformationService.cpp
+ components/ble/ImmediateAlertService.cpp
components/ble/ServiceDiscovery.cpp
components/firmwarevalidator/FirmwareValidator.cpp
components/motor/MotorController.cpp
@@ -479,9 +484,12 @@ list(APPEND RECOVERY_SOURCE_FILES
components/ble/NimbleController.cpp
components/ble/DeviceInformationService.cpp
components/ble/CurrentTimeClient.cpp
+ components/ble/AlertNotificationClient.cpp
components/ble/DfuService.cpp
components/ble/CurrentTimeService.cpp
+ components/ble/AlertNotificationService.cpp
components/ble/BatteryInformationService.cpp
+ components/ble/ImmediateAlertService.cpp
components/ble/ServiceDiscovery.cpp
components/firmwarevalidator/FirmwareValidator.cpp
components/settings/Settings.cpp
@@ -536,12 +544,14 @@ set(INCLUDE_FILES
displayapp/screens/Meter.h
displayapp/screens/BatteryIcon.h
displayapp/screens/BleIcon.h
+ displayapp/screens/NotificationIcon.h
displayapp/screens/ScreenList.h
displayapp/screens/Label.h
displayapp/screens/FirmwareUpdate.h
displayapp/screens/FirmwareValidation.h
displayapp/screens/ApplicationList.h
displayapp/Apps.h
+ displayapp/screens/Notifications.h
displayapp/Colors.h
drivers/St7789.h
drivers/SpiNorFlash.h
@@ -567,9 +577,11 @@ set(INCLUDE_FILES
components/ble/NimbleController.h
components/ble/DeviceInformationService.h
components/ble/CurrentTimeClient.h
+ components/ble/AlertNotificationClient.h
components/ble/DfuService.h
components/firmwarevalidator/FirmwareValidator.h
components/ble/BatteryInformationService.h
+ components/ble/ImmediateAlertService.h
components/ble/ServiceDiscovery.h
components/ble/BleClient.h
components/settings/Settings.h
diff --git a/src/components/ble/AlertNotificationClient.cpp b/src/components/ble/AlertNotificationClient.cpp
new file mode 100644
index 0000000..335845e
--- /dev/null
+++ b/src/components/ble/AlertNotificationClient.cpp
@@ -0,0 +1,186 @@
+#include "components/ble/AlertNotificationClient.h"
+#include <algorithm>
+#include "components/ble/NotificationManager.h"
+#include "systemtask/SystemTask.h"
+#include <nrf_log.h>
+
+using namespace Pinetime::Controllers;
+constexpr ble_uuid16_t AlertNotificationClient::ansServiceUuid;
+constexpr ble_uuid16_t AlertNotificationClient::supportedNewAlertCategoryUuid;
+constexpr ble_uuid16_t AlertNotificationClient::supportedUnreadAlertCategoryUuid;
+constexpr ble_uuid16_t AlertNotificationClient::newAlertUuid;
+constexpr ble_uuid16_t AlertNotificationClient::unreadAlertStatusUuid;
+constexpr ble_uuid16_t AlertNotificationClient::controlPointUuid;
+
+namespace {
+ int OnDiscoveryEventCallback(uint16_t conn_handle, const struct ble_gatt_error* error, const struct ble_gatt_svc* service, void* arg) {
+ auto client = static_cast<AlertNotificationClient*>(arg);
+ return client->OnDiscoveryEvent(conn_handle, error, service);
+ }
+
+ int OnAlertNotificationCharacteristicDiscoveredCallback(uint16_t conn_handle,
+ const struct ble_gatt_error* error,
+ const struct ble_gatt_chr* chr,
+ void* arg) {
+ auto client = static_cast<AlertNotificationClient*>(arg);
+ return client->OnCharacteristicsDiscoveryEvent(conn_handle, error, chr);
+ }
+
+ int OnAlertNotificationDescriptorDiscoveryEventCallback(
+ uint16_t conn_handle, const struct ble_gatt_error* error, uint16_t chr_val_handle, const struct ble_gatt_dsc* dsc, void* arg) {
+ auto client = static_cast<AlertNotificationClient*>(arg);
+ return client->OnDescriptorDiscoveryEventCallback(conn_handle, error, chr_val_handle, dsc);
+ }
+
+ int NewAlertSubcribeCallback(uint16_t conn_handle, const struct ble_gatt_error* error, struct ble_gatt_attr* attr, void* arg) {
+ auto client = static_cast<AlertNotificationClient*>(arg);
+ return client->OnNewAlertSubcribe(conn_handle, error, attr);
+ }
+}
+
+AlertNotificationClient::AlertNotificationClient(Pinetime::System::SystemTask& systemTask,
+ Pinetime::Controllers::NotificationManager& notificationManager)
+ : systemTask {systemTask}, notificationManager {notificationManager} {
+}
+
+bool AlertNotificationClient::OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error* error, const ble_gatt_svc* service) {
+ if (service == nullptr && error->status == BLE_HS_EDONE) {
+ if (isDiscovered) {
+ NRF_LOG_INFO("ANS Discovery found, starting characteristics discovery");
+
+ ble_gattc_disc_all_chrs(connectionHandle, ansStartHandle, ansEndHandle, OnAlertNotificationCharacteristicDiscoveredCallback, this);
+ } else {
+ NRF_LOG_INFO("ANS not found");
+ onServiceDiscovered(connectionHandle);
+ }
+ return true;
+ }
+
+ if (service != nullptr && ble_uuid_cmp(&ansServiceUuid.u, &service->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS discovered : 0x%x - 0x%x", service->start_handle, service->end_handle);
+ ansStartHandle = service->start_handle;
+ ansEndHandle = service->end_handle;
+ isDiscovered = true;
+ }
+ return false;
+}
+
+int AlertNotificationClient::OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle,
+ const ble_gatt_error* error,
+ const ble_gatt_chr* characteristic) {
+ if (error->status != 0 && error->status != BLE_HS_EDONE) {
+ NRF_LOG_INFO("ANS Characteristic discovery ERROR");
+ onServiceDiscovered(connectionHandle);
+ return 0;
+ }
+
+ if (characteristic == nullptr && error->status == BLE_HS_EDONE) {
+ NRF_LOG_INFO("ANS Characteristic discovery complete");
+ if (isCharacteristicDiscovered) {
+ ble_gattc_disc_all_dscs(connectionHandle, newAlertHandle, ansEndHandle, OnAlertNotificationDescriptorDiscoveryEventCallback, this);
+ } else
+ onServiceDiscovered(connectionHandle);
+ } else {
+ if (characteristic != nullptr && ble_uuid_cmp(&supportedNewAlertCategoryUuid.u, &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : supportedNewAlertCategoryUuid");
+ supportedNewAlertCategoryHandle = characteristic->val_handle;
+ } else if (characteristic != nullptr && ble_uuid_cmp(&supportedUnreadAlertCategoryUuid.u, &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : supportedUnreadAlertCategoryUuid");
+ supportedUnreadAlertCategoryHandle = characteristic->val_handle;
+ } else if (characteristic != nullptr && ble_uuid_cmp(&newAlertUuid.u, &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : newAlertUuid");
+ newAlertHandle = characteristic->val_handle;
+ newAlertDefHandle = characteristic->def_handle;
+ isCharacteristicDiscovered = true;
+ } else if (characteristic != nullptr && ble_uuid_cmp(&unreadAlertStatusUuid.u, &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : unreadAlertStatusUuid");
+ unreadAlertStatusHandle = characteristic->val_handle;
+ } else if (characteristic != nullptr && ble_uuid_cmp(&controlPointUuid.u, &characteristic->uuid.u) == 0) {
+ NRF_LOG_INFO("ANS Characteristic discovered : controlPointUuid");
+ controlPointHandle = characteristic->val_handle;
+ } else
+ NRF_LOG_INFO("ANS Characteristic discovered : 0x%x", characteristic->val_handle);
+ }
+ return 0;
+}
+
+int AlertNotificationClient::OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error* error, ble_gatt_attr* attribute) {
+ if (error->status == 0) {
+ NRF_LOG_INFO("ANS New alert subscribe OK");
+ } else {
+ NRF_LOG_INFO("ANS New alert subscribe ERROR");
+ }
+ onServiceDiscovered(connectionHandle);
+
+ return 0;
+}
+
+int AlertNotificationClient::OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle,
+ const ble_gatt_error* error,
+ uint16_t characteristicValueHandle,
+ const ble_gatt_dsc* descriptor) {
+ if (error->status == 0) {
+ if (characteristicValueHandle == newAlertHandle && ble_uuid_cmp(&newAlertUuid.u, &descriptor->uuid.u)) {
+ if (newAlertDescriptorHandle == 0) {
+ NRF_LOG_INFO("ANS Descriptor discovered : %d", descriptor->handle);
+ newAlertDescriptorHandle = descriptor->handle;
+ isDescriptorFound = true;
+ uint8_t value[2];
+ value[0] = 1;
+ value[1] = 0;
+ ble_gattc_write_flat(connectionHandle, newAlertDescriptorHandle, value, sizeof(value), NewAlertSubcribeCallback, this);
+ }
+ }
+ } else {
+ if (!isDescriptorFound)
+ onServiceDiscovered(connectionHandle);
+ }
+ return 0;
+}
+
+void AlertNotificationClient::OnNotification(ble_gap_event* event) {
+ if (event->notify_rx.attr_handle == newAlertHandle) {
+ constexpr size_t stringTerminatorSize = 1; // end of string '\0'
+ constexpr size_t headerSize = 3;
+ const auto maxMessageSize {NotificationManager::MaximumMessageSize()};
+ const auto maxBufferSize {maxMessageSize + headerSize};
+
+ // Ignore notifications with empty message
+ const auto packetLen = OS_MBUF_PKTLEN(event->notify_rx.om);
+ if (packetLen <= headerSize)
+ return;
+
+ size_t bufferSize = std::min(packetLen + stringTerminatorSize, maxBufferSize);
+ auto messageSize = std::min(maxMessageSize, (bufferSize - headerSize));
+
+ NotificationManager::Notification notif;
+ os_mbuf_copydata(event->notify_rx.om, headerSize, messageSize - 1, notif.message.data());
+ notif.message[messageSize - 1] = '\0';
+ notif.size = messageSize;
+ notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
+ notificationManager.Push(std::move(notif));
+
+ systemTask.PushMessage(Pinetime::System::Messages::OnNewNotification);
+ }
+}
+
+void AlertNotificationClient::Reset() {
+ ansStartHandle = 0;
+ ansEndHandle = 0;
+ supportedNewAlertCategoryHandle = 0;
+ supportedUnreadAlertCategoryHandle = 0;
+ newAlertHandle = 0;
+ newAlertDescriptorHandle = 0;
+ newAlertDefHandle = 0;
+ unreadAlertStatusHandle = 0;
+ controlPointHandle = 0;
+ isDiscovered = false;
+ isCharacteristicDiscovered = false;
+ isDescriptorFound = false;
+}
+
+void AlertNotificationClient::Discover(uint16_t connectionHandle, std::function<void(uint16_t)> onServiceDiscovered) {
+ NRF_LOG_INFO("[ANS] Starting discovery");
+ this->onServiceDiscovered = onServiceDiscovered;
+ ble_gattc_disc_svc_by_uuid(connectionHandle, &ansServiceUuid.u, OnDiscoveryEventCallback, this);
+}
diff --git a/src/components/ble/AlertNotificationClient.h b/src/components/ble/AlertNotificationClient.h
new file mode 100644
index 0000000..2d6a387
--- /dev/null
+++ b/src/components/ble/AlertNotificationClient.h
@@ -0,0 +1,70 @@
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#undef max
+#undef min
+#include "components/ble/BleClient.h"
+
+namespace Pinetime {
+
+ namespace System {
+ class SystemTask;
+ }
+
+ namespace Controllers {
+ class NotificationManager;
+
+ class AlertNotificationClient : public BleClient {
+ public:
+ explicit AlertNotificationClient(Pinetime::System::SystemTask& systemTask,
+ Pinetime::Controllers::NotificationManager& notificationManager);
+
+ bool OnDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error* error, const ble_gatt_svc* service);
+ int OnCharacteristicsDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error* error, const ble_gatt_chr* characteristic);
+ int OnNewAlertSubcribe(uint16_t connectionHandle, const ble_gatt_error* error, ble_gatt_attr* attribute);
+ int OnDescriptorDiscoveryEventCallback(uint16_t connectionHandle,
+ const ble_gatt_error* error,
+ uint16_t characteristicValueHandle,
+ const ble_gatt_dsc* descriptor);
+ void OnNotification(ble_gap_event* event);
+ void Reset();
+ void Discover(uint16_t connectionHandle, std::function<void(uint16_t)> lambda) override;
+
+ private:
+ static constexpr uint16_t ansServiceId {0x1811};
+ static constexpr uint16_t supportedNewAlertCategoryId = 0x2a47;
+ static constexpr uint16_t supportedUnreadAlertCategoryId = 0x2a48;
+ static constexpr uint16_t newAlertId = 0x2a46;
+ static constexpr uint16_t unreadAlertStatusId = 0x2a45;
+ static constexpr uint16_t controlPointId = 0x2a44;
+
+ static constexpr ble_uuid16_t ansServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ansServiceId};
+ static constexpr ble_uuid16_t supportedNewAlertCategoryUuid {.u {.type = BLE_UUID_TYPE_16}, .value = supportedNewAlertCategoryId};
+ static constexpr ble_uuid16_t supportedUnreadAlertCategoryUuid {.u {.type = BLE_UUID_TYPE_16},
+ .value = supportedUnreadAlertCategoryId};
+ static constexpr ble_uuid16_t newAlertUuid {.u {.type = BLE_UUID_TYPE_16}, .value = newAlertId};
+ static constexpr ble_uuid16_t unreadAlertStatusUuid {.u {.type = BLE_UUID_TYPE_16}, .value = unreadAlertStatusId};
+ static constexpr ble_uuid16_t controlPointUuid {.u {.type = BLE_UUID_TYPE_16}, .value = controlPointId};
+
+ uint16_t ansStartHandle = 0;
+ uint16_t ansEndHandle = 0;
+ uint16_t supportedNewAlertCategoryHandle = 0;
+ uint16_t supportedUnreadAlertCategoryHandle = 0;
+ uint16_t newAlertHandle = 0;
+ uint16_t newAlertDescriptorHandle = 0;
+ uint16_t newAlertDefHandle = 0;
+ uint16_t unreadAlertStatusHandle = 0;
+ uint16_t controlPointHandle = 0;
+ bool isDiscovered = false;
+ Pinetime::System::SystemTask& systemTask;
+ Pinetime::Controllers::NotificationManager& notificationManager;
+ std::function<void(uint16_t)> onServiceDiscovered;
+ bool isCharacteristicDiscovered = false;
+ bool isDescriptorFound = false;
+ };
+ }
+}
diff --git a/src/components/ble/AlertNotificationService.cpp b/src/components/ble/AlertNotificationService.cpp
new file mode 100644
index 0000000..0481912
--- /dev/null
+++ b/src/components/ble/AlertNotificationService.cpp
@@ -0,0 +1,124 @@
+#include "components/ble/AlertNotificationService.h"
+#include <hal/nrf_rtc.h>
+#include <cstring>
+#include <algorithm>
+#include "components/ble/NotificationManager.h"
+#include "systemtask/SystemTask.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t AlertNotificationService::ansUuid;
+constexpr ble_uuid16_t AlertNotificationService::ansCharUuid;
+constexpr ble_uuid128_t AlertNotificationService::notificationEventUuid;
+
+int AlertNotificationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ auto anService = static_cast<AlertNotificationService*>(arg);
+ return anService->OnAlert(conn_handle, attr_handle, ctxt);
+}
+
+void AlertNotificationService::Init() {
+ int res;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+AlertNotificationService::AlertNotificationService(System::SystemTask& systemTask, NotificationManager& notificationManager)
+ : characteristicDefinition {{.uuid = &ansCharUuid.u, .access_cb = AlertNotificationCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE},
+ {.uuid = &notificationEventUuid.u,
+ .access_cb = AlertNotificationCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_NOTIFY,
+ .val_handle = &eventHandle},
+ {0}},
+ serviceDefinition {
+ {/* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = &ansUuid.u,
+ .characteristics = characteristicDefinition},
+ {0},
+ },
+ systemTask {systemTask},
+ notificationManager {notificationManager} {
+}
+
+int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) {
+ if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ constexpr size_t stringTerminatorSize = 1; // end of string '\0'
+ constexpr size_t headerSize = 3;
+ const auto maxMessageSize {NotificationManager::MaximumMessageSize()};
+ const auto maxBufferSize {maxMessageSize + headerSize};
+
+ // Ignore notifications with empty message
+ const auto packetLen = OS_MBUF_PKTLEN(ctxt->om);
+ if (packetLen <= headerSize) {
+ return 0;
+ }
+
+ size_t bufferSize = std::min(packetLen + stringTerminatorSize, maxBufferSize);
+ auto messageSize = std::min(maxMessageSize, (bufferSize - headerSize));
+ Categories category;
+
+ NotificationManager::Notification notif;
+ os_mbuf_copydata(ctxt->om, headerSize, messageSize - 1, notif.message.data());
+ os_mbuf_copydata(ctxt->om, 0, 1, &category);
+ notif.message[messageSize - 1] = '\0';
+ notif.size = messageSize;
+
+ // TODO convert all ANS categories to NotificationController categories
+ switch (category) {
+ case Categories::Call:
+ notif.category = Pinetime::Controllers::NotificationManager::Categories::IncomingCall;
+ break;
+ default:
+ notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
+ break;
+ }
+
+ auto event = Pinetime::System::Messages::OnNewNotification;
+ notificationManager.Push(std::move(notif));
+ systemTask.PushMessage(event);
+ }
+ return 0;
+}
+
+void AlertNotificationService::AcceptIncomingCall() {
+ auto response = IncomingCallResponses::Answer;
+ auto* om = ble_hs_mbuf_from_flat(&response, 1);
+
+ uint16_t connectionHandle = systemTask.nimble().connHandle();
+
+ if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+ return;
+ }
+
+ ble_gattc_notify_custom(connectionHandle, eventHandle, om);
+}
+
+void AlertNotificationService::RejectIncomingCall() {
+ auto response = IncomingCallResponses::Reject;
+ auto* om = ble_hs_mbuf_from_flat(&response, 1);
+
+ uint16_t connectionHandle = systemTask.nimble().connHandle();
+
+ if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+ return;
+ }
+
+ ble_gattc_notify_custom(connectionHandle, eventHandle, om);
+}
+
+void AlertNotificationService::MuteIncomingCall() {
+ auto response = IncomingCallResponses::Mute;
+ auto* om = ble_hs_mbuf_from_flat(&response, 1);
+
+ uint16_t connectionHandle = systemTask.nimble().connHandle();
+
+ if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+ return;
+ }
+
+ ble_gattc_notify_custom(connectionHandle, eventHandle, om);
+}
diff --git a/src/components/ble/AlertNotificationService.h b/src/components/ble/AlertNotificationService.h
new file mode 100644
index 0000000..5c7d428
--- /dev/null
+++ b/src/components/ble/AlertNotificationService.h
@@ -0,0 +1,68 @@
+#pragma once
+#include <cstdint>
+#include <array>
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#undef max
+#undef min
+
+// 00020001-78fc-48fe-8e23-433b3a1942d0
+#define NOTIFICATION_EVENT_SERVICE_UUID_BASE \
+ { 0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x01, 0x00, 0x02, 0x00 }
+
+namespace Pinetime {
+
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class NotificationManager;
+
+ class AlertNotificationService {
+ public:
+ AlertNotificationService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::NotificationManager& notificationManager);
+ void Init();
+
+ int OnAlert(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt);
+
+ void AcceptIncomingCall();
+ void RejectIncomingCall();
+ void MuteIncomingCall();
+
+ enum class IncomingCallResponses : uint8_t { Reject = 0x00, Answer = 0x01, Mute = 0x02 };
+
+ private:
+ enum class Categories : uint8_t {
+ SimpleAlert = 0x00,
+ Email = 0x01,
+ News = 0x02,
+ Call = 0x03,
+ MissedCall = 0x04,
+ MmsSms = 0x05,
+ VoiceMail = 0x06,
+ Schedule = 0x07,
+ HighPrioritizedAlert = 0x08,
+ InstantMessage = 0x09,
+ All = 0xff
+ };
+
+ static constexpr uint16_t ansId {0x1811};
+ static constexpr uint16_t ansCharId {0x2a46};
+
+ static constexpr ble_uuid16_t ansUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ansId};
+
+ static constexpr ble_uuid16_t ansCharUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ansCharId};
+
+ static constexpr ble_uuid128_t notificationEventUuid {.u {.type = BLE_UUID_TYPE_128}, .value = NOTIFICATION_EVENT_SERVICE_UUID_BASE};
+
+ struct ble_gatt_chr_def characteristicDefinition[3];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+ Pinetime::System::SystemTask& systemTask;
+ NotificationManager& notificationManager;
+
+ uint16_t eventHandle;
+ };
+ }
+}
diff --git a/src/components/ble/ImmediateAlertService.cpp b/src/components/ble/ImmediateAlertService.cpp
new file mode 100644
index 0000000..c80b378
--- /dev/null
+++ b/src/components/ble/ImmediateAlertService.cpp
@@ -0,0 +1,75 @@
+#include "components/ble/ImmediateAlertService.h"
+#include <cstring>
+#include "components/ble/NotificationManager.h"
+#include "systemtask/SystemTask.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t ImmediateAlertService::immediateAlertServiceUuid;
+constexpr ble_uuid16_t ImmediateAlertService::alertLevelUuid;
+
+namespace {
+ int AlertLevelCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ auto* immediateAlertService = static_cast<ImmediateAlertService*>(arg);
+ return immediateAlertService->OnAlertLevelChanged(conn_handle, attr_handle, ctxt);
+ }
+
+ const char* ToString(ImmediateAlertService::Levels level) {
+ switch (level) {
+ case ImmediateAlertService::Levels::NoAlert:
+ return "Alert : None";
+ case ImmediateAlertService::Levels::HighAlert:
+ return "Alert : High";
+ case ImmediateAlertService::Levels::MildAlert:
+ return "Alert : Mild";
+ default:
+ return "";
+ }
+ }
+}
+
+ImmediateAlertService::ImmediateAlertService(Pinetime::System::SystemTask& systemTask,
+ Pinetime::Controllers::NotificationManager& notificationManager)
+ : systemTask {systemTask},
+ notificationManager {notificationManager},
+ characteristicDefinition {{.uuid = &alertLevelUuid.u,
+ .access_cb = AlertLevelCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
+ .val_handle = &alertLevelHandle},
+ {0}},
+ serviceDefinition {
+ {/* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = &immediateAlertServiceUuid.u,
+ .characteristics = characteristicDefinition},
+ {0},
+ } {
+}
+
+void ImmediateAlertService::Init() {
+ int res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+int ImmediateAlertService::OnAlertLevelChanged(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) {
+ if (attributeHandle == alertLevelHandle) {
+ if (context->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ auto alertLevel = static_cast<Levels>(context->om->om_data[0]);
+ auto* alertString = ToString(alertLevel);
+
+ NotificationManager::Notification notif;
+ std::memcpy(notif.message.data(), alertString, strlen(alertString));
+ notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
+ notificationManager.Push(std::move(notif));
+
+ systemTask.PushMessage(Pinetime::System::Messages::OnNewNotification);
+ }
+ }
+
+ return 0;
+}
diff --git a/src/components/ble/ImmediateAlertService.h b/src/components/ble/ImmediateAlertService.h
new file mode 100644
index 0000000..1f778ac
--- /dev/null
+++ b/src/components/ble/ImmediateAlertService.h
@@ -0,0 +1,39 @@
+#pragma once
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#undef max
+#undef min
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class NotificationManager;
+ class ImmediateAlertService {
+ public:
+ enum class Levels : uint8_t { NoAlert = 0, MildAlert = 1, HighAlert = 2 };
+
+ ImmediateAlertService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::NotificationManager& notificationManager);
+ void Init();
+ int OnAlertLevelChanged(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context);
+
+ private:
+ Pinetime::System::SystemTask& systemTask;
+ NotificationManager& notificationManager;
+
+ static constexpr uint16_t immediateAlertServiceId {0x1802};
+ static constexpr uint16_t alertLevelId {0x2A06};
+
+ static constexpr ble_uuid16_t immediateAlertServiceUuid {.u {.type = BLE_UUID_TYPE_16}, .value = immediateAlertServiceId};
+
+ static constexpr ble_uuid16_t alertLevelUuid {.u {.type = BLE_UUID_TYPE_16}, .value = alertLevelId};
+
+ struct ble_gatt_chr_def characteristicDefinition[3];
+ struct ble_gatt_svc_def serviceDefinition[2];
+
+ uint16_t alertLevelHandle;
+ };
+ }
+}
diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp
index 5236619..f63d98c 100644
--- a/src/components/ble/NimbleController.cpp
+++ b/src/components/ble/NimbleController.cpp
@@ -38,9 +38,11 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
currentTimeClient {dateTimeController},
anService {systemTask, notificationManager},
+ alertNotificationClient {systemTask, notificationManager},
currentTimeService {dateTimeController},
batteryInformationService {batteryController},
- serviceDiscovery({&currentTimeClient}) {
+ immediateAlertService {systemTask, notificationManager},
+ serviceDiscovery({&currentTimeClient, &alertNotificationClient}) {
}
void nimble_on_reset(int reason) {
@@ -81,6 +83,7 @@ void NimbleController::Init() {
anService.Init();
dfuService.Init();
batteryInformationService.Init();
+ immediateAlertService.Init();
int rc;
rc = ble_hs_util_ensure_addr(0);
@@ -163,6 +166,7 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
if (event->connect.status != 0) {
/* Connection failed; resume advertising. */
currentTimeClient.Reset();
+ alertNotificationClient.Reset();
connectionHandle = BLE_HS_CONN_HANDLE_NONE;
bleController.Disconnect();
fastAdvCount = 0;
@@ -181,6 +185,7 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
NRF_LOG_INFO("disconnect reason=%d", event->disconnect.reason);
currentTimeClient.Reset();
+ alertNotificationClient.Reset();
connectionHandle = BLE_HS_CONN_HANDLE_NONE;
if(bleController.IsConnected()) {
bleController.Disconnect();
@@ -271,6 +276,8 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
event->notify_rx.conn_handle,
event->notify_rx.attr_handle,
notifSize);
+
+ alertNotificationClient.OnNotification(event);
} break;
case BLE_GAP_EVENT_NOTIFY_TX:
diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h
index 2069d8c..c9d6420 100644
--- a/src/components/ble/NimbleController.h
+++ b/src/components/ble/NimbleController.h
@@ -7,11 +7,14 @@
#include <host/ble_gap.h>
#undef max
#undef min
+#include "components/ble/AlertNotificationClient.h"
+#include "components/ble/AlertNotificationService.h"
#include "components/ble/BatteryInformationService.h"
#include "components/ble/CurrentTimeClient.h"
#include "components/ble/CurrentTimeService.h"
#include "components/ble/DeviceInformationService.h"
#include "components/ble/DfuService.h"
+#include "components/ble/ImmediateAlertService.h"
#include "components/ble/ServiceDiscovery.h"
namespace Pinetime {
@@ -42,6 +45,10 @@ namespace Pinetime {
int OnGAPEvent(ble_gap_event* event);
void StartDiscovery();
+ Pinetime::Controllers::AlertNotificationService& alertService() {
+ return anService;
+ };
+
uint16_t connHandle();
void NotifyBatteryLevel(uint8_t level);
@@ -60,8 +67,11 @@ namespace Pinetime {
DeviceInformationService deviceInformationService;
CurrentTimeClient currentTimeClient;
+ AlertNotificationService anService;
+ AlertNotificationClient alertNotificationClient;
CurrentTimeService currentTimeService;
BatteryInformationService batteryInformationService;
+ ImmediateAlertService immediateAlertService;
ServiceDiscovery serviceDiscovery;
uint8_t addrType;
diff --git a/src/components/motor/MotorController.cpp b/src/components/motor/MotorController.cpp
index 24aeeb0..bee07fb 100644
--- a/src/components/motor/MotorController.cpp
+++ b/src/components/motor/MotorController.cpp
@@ -5,6 +5,7 @@
#include "drivers/PinMap.h"
APP_TIMER_DEF(shortVibTimer);
+APP_TIMER_DEF(longVibTimer);
using namespace Pinetime::Controllers;
@@ -13,6 +14,7 @@ void MotorController::Init() {
nrf_gpio_pin_set(PinMap::Motor);
app_timer_create(&shortVibTimer, APP_TIMER_MODE_SINGLE_SHOT, StopMotor);
+ app_timer_create(&longVibTimer, APP_TIMER_MODE_REPEATED, Ring);
}
void MotorController::Ring(void* p_context) {
@@ -25,6 +27,16 @@ void MotorController::RunForDuration(uint8_t motorDuration) {
app_timer_start(shortVibTimer, APP_TIMER_TICKS(motorDuration), nullptr);
}
+void MotorController::StartRinging() {
+ Ring(this);
+ app_timer_start(longVibTimer, APP_TIMER_TICKS(1000), this);
+}
+
+void MotorController::StopRinging() {
+ app_timer_stop(longVibTimer);
+ nrf_gpio_pin_set(PinMap::Motor);
+}
+
void MotorController::StopMotor(void* p_context) {
nrf_gpio_pin_set(PinMap::Motor);
}
diff --git a/src/components/motor/MotorController.h b/src/components/motor/MotorController.h
index 3117f5c..b5a592b 100644
--- a/src/components/motor/MotorController.h
+++ b/src/components/motor/MotorController.h
@@ -11,6 +11,8 @@ namespace Pinetime {
void Init();
void RunForDuration(uint8_t motorDuration);
+ void StartRinging();
+ void StopRinging();
private:
static void Ring(void* p_context);
diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index 8f6ab6e..e7b3416 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -10,6 +10,7 @@
#include "displayapp/screens/Clock.h"
#include "displayapp/screens/FirmwareUpdate.h"
#include "displayapp/screens/FirmwareValidation.h"
+#include "displayapp/screens/Notifications.h"
#include "displayapp/screens/Tile.h"
#include "displayapp/screens/Error.h"
@@ -141,6 +142,9 @@ void DisplayApp::Refresh() {
// clockScreen.SetBleConnectionState(bleController.IsConnected() ? Screens::Clock::BleConnectionStates::Connected :
// Screens::Clock::BleConnectionStates::NotConnected);
break;
+ case Messages::NewNotification:
+ LoadApp(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down);
+ break;
case Messages::TouchEvent: {
if (state != States::Running) {
break;
@@ -155,6 +159,9 @@ void DisplayApp::Refresh() {
case TouchEvents::SwipeUp:
LoadApp(Apps::Launcher, DisplayApp::FullRefreshDirections::Up);
break;
+ case TouchEvents::SwipeDown:
+ LoadApp(Apps::Notifications, DisplayApp::FullRefreshDirections::Down);
+ break;
case TouchEvents::SwipeRight:
LoadApp(Apps::QuickSettings, DisplayApp::FullRefreshDirections::RightAnim);
break;
@@ -177,7 +184,9 @@ void DisplayApp::Refresh() {
break;
case Messages::ButtonLongPressed:
if (currentApp != Apps::Clock) {
- if (currentApp == Apps::QuickSettings) {
+ if (currentApp == Apps::Notifications) {
+ LoadApp(Apps::Clock, DisplayApp::FullRefreshDirections::Up);
+ } else if (currentApp == Apps::QuickSettings) {
LoadApp(Apps::Clock, DisplayApp::FullRefreshDirections::LeftAnim);
} else {
LoadApp(Apps::Clock, DisplayApp::FullRefreshDirections::Down);
@@ -188,6 +197,12 @@ void DisplayApp::Refresh() {
// Open up Firmware window, before possible reboot if press continues
LoadApp(Apps::FirmwareValidation, DisplayApp::FullRefreshDirections::Up);
break;
+ case Messages::ButtonDoubleClicked:
+ if (currentApp != Apps::Notifications && currentApp != Apps::NotificationsPreview) {
+ LoadApp(Apps::Notifications, DisplayApp::FullRefreshDirections::Down);
+ }
+ break;
+
case Messages::BleFirmwareUpdateStarted:
LoadApp(Apps::FirmwareUpdate, DisplayApp::FullRefreshDirections::Down);
break;
@@ -259,6 +274,16 @@ void DisplayApp::LoadApp(Apps app, DisplayApp::FullRefreshDirections direction)
currentScreen = std::make_unique<Screens::FirmwareUpdate>(this, bleController);
ReturnApp(Apps::Clock, FullRefreshDirections::Down, TouchEvents::None);
break;
+ case Apps::Notifications:
+ currentScreen = std::make_unique<Screens::Notifications>(
+ this, notificationManager, systemTask->nimble().alertService(), motorController, *systemTask, Screens::Notifications::Modes::Normal);
+ ReturnApp(Apps::Clock, FullRefreshDirections::Up, TouchEvents::SwipeUp);
+ break;
+ case Apps::NotificationsPreview:
+ currentScreen = std::make_unique<Screens::Notifications>(
+ this, notificationManager, systemTask->nimble().alertService(), motorController, *systemTask, Screens::Notifications::Modes::Preview);
+ ReturnApp(Apps::Clock, FullRefreshDirections::Up, TouchEvents::SwipeUp);
+ break;
// Settings
case Apps::QuickSettings:
currentScreen = std::make_unique<Screens::QuickSettings>(
diff --git a/src/displayapp/screens/NotificationIcon.cpp b/src/displayapp/screens/NotificationIcon.cpp
new file mode 100644
index 0000000..0e913ae
--- /dev/null
+++ b/src/displayapp/screens/NotificationIcon.cpp
@@ -0,0 +1,10 @@
+#include "displayapp/screens/NotificationIcon.h"
+#include "displayapp/screens/Symbols.h"
+using namespace Pinetime::Applications::Screens;
+
+const char* NotificationIcon::GetIcon(bool newNotificationAvailable) {
+ if (newNotificationAvailable)
+ return Symbols::info;
+ else
+ return "";
+} \ No newline at end of file
diff --git a/src/displayapp/screens/NotificationIcon.h b/src/displayapp/screens/NotificationIcon.h
new file mode 100644
index 0000000..dc34c3f
--- /dev/null
+++ b/src/displayapp/screens/NotificationIcon.h
@@ -0,0 +1,12 @@
+#pragma once
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+ class NotificationIcon {
+ public:
+ static const char* GetIcon(bool newNotificationAvailable);
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/displayapp/screens/Notifications.cpp b/src/displayapp/screens/Notifications.cpp
new file mode 100644
index 0000000..81ee234
--- /dev/null
+++ b/src/displayapp/screens/Notifications.cpp
@@ -0,0 +1,290 @@
+#include "displayapp/screens/Notifications.h"
+#include "displayapp/DisplayApp.h"
+#include "components/ble/AlertNotificationService.h"
+#include "displayapp/screens/Symbols.h"
+
+using namespace Pinetime::Applications::Screens;
+
+Notifications::Notifications(DisplayApp* app,
+ Pinetime::Controllers::NotificationManager& notificationManager,
+ Pinetime::Controllers::AlertNotificationService& alertNotificationService,
+ Pinetime::Controllers::MotorController& motorController,
+ System::SystemTask& systemTask,
+ Modes mode)
+ : Screen(app),
+ notificationManager {notificationManager},
+ alertNotificationService {alertNotificationService},
+ motorController {motorController},
+ systemTask {systemTask},
+ mode {mode} {
+ notificationManager.ClearNewNotificationFlag();
+ auto notification = notificationManager.GetLastNotification();
+ if (notification.valid) {
+ currentId = notification.id;
+ currentItem = std::make_unique<NotificationItem>(notification.Title(),
+ notification.Message(),
+ notification.index,
+ notification.category,
+ notificationManager.NbNotifications(),
+ mode,
+ alertNotificationService,
+ motorController);
+ validDisplay = true;
+ } else {
+ currentItem = std::make_unique<NotificationItem>("Notification",
+ "No notification to display",
+ 0,
+ notification.category,
+ notificationManager.NbNotifications(),
+ Modes::Preview,
+ alertNotificationService,
+ motorController);
+ }
+
+ if (mode == Modes::Preview) {
+ if (notification.category == Controllers::NotificationManager::Categories::IncomingCall) {
+ motorController.StartRinging();
+ } else {
+ motorController.RunForDuration(35);
+ }
+
+ timeoutLine = lv_line_create(lv_scr_act(), nullptr);
+
+ lv_obj_set_style_local_line_width(timeoutLine, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, 3);
+ lv_obj_set_style_local_line_color(timeoutLine, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE);
+ lv_obj_set_style_local_line_rounded(timeoutLine, LV_LINE_PART_MAIN, LV_STATE_DEFAULT, true);
+
+ lv_line_set_points(timeoutLine, timeoutLinePoints, 2);
+ timeoutTickCountStart = xTaskGetTickCount();
+ interacted = false;
+ }
+
+ taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this);
+}
+
+Notifications::~Notifications() {
+ lv_task_del(taskRefresh);
+ // make sure we stop any vibrations before exiting
+ motorController.StopRinging();
+ lv_obj_clean(lv_scr_act());
+}
+
+void Notifications::Refresh() {
+ if (mode == Modes::Preview && timeoutLine != nullptr) {
+ TickType_t tick = xTaskGetTickCount();
+ int32_t pos = 240 - ((tick - timeoutTickCountStart) / (timeoutLength / 240));
+ if (pos <= 0) {
+ running = false;
+ } else {
+ timeoutLinePoints[1].x = pos;
+ lv_line_set_points(timeoutLine, timeoutLinePoints, 2);
+ }
+ }
+ running = currentItem->IsRunning() && running;
+}
+
+void Notifications::OnPreviewInteraction() {
+ motorController.StopRinging();
+ if (timeoutLine != nullptr) {
+ lv_obj_del(timeoutLine);
+ timeoutLine = nullptr;
+ }
+}
+
+bool Notifications::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ if (mode != Modes::Normal) {
+ if (!interacted && event == TouchEvents::Tap) {
+ interacted = true;
+ OnPreviewInteraction();
+ return true;
+ }
+ return false;
+ }
+
+ switch (event) {
+ case Pinetime::Applications::TouchEvents::SwipeDown: {
+ Controllers::NotificationManager::Notification previousNotification;
+ if (validDisplay)
+ previousNotification = notificationManager.GetPrevious(currentId);
+ else
+ previousNotification = notificationManager.GetLastNotification();
+
+ if (!previousNotification.valid)
+ return true;
+
+ validDisplay = true;
+ currentId = previousNotification.id;
+ currentItem.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::Down);
+ currentItem = std::make_unique<NotificationItem>(previousNotification.Title(),
+ previousNotification.Message(),
+ previousNotification.index,
+ previousNotification.category,
+ notificationManager.NbNotifications(),
+ mode,
+ alertNotificationService,
+ motorController);
+ }
+ return true;
+ case Pinetime::Applications::TouchEvents::SwipeUp: {
+ Controllers::NotificationManager::Notification nextNotification;
+ if (validDisplay)
+ nextNotification = notificationManager.GetNext(currentId);
+ else
+ nextNotification = notificationManager.GetLastNotification();
+
+ if (!nextNotification.valid) {
+ running = false;
+ return false;
+ }
+
+ validDisplay = true;
+ currentId = nextNotification.id;
+ currentItem.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::Up);
+ currentItem = std::make_unique<NotificationItem>(nextNotification.Title(),
+ nextNotification.Message(),
+ nextNotification.index,
+ nextNotification.category,
+ notificationManager.NbNotifications(),
+ mode,
+ alertNotificationService,
+ motorController);
+ }
+ return true;
+ default:
+ return false;
+ }
+}
+
+namespace {
+ void CallEventHandler(lv_obj_t* obj, lv_event_t event) {
+ auto* item = static_cast<Notifications::NotificationItem*>(obj->user_data);
+ item->OnCallButtonEvent(obj, event);
+ }
+}
+
+Notifications::NotificationItem::NotificationItem(const char* title,
+ const char* msg,
+ uint8_t notifNr,
+ Controllers::NotificationManager::Categories category,
+ uint8_t notifNb,
+ Modes mode,
+ Pinetime::Controllers::AlertNotificationService& alertNotificationService,
+ Pinetime::Controllers::MotorController& motorController)
+ : mode {mode}, alertNotificationService {alertNotificationService}, motorController {motorController} {
+ lv_obj_t* container1 = lv_cont_create(lv_scr_act(), NULL);
+
+ lv_obj_set_style_local_bg_color(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x222222));
+ lv_obj_set_style_local_pad_all(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 10);
+ lv_obj_set_style_local_pad_inner(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 5);
+ lv_obj_set_style_local_border_width(container1, LV_CONT_PART_MAIN, LV_STATE_DEFAULT, 0);
+
+ lv_obj_set_pos(container1, 0, 50);
+ lv_obj_set_size(container1, LV_HOR_RES, 190);
+
+ lv_cont_set_layout(container1, LV_LAYOUT_COLUMN_LEFT);
+ lv_cont_set_fit(container1, LV_FIT_NONE);
+
+ lv_obj_t* alert_count = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text_fmt(alert_count, "%i/%i", notifNr, notifNb);
+ lv_obj_align(alert_count, NULL, LV_ALIGN_IN_TOP_RIGHT, 0, 16);
+
+ lv_obj_t* alert_type = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(alert_type, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x888888));
+ if(title == nullptr) {
+ lv_label_set_text_static(alert_type, "Notification");
+ } else {
+ // copy title to label and replace newlines with spaces
+ lv_label_set_text(alert_type, title);
+ char *pchar = strchr(lv_label_get_text(alert_type), '\n');
+ while (pchar != nullptr) {
+ *pchar = ' ';
+ pchar = strchr(pchar + 1, '\n');
+ }
+ lv_label_refr_text(alert_type);
+ }
+ lv_label_set_long_mode(alert_type, LV_LABEL_LONG_SROLL_CIRC);
+ lv_obj_set_width(alert_type, 180);
+ lv_obj_align(alert_type, NULL, LV_ALIGN_IN_TOP_LEFT, 0, 16);
+
+ /////////
+ switch (category) {
+ default: {
+ lv_obj_t* alert_subject = lv_label_create(container1, nullptr);
+ lv_obj_set_style_local_text_color(alert_subject, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
+ lv_label_set_long_mode(alert_subject, LV_LABEL_LONG_BREAK);
+ lv_obj_set_width(alert_subject, LV_HOR_RES - 20);
+ lv_label_set_text(alert_subject, msg);
+ } break;
+ case Controllers::NotificationManager::Categories::IncomingCall: {
+ lv_obj_set_height(container1, 108);
+ lv_obj_t* alert_subject = lv_label_create(container1, nullptr);
+ lv_obj_set_style_local_text_color(alert_subject, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_ORANGE);
+ lv_label_set_long_mode(alert_subject, LV_LABEL_LONG_BREAK);
+ lv_obj_set_width(alert_subject, LV_HOR_RES - 20);
+ lv_label_set_text(alert_subject, "Incoming call from");
+
+ lv_obj_t* alert_caller = lv_label_create(container1, nullptr);
+ lv_obj_align(alert_caller, alert_subject, LV_ALIGN_OUT_BOTTOM_LEFT, 0, 0);
+ lv_label_set_long_mode(alert_caller, LV_LABEL_LONG_BREAK);
+ lv_obj_set_width(alert_caller, LV_HOR_RES - 20);
+ lv_label_set_text(alert_caller, msg);
+
+ bt_accept = lv_btn_create(lv_scr_act(), nullptr);
+ bt_accept->user_data = this;
+ lv_obj_set_event_cb(bt_accept, CallEventHandler);
+ lv_obj_set_size(bt_accept, 76, 76);
+ lv_obj_align(bt_accept, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 0, 0);
+ label_accept = lv_label_create(bt_accept, nullptr);
+ lv_label_set_text(label_accept, Symbols::phone);
+ lv_obj_set_style_local_bg_color(bt_accept, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
+
+ bt_reject = lv_btn_create(lv_scr_act(), nullptr);
+ bt_reject->user_data = this;
+ lv_obj_set_event_cb(bt_reject, CallEventHandler);
+ lv_obj_set_size(bt_reject, 76, 76);
+ lv_obj_align(bt_reject, NULL, LV_ALIGN_IN_BOTTOM_MID, 0, 0);
+ label_reject = lv_label_create(bt_reject, nullptr);
+ lv_label_set_text(label_reject, Symbols::phoneSlash);
+ lv_obj_set_style_local_bg_color(bt_reject, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_RED);
+
+ bt_mute = lv_btn_create(lv_scr_act(), nullptr);
+ bt_mute->user_data = this;
+ lv_obj_set_event_cb(bt_mute, CallEventHandler);
+ lv_obj_set_size(bt_mute, 76, 76);
+ lv_obj_align(bt_mute, NULL, LV_ALIGN_IN_BOTTOM_RIGHT, 0, 0);
+ label_mute = lv_label_create(bt_mute, nullptr);
+ lv_label_set_text(label_mute, Symbols::volumMute);
+ lv_obj_set_style_local_bg_color(bt_mute, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GRAY);
+ } break;
+ }
+
+ lv_obj_t* backgroundLabel = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP);
+ lv_obj_set_size(backgroundLabel, 240, 240);
+ lv_obj_set_pos(backgroundLabel, 0, 0);
+ lv_label_set_text(backgroundLabel, "");
+}
+
+void Notifications::NotificationItem::OnCallButtonEvent(lv_obj_t* obj, lv_event_t event) {
+ if (event != LV_EVENT_CLICKED) {
+ return;
+ }
+
+ motorController.StopRinging();
+
+ if (obj == bt_accept) {
+ alertNotificationService.AcceptIncomingCall();
+ } else if (obj == bt_reject) {
+ alertNotificationService.RejectIncomingCall();
+ } else if (obj == bt_mute) {
+ alertNotificationService.MuteIncomingCall();
+ }
+
+ running = false;
+}
+
+Notifications::NotificationItem::~NotificationItem() {
+ lv_obj_clean(lv_scr_act());
+}
diff --git a/src/displayapp/screens/Notifications.h b/src/displayapp/screens/Notifications.h
new file mode 100644
index 0000000..7416035
--- /dev/null
+++ b/src/displayapp/screens/Notifications.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <lvgl/lvgl.h>
+#include <FreeRTOS.h>
+#include <cstdint>
+#include <memory>
+#include "displayapp/screens/Screen.h"
+#include "components/ble/NotificationManager.h"
+#include "components/motor/MotorController.h"
+#include "systemtask/SystemTask.h"
+
+namespace Pinetime {
+ namespace Controllers {
+ class AlertNotificationService;
+ }
+ namespace Applications {
+ namespace Screens {
+
+ class Notifications : public Screen {
+ public:
+ enum class Modes { Normal, Preview };
+ explicit Notifications(DisplayApp* app,
+ Pinetime::Controllers::NotificationManager& notificationManager,
+ Pinetime::Controllers::AlertNotificationService& alertNotificationService,
+ Pinetime::Controllers::MotorController& motorController,
+ System::SystemTask& systemTask,
+ Modes mode);
+ ~Notifications() override;
+
+ void Refresh() override;
+ bool OnTouchEvent(Pinetime::Applications::TouchEvents event) override;
+ void OnPreviewInteraction();
+
+ class NotificationItem {
+ public:
+ NotificationItem(const char* title,
+ const char* msg,
+ uint8_t notifNr,
+ Controllers::NotificationManager::Categories,
+ uint8_t notifNb,
+ Modes mode,
+ Pinetime::Controllers::AlertNotificationService& alertNotificationService,
+ Pinetime::Controllers::MotorController& motorController);
+ ~NotificationItem();
+ bool IsRunning() const {
+ return running;
+ }
+ void OnCallButtonEvent(lv_obj_t*, lv_event_t event);
+
+ private:
+ lv_obj_t* container1;
+ lv_obj_t* bt_accept;
+ lv_obj_t* bt_mute;
+ lv_obj_t* bt_reject;
+ lv_obj_t* label_accept;
+ lv_obj_t* label_mute;
+ lv_obj_t* label_reject;
+ Modes mode;
+ Pinetime::Controllers::AlertNotificationService& alertNotificationService;
+ Pinetime::Controllers::MotorController& motorController;
+ bool running = true;
+ };
+
+ private:
+ Pinetime::Controllers::NotificationManager& notificationManager;
+ Pinetime::Controllers::AlertNotificationService& alertNotificationService;
+ Pinetime::Controllers::MotorController& motorController;
+ System::SystemTask& systemTask;
+ Modes mode = Modes::Normal;
+ std::unique_ptr<NotificationItem> currentItem;
+ Controllers::NotificationManager::Notification::Id currentId;
+ bool validDisplay = false;
+
+ lv_point_t timeoutLinePoints[2] {{0, 1}, {239, 1}};
+ lv_obj_t* timeoutLine = nullptr;
+ TickType_t timeoutTickCountStart;
+ static const TickType_t timeoutLength = pdMS_TO_TICKS(7000);
+ bool interacted = true;
+
+ lv_task_t* taskRefresh;
+ };
+ }
+ }
+}
diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h
index a55acf4..3c99a89 100644
--- a/src/systemtask/Messages.h
+++ b/src/systemtask/Messages.h
@@ -4,6 +4,7 @@ namespace Pinetime {
namespace System {
enum class Messages {
OnNewTime,
+ OnNewNotification,
OnNewCall,
BleConnected,
BleFirmwareUpdateStarted,
@@ -13,6 +14,7 @@ namespace Pinetime {
HandleButtonTimerEvent,
OnNewDay,
OnChargingEvent,
+ StopRinging,
MeasureBatteryTimerExpired,
BatteryPercentageUpdated,
StartFileTransfer,
diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp
index 48b7d6e..60b4cc9 100644
--- a/src/systemtask/SystemTask.cpp
+++ b/src/systemtask/SystemTask.cpp
@@ -186,6 +186,14 @@ void SystemTask::Work() {
case Messages::OnNewTime:
displayApp.PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime);
break;
+ case Messages::OnNewNotification:
+ if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::ON) {
+ displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
+ }
+ break;
+ case Messages::StopRinging:
+ motorController.StopRinging();
+ break;
case Messages::BleConnected:
isBleDiscoveryTimerRunning = true;
bleDiscoveryTimer = 5;