M .idea/modules.xml => .idea/modules.xml +1 -1
@@ 5,4 5,4 @@
<module fileurl="file://$PROJECT_DIR$/.idea/PurePhone.iml" filepath="$PROJECT_DIR$/.idea/PurePhone.iml" />
</modules>
</component>
-</project>
+</project><
\ No newline at end of file
M doc/i18n.md => doc/i18n.md +5 -12
@@ 24,16 24,7 @@ The keys on the left side refer to the values on the right side. These values ar
### Keyboard input language
-The first four values of JSON language files tell MuditaOS, which keyboard input language is the specific language setting using:
-```c++
-"common_kbd_lower": "English_lower",
-"common_kbd_upper": "English_upper",
-"common_kbd_numeric": "numeric",
-"common_kbd_phone": "phone",
-(...)
-```
-
-These values are names of JSON files, which are located in [the image/assets/profiles folder](../image/assets/profiles/).
+Keyboard input language files have JSON extension and are located in [the image/assets/profiles folder](../image/assets/profiles/).
Every language has its own files for upper and lower letters. Here's an example of a working JSON file for `English_lower.json`:
```c++
@@ 57,9 48,11 @@ The first value declares the type of this file:
- `normal` - they are shown in input language settings, user can change it through GUI (e.g. from English to Polish).
- `special` - they won't show in input language settings, they can be modified only in code (e.g. numeric keyboard).
-Normal-type files will be displayed in settings by their filename (e.g. English_lower will be shown as English). When you add a new input language you should always include files for lower and upper letters for it.
+Normal-type files will be displayed in settings by their filename (e.g. `English_lower.json` and `English_upper.json` will be shown as `English`). When you add a new input language you should always include files for lower and upper letters for it.
-Next key-value pairs includes code of the key (key) and characters available under this key (value).
+Next key-value pairs includes code of the key (key) and characters available under this key (value). However, there are two buttons with hexadecimal number (under `61` and `63` buttons). Their value tells MuditaOS what they do:
+- `0x0A` - after shortpress (explained below) button change input mode (from lower letters to upper, from upper to numbers etc.). After longpress they show special characters window.
+- `0x08` - after shortpress button deletes character.
Files naming pattern should be: `<language>_<lower/upper>`, eg. correct implementation of Rodian input language should consist of two files: `Rodian_lower.json` and `Rodian_upper.json`.
M doc/quickstart.md => doc/quickstart.md +111 -103
@@ 2,76 2,108 @@
You can quickstart the project by going through one of the following guides:
-- [Quickstart using Docker](#quickstart-using-docker)
+- [Introduction](#introduction)
+- [Quickstart on Linux](#quickstart-on-linux)
+- [Quickstart with unit tests](#quickstart-with-unit-tests)
+- [Quickstart on the phone](#quickstart-on-the-phone)
+- [More details on preparing your local environment](#more-details-on-preparing-your-local-environment)
+- [CI build using Docker](#ci-build-using-docker)
- [Building your own Docker image](#building-your-own-docker-image)
-- [Quickstart in your local environment](#quickstart-in-your-local-environment)
-- [Longstart](#longstart)
-- [Super dirty quickstart on Linux](#super-dirty-quickstart-on-linux)
-- [Super dirty quickstart with unit tests](#super-dirty-quickstart-with-unit-tests)
-- [Super dirty quickstart on the phone](#super-dirty-quickstart-on-the-phone)
- [Preparing packages](#preparing-packages)
+## Introduction
-## Quickstart using Docker
-You can build MuditaOS using a Docker container. To do that, follow these steps
+Run the provisioning script `./config/bootstrap.sh` to install all dependencies. The script is written for Ubuntu and tested on 20.04.
-1. Get Docker by running `./config/bootstrap.sh 8` or [install it yourself](https://www.docker.com/get-started).
+To run the script execute the following command: `cd config && ./bootstrap.sh 0-`
-2. Add yourself to a `docker` group:
+After running provisioning you are ready to checkout and build project for both linux and RT1051. Please follow these steps:
+
+1. Check out the project with submodules for the first time:
```bash
-NAME=$(whoami)
-sudo usermod -aG docker ${NAME}
+git submodule update --init --recursive
```
-After that you have to log out and log back in as groups are set during the login process. To make sure you are in docker group enter the `groups` command and
-you will see the list of groups your user is assigned to.
-
-3. Configure for Linux Debug: `./in_docker.sh config linux Debug`
-
-4. Build linux Debug: `./in_docker.sh make build-linux-Debug`
+2. Update the repository with submodules
-5. Build RT1051 Release
```bash
-./in_docker.sh config rt1051 Release
-./in_docker.sh make build-rt1051-Release
+git pull --recurse-submodules
```
-6. Build and run tests (Linux only)
+3. Build the project
```bash
-./in_docker.sh make build-linux-Debug
-./in_docker.sh make check
+./configure.sh [rt1051|linux] [release|debug|relwithdebinfo]
+cd build-[rt1051|linux]-[release|debug|relwithdebinfo]
+make
```
-To build selected test manually in the working directory of attached Docker image run:
+
+### Quickstart on Linux
+
+Here's the bare minimum that will enable you to bootstrap the environment on Linux.
+
```bash
-cd ./build-linux-Debug ; make <test_name>; ./<test_name>
+git submodule update --init --recursive # initialize submodules
+cd ./config/ && ./bootstrap.sh 0- && cd ../ # bootstrap requirements
+./configure.sh rt1051|linux Debug|Release|RelWithDebInfo # configure build
+cd <build-dir> # build dir depends on configuration
+make -j # build
+./PurePhone # run PurePhone - emulator screen will pop up (on the Linux filesystem)
```
-i.e.:
+
+If you want to run the emulator with image and our VFS implementation
+you need to run the image through the script:
```bash
-cd ./build-linux-Debug ; make unittest_gui && ./unittest_gui
+./run_emulator_on_filesystem_image.sh
```
-Running tests checks memory leaks too - this might be helpful in creation of new widgets.
-## Building your own Docker image
+### Quickstart with unit tests
-If for some reason you don't want to use [the existing Docker image](docker push wearemudita/mudita_os_builder:latest) you can build your own by following
-
-1. Configure Docker file and download the necessary tool chain packages:
+After going through the Super dirty quickstart on Linux, run the following commands to build and run all unit tests:
```bash
-./config/build_docker_runner
+./configure.sh linux debug
+cd <build-dir>
+make check
```
+### Quickstart on the phone
-2. Build a new Docker image
+1. First install J-Link driver. We suggest using J-Link driver in version J-Link v634f ([Ubuntu download](https://www.segger.com/downloads/jlink/JLink_Linux_V634f_x86_64.deb))
+
+2. Please run the following commands:
```bash
-docker build docker -f docker/Dockerfile -t "wearemudita/mudita_os_builder:latest"
+cp ./env.cmake.sample ./env.cmake && sed -i "s:<HOME>:$HOME:" env.cmake
+git submodule update --init --recursive
+cd ./config/ && ./bootstrap.sh 0- && cd ../
+./configure.sh rt1051 RelWithDebInfo
+cd build-arm-RelWithDebInfo
+make -j
+
```
-Please make sure that you add proper tag for image (`-t wearemudita/mudita_os_builder:latest`) as other scripts are using it for building, and if Docker wouldn't be able to find it locally it will download it from Docker Hub.
+3. Please take the following steps in three parallel consoles/sessions:
+
+ - Start J-Link server: `./StartJLinkServer.sh`
+ - Load app with GDB via J-Link connected to Mudita Pure: `./run.sh <build folder>`
+ - Catch logs from Mudita Pure from J-Link RTT and write them to `/tmp/log.txt`: `JLinkRTTClient | tee /tmp/log.txt`
+
+#### Catching logs using UART
+
+If you want to catch logs from Mudita Pure from UART please follow these steps:
+
+1. In `config/ProjectConfig.cmake` change `RTT_JLINK` to `RTT_LUART`
+2. Rebuild the project
+3. Catch logs: `picocom /dev/ttyACM0 -b 115200 --imap lfcrlf`
-## Quickstart in your local environment
+Please mind that logs on UART are more costly, so these might cause timing issues and slow down the phone too. To avoid that consider `release` build for tests with `uart` enabled.
-Prior to setting up your environment, please run the following command: `cd config && ./bootstrap.sh` The script is written for Ubuntu and tested on 20.04.
+## More details on preparing your local environment
+
+The quickest way to set up your environment is running the following command:
+```bash
+cd config && ./bootstrap.sh 0-
+```
+The script is written for Ubuntu and tested on 20.04.
* This script will require `sudo` (for `apt`)
* It will install `cmake` and `gcc` by default in `${HOME}` directory - in case of other needs, please change the script
@@ 92,7 124,7 @@ If the script is run after a fresh `git clone` you need to update your git confi
* `./config/bootstrap.sh 7` - add paths for `cmake` to your `PATH` environment variable
* `./config/bootstrap.sh 8` - install Docker
-#### Code style with git hooks
+### Code style with git hooks
The `bootstrap.sh` script installs git hooks for code style checking. `pre-commit.hook`automatically updates style during commit. If you haven't run `bootstrap.sh` and want to use git hooks, you have to copy (or link) `pre-commit.hook` to your git config directory `.git/config/hooks`: `ln -s `pwd`/config/pre-commit.hook .git/hooks/pre-commit`
@@ 114,7 146,7 @@ git commit
To fix the style for Pull Request CI:
`./config/pre-commit.hook --branch-fix`
-## Commit message template
+### Commit message template
To add commit message template use this command:
@@ 125,19 157,19 @@ git config commit.template .gitmessage
This way each time you add new commit you will see template that will help
you with proper message format. More about that in (Development Workflow)[./doc/development_workflow.md#commit-changes]
-## Commit message hook
+### Commit message hook
This hooks automatically converts your branch name to commit title
rules:
if branch starts with EGD-xxxx it is treated as jira ticket
all `-` and `_` are converted to spaces
-### adding a hook:
+#### Adding a hook:
```bash
cd .git/hooks
ln -s ../../config/prepare-commit-msg
```
-### using hook:
+#### Using a hook:
Create a branch
@@ 151,92 183,68 @@ Do your changes, and prepare commit
git commit
```
+## CI build using Docker
+You can build MuditaOS using a Docker container. To do that, follow these steps
-## Longstart
-
-Run the provisioning script `./config/bootstrap.sh` to install all dependencies. The script is written for Ubuntu and tested on 20.04.
-
-To run the script execute the following command: `cd config && ./bootstrap.sh`
-
-After running provisioning you are ready to checkout and build project for both linux and RT1051. Pleasse follow these steps:
+1. Get Docker by running `./config/bootstrap.sh 8` or [install it yourself](https://www.docker.com/get-started) (if was not installed previously with `./config/bootstrap 0-`).
-1. Check out the project with submodules for the first time:
+2. Add yourself to a `docker` group:
```bash
-git submodule update --init --recursive
+NAME=$(whoami)
+sudo usermod -aG docker ${NAME}
```
-2. Update the repository with submodules
+After that you have to log out and log back in as groups are set during the login process. To make sure you are in docker group enter the `groups` command and
+you will see the list of groups your user is assigned to.
+3. Configure for Linux Debug:
```bash
-git pull --recurse-submodules
+./in_docker.sh config linux Debug
```
-3. Build the project
+4. Build linux Debug:
```bash
-./configure.sh [rt1051|linux] [release|debug|relwithdebinfo]
-cd build-[rt1051|linux]-[release|debug|relwithdebinfo]
-make
+./in_docker.sh make build-linux-Debug
```
-## Super dirty quickstart on Linux
-
-Here's the bare minimum that will enable you to bootstrap the environment on Linux.
-
+5. Build RT1051 Release
```bash
-git submodule update --init --recursive # initialize submodules
-cd ./config/ && ./bootstrap.sh && cd ../ # bootstrap requirements
-./configure.sh rt1051|linux Debug|Release|RelWithDebInfo # configure build
-cd <build-dir> # build dir depends on configuration
-make -j # build
-./PurePhone # run PurePhone - emulator screen will pop up (on the Linux filesystem)
+./in_docker.sh config rt1051 Release
+./in_docker.sh make build-rt1051-Release
```
+6. Build and run tests (Linux only)
-If you want to run the emulator with image and our VFS implementation
-you need to run the image through the script:
```bash
- ./run_emulator_on_filesystem_image.sh
+./in_docker.sh make build-linux-Debug
+./in_docker.sh make build-linux-Debug check
```
-
-## Super dirty quickstart with unit tests
-
-After going through the Super dirty quickstart on Linux, run the following commands to build and run all unit tests:
-
+To build selected test manually in the working directory of attached Docker image run:
```bash
-./configure.sh linux debug
-cd <build-dir>
-make check
+cd ./build-linux-Debug ; make <test_name>; ./<test_name>
```
-## Super dirty quickstart on the phone
-
-1. First install J-Link driver. We suggest using J-Link driver in version J-Link v634f ([Ubuntu download](https://www.segger.com/downloads/jlink/JLink_Linux_V634f_x86_64.deb)
-
-2. Please run the following commands:
-
+i.e.:
```bash
-cp ./env.cmake.sample ./env.cmake && sed -i "s:<HOME>:$HOME:" env.cmake
-git submodule update --init --recursive
-cd ./config/ && ./bootstrap.sh && cd ../
-./configure.sh rt1051 RelWithDebInfo
-cd build-arm-RelWithDebInfo
-make -j
-
+cd ./build-linux-Debug ; make unittest_gui && ./unittest_gui
```
+Running tests checks memory leaks too - this might be helpful in creation of new widgets.
-3. Please take the following steps in three parallel consoles/sessions:
+### Building your own Docker image
- - Start J-Link server: `./StartJLinkServer.sh`
- - Load app with GDB via J-Link connected to Mudita Pure: `./run.sh <build folder>`
- - Catch logs from Mudita Pure from J-Link RTT and write them to `/tmp/log.txt`: `JLinkRTTClient | tee /tmp/log.txt`
+If for some reason (f.ex. testing new dependencies) you need to create your own docker image, you can do it by following:
-### Catching logs using UART
+1. Configure Docker file and download the necessary tool chain packages:
-If you want to catch logs from Mudita Pure from UART please follow these steps:
+```bash
+./config/build_runner_docker
+```
-1. In `config/ProjectConfig.cmake` change `RTT_JLINK` to `RTT_LUART`
-2. Rebuild the project
-3. Catch logs: `picocom /dev/ttyACM0 -b 115200 --imap lfcrlf`
+2. Build a new Docker image
-Please mind that logs on UART are more costly, so these might cause timing issues and slow down the phone too. To avoid that consider `release` build for tests with `uart` enabled.
+```bash
+docker build docker -f docker/Dockerfile -t "wearemudita/mudita_os_builder:latest"
+```
+
+Please be aware that when building custom image you'll have to give it some tag (default from `build_runner_docker` is `latest`). You also need to provide some scripts with your tag (like `CONTAINER_TAG` in `in_docker.sh`), otherwise Docker will download and use original image from Mudita.
## Preparing packages
M image/assets/lang/Deutsch.json => image/assets/lang/Deutsch.json +0 -3
@@ 1,7 1,4 @@
{
- "common_kbd_lower": "Deutsch_lower",
- "common_kbd_upper": "Deutsch_upper",
- "common_kbd_numeric": "numeric",
"common_add": "ADD",
"common_open": "OPEN",
"common_call": "CALL",
M image/assets/lang/English.json => image/assets/lang/English.json +11 -12
@@ 1,8 1,4 @@
{
- "common_kbd_lower": "English_lower",
- "common_kbd_upper": "English_upper",
- "common_kbd_numeric": "numeric",
- "common_kbd_phone": "phone",
"common_add": "ADD",
"common_open": "OPEN",
"common_call": "CALL",
@@ 85,7 81,7 @@
"app_alarm_clock_repeat_everyday" : "Everyday",
"app_alarm_clock_repeat_week_days" : "Week Days",
"app_alarm_clock_repeat_custom" : "Custom",
- "app_alarm_clock_no_alarms_information" : "No alarms yet.\nPress left arrow to add new.",
+ "app_alarm_clock_no_alarms_information" : "<text align='center' color='9'>No alarms yet.<p>Press <b>left arrow</b> to add new.</p></text>",
"app_alarm_clock_options_title": "Options",
"app_alarm_clock_options_edit": "Edit",
"app_alarm_clock_options_delete": "Delete",
@@ 158,7 154,7 @@
"app_notes_edited": "Edited",
"app_notes_delete_note": "Delete",
"app_notes_note_delete_confirmation": "Do you really want to delete this note?",
- "app_notes_no_notes": "There are no notes yet.\nPress Left arrow to add new.",
+ "app_notes_no_notes": "<text align='center' color='9'>No notes yet.<p>Press <b>left arrow</b> to add new.</p></text>",
"app_notes_search_no_results": "No notes found.",
"app_calllog_title_main": "Calls",
@@ 267,8 263,8 @@
"app_call_offline": "You're offline.\n\nTo make a call\n switch to the Connected mode.",
"app_messages_title_main": "Messages",
"app_messages_new_message": "New Message",
- "app_messages_no_messages": "There are no messages yet.\nPress Left arrow to add new.",
- "app_messages_thread_delete_confirmation": "Do you really want to delete\nthis conversation?",
+ "app_messages_no_messages": "<text align='center' color='9'>No messages yet.<p>Press <b>left arrow</b> to add new.</p></text>",
+ "app_messages_thread_delete_confirmation": "Delete this conversation?",
"app_messages_message_delete_confirmation": "<text align='center'><p>Do you really want to <text weight='bold'>delete</text></p>this message?</text>",
"app_messages_thread_no_result": "There are no results",
"app_messages_message": "Message",
@@ 346,6 342,9 @@
"app_settings_network_sim1": "Sim1",
"app_settings_network_sim2": "Sim2",
"app_settings_network_sim_none": "No Sim",
+ "app_settings_network_voice_over_lte" : "VoLTE (experimental)",
+ "app_settings_network_apn_settings" : "APN settings",
+ "app_settings_network_apn_settings_no_apns" : "<text align='center' color='9'>No APNs yet.<p>Press <b>left arrow</b> to add new.</p></text>",
"app_settings_toggle_on": "ON",
"app_settings_toggle_off": "OFF",
"app_settings_security_lock_screen_passcode": "Lock screen passcode",
@@ 402,8 401,8 @@
"app_meditation_interval_chime": "Interval Chime",
"app_meditation_interval_none": "None",
"app_meditation_interval_every_x_minutes": "Every %0 minutes",
- "app_meditation_minute" : "MINUTE",
- "app_meditation_minutes" : "MINUTES",
+ "app_meditation_minute": "MINUTE",
+ "app_meditation_minutes": "MINUTES",
"app_music_player_all_songs": "All songs",
"app_music_player_play": "PLAY",
"app_music_player_music_library": "MUSIC LIBRARY",
@@ 411,14 410,14 @@
"app_music_player_music_empty_window_notification": "Press Music Library to choose\n a song from the library",
"app_special_input_window": "Special characters",
"app_emoji_input_window": "Emoji",
- "sms_add_rec_num": "Add recipient or type a number",
+ "sms_add_rec_num": "Add contact",
"sms_title_message": "New message",
"sms_call_text": "Call ",
"sms_resend_failed": "Send again",
"sms_delete_conversation": "Delete conversation",
"sms_forward_message": "Forward message",
"sms_copy": "Copy",
- "sms_delete_message": "Delete",
+ "sms_delete_message": "Delete message",
"sms_from_this_sms": "From this message",
"sms_use_template": "Use Template",
"sms_paste": "Paste",
M image/assets/lang/Espanol.json => image/assets/lang/Espanol.json +0 -3
@@ 1,7 1,4 @@
{
- "common_kbd_lower": "Espanol_lower",
- "common_kbd_upper": "Espanol_upper",
- "common_kbd_numeric": "numeric",
"common_add": "ADD",
"common_open": "ABIERTA",
"common_call": "CALL",
M image/assets/lang/Francais.json => image/assets/lang/Francais.json +0 -3
@@ 1,7 1,4 @@
{
- "common_kbd_lower": "Francais_lower",
- "common_kbd_upper": "Francais_upper",
- "common_kbd_numeric": "numeric",
"common_add": "ADD",
"common_open": "OPEN",
"common_call": "CALL",
M image/assets/lang/Polski.json => image/assets/lang/Polski.json +0 -3
@@ 1,7 1,4 @@
{
- "common_kbd_lower": "Polski_lower",
- "common_kbd_upper": "Polski_upper",
- "common_kbd_numeric": "numeric",
"common_add": "DODAJ",
"common_open": "OTWÓRZ",
"common_call": "DZWOŃ",
D image/user/db/settings_v2_001.sql => image/user/db/settings_v2_001.sql +0 -43
@@ 1,43 0,0 @@
---
--- Main settings table, for string application persistent data
---
-CREATE TABLE IF NOT EXISTS settings_tab (
- path TEXT NOT NULL UNIQUE PRIMARY KEY,
- value TEXT
-);
-
---
--- Dictionary table, for variables with fixed set of values
---
-CREATE TABLE IF NOT EXISTS dictionary_tab (
- id INTEGER PRIMARY KEY,
- path TEXT NOT NULL,
- value TEXT,
- CONSTRAINT dictionary_unique
- UNIQUE (path, value) ON CONFLICT REPLACE
- );
-
---
--- Table contains information who to inform
--- about changes in values.
---
-CREATE TABLE IF NOT EXISTS notifications_tab (
- id INTEGER PRIMARY KEY,
- path TEXT NOT NULL,
- service TEXT,
- CONSTRAINT notification_unique
- UNIQUE(path, service)
- );
-
-CREATE TABLE IF NOT EXISTS settings_changed_tab(
-
- id INTEGER PRIMARY KEY,
- path TEXT NOT NULL,
- value TEXT,
- CONSTRAINT changed_unique
- UNIQUE(path ) ON CONFLICT REPLACE
-);
-
-CREATE TRIGGER IF NOT EXISTS on_update UPDATE OF value ON settings_tab
-WHEN new.value <> old.value BEGIN INSERT OR REPLACE INTO settings_changed_tab (path, value) VALUES (new.path,new.value); END;
-
M image/user/db/settings_v2_002.sql => image/user/db/settings_v2_002.sql +42 -19
@@ 1,20 1,43 @@
--- ----------- insert default values ----------------------
-INSERT OR REPLACE INTO dictionary_tab (path, value) VALUES
- ('system/phone_mode', 'online'),
- ('system/phone_mode', 'offline'),
- ('system/phone_mode', 'dnd');
+--
+-- Main settings table, for string application persistent data
+--
+CREATE TABLE IF NOT EXISTS settings_tab (
+ path TEXT NOT NULL UNIQUE PRIMARY KEY,
+ value TEXT
+);
+
+--
+-- Dictionary table, for variables with fixed set of values
+--
+CREATE TABLE IF NOT EXISTS dictionary_tab (
+ id INTEGER PRIMARY KEY,
+ path TEXT NOT NULL,
+ value TEXT,
+ CONSTRAINT dictionary_unique
+ UNIQUE (path, value) ON CONFLICT REPLACE
+ );
+
+--
+-- Table contains information who to inform
+-- about changes in values.
+--
+CREATE TABLE IF NOT EXISTS notifications_tab (
+ id INTEGER PRIMARY KEY,
+ path TEXT NOT NULL,
+ service TEXT,
+ CONSTRAINT notification_unique
+ UNIQUE(path, service)
+ );
+
+CREATE TABLE IF NOT EXISTS settings_changed_tab(
+
+ id INTEGER PRIMARY KEY,
+ path TEXT NOT NULL,
+ value TEXT,
+ CONSTRAINT changed_unique
+ UNIQUE(path ) ON CONFLICT REPLACE
+);
+
+CREATE TRIGGER IF NOT EXISTS on_update UPDATE OF value ON settings_tab
+WHEN new.value <> old.value BEGIN INSERT OR REPLACE INTO settings_changed_tab (path, value) VALUES (new.path,new.value); END;
--- ----------- insert default values -------------------
-INSERT OR IGNORE INTO settings_tab (path, value) VALUES
- ('system/phone_mode', 'online'),
- ('gs_time_format_12', '0'),
- ('gs_time_date_format', '1'),
- ('gs_active_sim', 'SIM1'),
- ('gs_lock_pass_hash', '3333'),
- ('gs_lock_time', '30000'),
- ('gs_display_language', 'English'),
- ('gs_input_language', 'English');
- ('bt_state', '0'),
- ('bt_device_visibility', '0'),
- ('bt_device_name', 'PurePhone'),
- ('bt_bonded_devices', '');
M module-apps/Application.cpp => module-apps/Application.cpp +11 -3
@@ 69,8 69,9 @@ namespace app
StartInBackground startInBackground,
uint32_t stackDepth,
sys::ServicePriority priority)
- : Service(name, parent, stackDepth, priority), default_window(gui::name::window::main_window),
- windowsStack(this), startInBackground{startInBackground}, settings(std::make_unique<settings::Settings>(this))
+ : Service(std::move(name), std::move(parent), stackDepth, priority),
+ default_window(gui::name::window::main_window), windowsStack(this), startInBackground{startInBackground},
+ callbackStorage{std::make_unique<CallbackStorage>()}, settings(std::make_unique<settings::Settings>(this))
{
keyTranslator = std::make_unique<gui::KeyInputSimpleTranslation>();
busChannels.push_back(sys::BusChannels::ServiceCellularNotifications);
@@ 85,7 86,10 @@ namespace app
[this](std::string value) { timeFormatChanged(value); });
}
- Application::~Application() = default;
+ Application::~Application() noexcept
+ {
+ windowsStack.windows.clear();
+ }
Application::State Application::getState()
{
@@ 701,4 705,8 @@ namespace app
return timeFormat12;
}
+ void Application::cancelCallbacks(AsyncCallbackReceiver::Ptr receiver)
+ {
+ callbackStorage->removeAll(receiver);
+ }
} /* namespace app */
M module-apps/Application.hpp => module-apps/Application.hpp +10 -2
@@ 3,8 3,10 @@
#pragma once
+#include "AsyncTask.hpp"
#include "Audio/AudioCommon.hpp" // for Volume, Play...
#include "Audio/Profiles/Profile.hpp" // for Profile, Pro...
+#include "CallbackStorage.hpp"
#include "Service/Bus.hpp" // for Bus
#include "Service/Common.hpp" // for ReturnCodes
#include "Service/Message.hpp" // for MessagePointer
@@ 117,7 119,7 @@ namespace app
/// This is template for creating new applications. Main difference between Application and service is that:
/// 1. Application has access to GUI and Input
/// 2. Application lifetime is managed with app::manager::ApplicationManager
- class Application : public sys::Service
+ class Application : public sys::Service, public AsyncCallbacksDeleter
{
public:
/// state in which application is right now
@@ 187,7 189,7 @@ namespace app
uint32_t stackDepth = 4096,
sys::ServicePriority priority = sys::ServicePriority::Idle);
- virtual ~Application();
+ virtual ~Application() noexcept;
Application::State getState();
void setState(State st);
@@ 282,6 284,8 @@ namespace app
void toggleTorch(bsp::torch::ColourTemperature temperature);
+ void cancelCallbacks(AsyncCallbackReceiver::Ptr receiver) override;
+
/// @defgroup Application Application static functions
/// All this functions are meant to be used in ApplicationManager only
/// @note consider moving these as private elements of ApplicationManager i.e. under names
@@ 351,6 355,10 @@ namespace app
/// set of rendering commands will carry information to GUI service that system needs to be closed. After
/// displaying the screen GUI will notify application manager to request system shutdown.
bool shutdownInProgress = false;
+ /// Storage for asynchronous tasks callbacks.
+ std::unique_ptr<CallbackStorage> callbackStorage;
+ friend class AsyncTask; // Async tasks need access to application internals, e.g. callback storage, to make
+ // their API simple.
/// informs self that there was key press
/// used to provide a way for long press/multipress handling in application
A module-apps/AsyncTask.cpp => module-apps/AsyncTask.cpp +68 -0
@@ 0,0 1,68 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "AsyncTask.hpp"
+#include "Application.hpp"
+
+namespace app
+{
+ AsyncCallbackReceiver::AsyncCallbackReceiver(AsyncCallbacksDeleter *deleter) noexcept : deleter{deleter}
+ {}
+
+ AsyncCallbackReceiver::~AsyncCallbackReceiver()
+ {
+ if (deleter != nullptr) {
+ deleter->cancelCallbacks(this);
+ }
+ }
+
+ void AsyncTask::execute(Application *application, AsyncCallbackReceiver::Ptr receiverObject)
+ {
+ const auto requestId = onExecute(application);
+ application->callbackStorage->registerCallback(requestId, receiverObject);
+ }
+
+ std::unique_ptr<AsyncQuery> AsyncQuery::createFromQuery(std::unique_ptr<db::Query> &&query,
+ db::Interface::Name target)
+ {
+ return std::make_unique<AsyncQuery>(std::move(query), target);
+ }
+
+ AsyncQuery::AsyncQuery(std::unique_ptr<db::Query> &&query, db::Interface::Name target) noexcept
+ : query{std::move(query)}, target{target}
+ {}
+
+ void AsyncQuery::setCallback(std::unique_ptr<db::QueryListener> &&listener) noexcept
+ {
+ query->setQueryListener(std::move(listener));
+ }
+
+ void AsyncQuery::setCallback(db::QueryCallbackFunction &&callback) noexcept
+ {
+ query->setQueryListener(db::QueryCallback::fromFunction(std::move(callback)));
+ }
+
+ RequestId AsyncQuery::onExecute(Application *application)
+ {
+ const auto [_, id] = DBServiceAPI::GetQuery(application, target, std::move(query));
+ return id;
+ }
+
+ auto NullCallback::execute() -> bool
+ {
+ // Nothing to do.
+ return false;
+ }
+
+ QueryCallback::QueryCallback(db::QueryResponse *response) : response{response}
+ {}
+
+ auto QueryCallback::execute() -> bool
+ {
+ const auto result = response->getResult();
+ if (result != nullptr && result->hasListener()) {
+ return result->handle();
+ }
+ return false;
+ }
+} // namespace app
A module-apps/AsyncTask.hpp => module-apps/AsyncTask.hpp +108 -0
@@ 0,0 1,108 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <service-db/DBServiceAPI.hpp>
+#include <service-db/QueryMessage.hpp>
+
+#include <cstdint>
+
+namespace app
+{
+ class Application; // Forward declaration
+
+ using RequestId = std::uint64_t;
+ class AsyncCallbackReceiver;
+
+ class AsyncCallbacksDeleter
+ {
+ public:
+ virtual ~AsyncCallbacksDeleter() noexcept = default;
+
+ virtual void cancelCallbacks(AsyncCallbackReceiver *receiver) = 0;
+ };
+
+ class AsyncCallbackReceiver
+ {
+ public:
+ using Ptr = AsyncCallbackReceiver *;
+
+ explicit AsyncCallbackReceiver(AsyncCallbacksDeleter *deleter) noexcept;
+ virtual ~AsyncCallbackReceiver() = 0;
+
+ private:
+ AsyncCallbacksDeleter *deleter;
+ };
+
+ /**
+ * Executes an operation on the sender's thread and saves the info about the receiver context for callback use.
+ * Intended to use in order to avoid callbacks called on invalid receiver contexts.
+ */
+ class AsyncTask
+ {
+ public:
+ virtual ~AsyncTask() noexcept = default;
+
+ /**
+ * Executes a task in application's thread and saves the receiver object.
+ * @param application Application
+ * @param receiverObject The context of receiver
+ */
+ void execute(Application *application, AsyncCallbackReceiver::Ptr receiverObject);
+
+ private:
+ /**
+ * The specific operation that is to be executed by a task.
+ * @param application Application
+ * @return Request identifier, to be matched with a response.
+ */
+ [[nodiscard]] virtual auto onExecute(Application *application) -> RequestId = 0;
+ };
+
+ /**
+ * A database query that is using the mechanism of AsyncTask.
+ */
+ class AsyncQuery : public AsyncTask
+ {
+ public:
+ [[nodiscard]] static auto createFromQuery(std::unique_ptr<db::Query> &&query, db::Interface::Name target)
+ -> std::unique_ptr<AsyncQuery>;
+
+ AsyncQuery(std::unique_ptr<db::Query> &&query, db::Interface::Name target) noexcept;
+
+ void setCallback(db::QueryCallbackFunction &&callback) noexcept;
+ void setCallback(std::unique_ptr<db::QueryListener> &&listener) noexcept;
+
+ private:
+ [[nodiscard]] auto onExecute(Application *application) -> RequestId override;
+
+ std::unique_ptr<db::Query> query;
+ db::Interface::Name target;
+ };
+
+ class AsyncCallback
+ {
+ public:
+ virtual ~AsyncCallback() noexcept = default;
+
+ [[nodiscard]] virtual auto execute() -> bool = 0;
+ };
+
+ class NullCallback : public AsyncCallback
+ {
+ public:
+ [[nodiscard]] auto execute() -> bool override;
+ };
+
+ class QueryCallback : public AsyncCallback
+ {
+ public:
+ explicit QueryCallback(db::QueryResponse *response);
+
+ [[nodiscard]] auto execute() -> bool override;
+
+ private:
+ db::QueryResponse *response;
+ };
+} // namespace app
M module-apps/CMakeLists.txt => module-apps/CMakeLists.txt +7 -1
@@ 15,6 15,8 @@ set( SOURCES
"Application.cpp"
"GuiTimer.cpp"
"WindowsFactory.cpp"
+ "AsyncTask.cpp"
+ "CallbackStorage.cpp"
"windows/AppWindow.cpp"
"windows/OptionWindow.cpp"
"windows/Options.cpp"
@@ 30,7 32,7 @@ set( SOURCES
"widgets/BrightnessBox.cpp"
"widgets/ModesBox.cpp"
"widgets/BarGraph.cpp"
- )
+)
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${BOARD_SOURCES})
@@ 156,3 158,7 @@ target_compile_features(${PROJECT_NAME} PUBLIC
cxx_std_14
)
+
+if (${ENABLE_TESTS})
+ add_subdirectory(tests)
+endif()
A module-apps/CallbackStorage.cpp => module-apps/CallbackStorage.cpp +59 -0
@@ 0,0 1,59 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "CallbackStorage.hpp"
+
+#include "MessageType.hpp"
+
+#include <algorithm>
+
+namespace app
+{
+ CallbackStorage::CallbackEntry::CallbackEntry(RequestId id, AsyncCallbackReceiver::Ptr receiver) noexcept
+ : id{id}, receiver{receiver}
+ {}
+
+ auto CallbackStorage::getCallback(sys::ResponseMessage *response) -> std::unique_ptr<AsyncCallback>
+ {
+ if (containsCallbackFor(response)) {
+ remove(response);
+ return toCallback(response);
+ }
+ return std::make_unique<NullCallback>();
+ }
+
+ auto CallbackStorage::containsCallbackFor(sys::ResponseMessage *response) const noexcept -> bool
+ {
+ return std::any_of(
+ entries.begin(), entries.end(), [response](const auto &entry) { return entry->id == response->uniID; });
+ }
+
+ void CallbackStorage::remove(sys::ResponseMessage *response)
+ {
+ const auto it = std::remove_if(
+ entries.begin(), entries.end(), [response](auto &&entry) { return entry->id == response->uniID; });
+ entries.erase(it, entries.end());
+ }
+
+ auto CallbackStorage::toCallback(sys::ResponseMessage *response) -> std::unique_ptr<AsyncCallback>
+ {
+ if (response->responseTo == MessageType::DBQuery) {
+ if (auto queryResponse = dynamic_cast<db::QueryResponse *>(response); queryResponse != nullptr) {
+ return std::make_unique<QueryCallback>(queryResponse);
+ }
+ }
+ return std::make_unique<NullCallback>();
+ }
+
+ void CallbackStorage::registerCallback(RequestId id, AsyncCallbackReceiver::Ptr receiver)
+ {
+ entries.push_back(std::make_unique<CallbackEntry>(id, receiver));
+ }
+
+ void CallbackStorage::removeAll(AsyncCallbackReceiver::Ptr receiver)
+ {
+ const auto it = std::remove_if(
+ entries.begin(), entries.end(), [receiver](const auto &entry) { return entry->receiver == receiver; });
+ entries.erase(it, entries.end());
+ }
+} // namespace app
A module-apps/CallbackStorage.hpp => module-apps/CallbackStorage.hpp +39 -0
@@ 0,0 1,39 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "AsyncTask.hpp"
+
+#include "module-sys/Service/Message.hpp"
+
+#include <memory>
+#include <list>
+
+namespace app
+{
+ class CallbackStorage
+ {
+ public:
+ [[nodiscard]] auto containsCallbackFor(sys::ResponseMessage *response) const noexcept -> bool;
+ [[nodiscard]] auto getCallback(sys::ResponseMessage *response) -> std::unique_ptr<AsyncCallback>;
+
+ void registerCallback(RequestId id, AsyncCallbackReceiver::Ptr receiver);
+ void removeAll(AsyncCallbackReceiver::Ptr receiver);
+
+ private:
+ [[nodiscard]] static auto toCallback(sys::ResponseMessage *response) -> std::unique_ptr<AsyncCallback>;
+
+ void remove(sys::ResponseMessage *response);
+
+ struct CallbackEntry
+ {
+ public:
+ CallbackEntry(RequestId id, AsyncCallbackReceiver::Ptr receiver) noexcept;
+
+ RequestId id;
+ AsyncCallbackReceiver::Ptr receiver;
+ };
+ std::list<std::unique_ptr<CallbackEntry>> entries;
+ };
+} // namespace app
M module-apps/application-alarm-clock/ApplicationAlarmClock.cpp => module-apps/application-alarm-clock/ApplicationAlarmClock.cpp +2 -11
@@ 51,17 51,8 @@ namespace app
// handle database response
if (resp != nullptr) {
handled = true;
- switch (resp->responseTo) {
- case MessageType::DBQuery: {
- if (auto queryResponse = dynamic_cast<db::QueryResponse *>(resp)) {
- auto result = queryResponse->getResult();
- if (result->hasListener() && result->handle()) {
- refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
- }
- }
- } break;
- default:
- break;
+ if (auto command = callbackStorage->getCallback(resp); command->execute()) {
+ refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
}
}
if (handled) {
M module-apps/application-alarm-clock/models/AlarmsRepository.cpp => module-apps/application-alarm-clock/models/AlarmsRepository.cpp +12 -7
@@ 9,15 9,19 @@
#include <module-db/queries/alarms/QueryAlarmsTurnOffAll.hpp>
#include <service-db/DBServiceAPI.hpp>
+#include "AsyncTask.hpp"
+
namespace app::alarmClock
{
- AlarmsDBRepository::AlarmsDBRepository(Application *application) : application{application}
+ AlarmsDBRepository::AlarmsDBRepository(Application *application)
+ : AsyncCallbackReceiver{application}, application{application}
{}
void AlarmsDBRepository::getLimited(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback)
{
auto query = std::make_unique<db::query::alarms::GetLimited>(offset, limit);
- query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Alarms);
+ task->setCallback([callback](auto response) {
auto result = dynamic_cast<db::query::alarms::GetLimitedResult *>(response);
if (result == nullptr) {
return false;
@@ 26,15 30,16 @@ namespace app::alarmClock
callback(result->getResult(), result->getCountResult());
}
return true;
- }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Alarms, std::move(query));
+ });
+ task->execute(application, this);
}
template <class QueryType, class ResultType>
void AlarmsDBRepository::GetQuery(std::unique_ptr<QueryType> query,
const AbstractAlarmsRepository::OnResultCallback &callback)
{
- query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Alarms);
+ task->setCallback([callback](auto response) {
auto result = dynamic_cast<ResultType *>(response);
if (result == nullptr) {
return false;
@@ 43,8 48,8 @@ namespace app::alarmClock
callback(result->succeed());
}
return true;
- }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Alarms, std::move(query));
+ });
+ task->execute(application, this);
}
void AlarmsDBRepository::add(const AlarmsRecord &alarm, const AbstractAlarmsRepository::OnResultCallback &callback)
M module-apps/application-alarm-clock/models/AlarmsRepository.hpp => module-apps/application-alarm-clock/models/AlarmsRepository.hpp +1 -1
@@ 23,7 23,7 @@ namespace app::alarmClock
virtual void turnOffAll(const OnResultCallback &callback) = 0;
};
- class AlarmsDBRepository : public AbstractAlarmsRepository
+ class AlarmsDBRepository : public AbstractAlarmsRepository, public AsyncCallbackReceiver
{
public:
explicit AlarmsDBRepository(Application *app);
M module-apps/application-calendar/ApplicationCalendar.cpp => module-apps/application-calendar/ApplicationCalendar.cpp +2 -15
@@ 81,21 81,8 @@ namespace app
// handle database response
if (resp != nullptr) {
handled = true;
- switch (resp->responseTo) {
- case MessageType::DBQuery: {
- if (auto queryResponse = dynamic_cast<db::QueryResponse *>(resp)) {
- auto result = queryResponse->getResult();
- LOG_DEBUG("queryResponse != nullptr");
- if (result->hasListener()) {
- LOG_DEBUG("Has listener");
- if (result->handle()) {
- refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
- }
- }
- }
- } break;
- default:
- break;
+ if (auto command = callbackStorage->getCallback(resp); command->execute()) {
+ refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
}
}
if (handled) {
M module-apps/application-calendar/models/AllEventsModel.cpp => module-apps/application-calendar/models/AllEventsModel.cpp +4 -4
@@ 10,7 10,7 @@
#include <service-db/DBServiceAPI.hpp>
#include <queries/calendar/QueryEventsGetAllLimited.hpp>
-AllEventsModel::AllEventsModel(app::Application *app) : DatabaseModel(app)
+AllEventsModel::AllEventsModel(app::Application *app) : DatabaseModel(app), app::AsyncCallbackReceiver{app}
{
application = app;
assert(app != nullptr);
@@ 24,9 24,9 @@ unsigned int AllEventsModel::requestRecordsCount()
void AllEventsModel::requestRecords(const uint32_t offset, const uint32_t limit)
{
auto query = std::make_unique<db::query::events::GetAllLimited>(offset, limit);
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Events, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Events);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(application, this);
}
unsigned int AllEventsModel::getMinimalItemHeight() const
M module-apps/application-calendar/models/AllEventsModel.hpp => module-apps/application-calendar/models/AllEventsModel.hpp +3 -2
@@ 8,13 8,14 @@
#include <module-db/Interface/EventsRecord.hpp>
#include <module-db/Common/Query.hpp>
-class AllEventsModel : public app::DatabaseModel<EventsRecord>, public gui::ListItemProvider
+class AllEventsModel : public app::DatabaseModel<EventsRecord>,
+ public gui::ListItemProvider,
+ public app::AsyncCallbackReceiver
{
app::Application *application = nullptr;
public:
AllEventsModel(app::Application *app);
- virtual ~AllEventsModel() override = default;
void requestRecords(const uint32_t offset, const uint32_t limit) override;
bool updateRecords(std::vector<EventsRecord> records) override;
M module-apps/application-calendar/models/DayEventsModel.cpp => module-apps/application-calendar/models/DayEventsModel.cpp +5 -4
@@ 11,7 11,8 @@
#include <service-db/QueryMessage.hpp>
#include <module-db/queries/RecordQuery.hpp>
-DayEventsModel::DayEventsModel(app::Application *app) : DatabaseModel(app), application(app)
+DayEventsModel::DayEventsModel(app::Application *app)
+ : DatabaseModel(app), app::AsyncCallbackReceiver{app}, application(app)
{}
unsigned int DayEventsModel::requestRecordsCount()
@@ 27,9 28,9 @@ unsigned int DayEventsModel::getMinimalItemHeight() const
void DayEventsModel::requestRecords(const uint32_t offset, const uint32_t limit)
{
auto query = std::make_unique<db::query::events::GetFiltered>(filterFrom, filterTill, offset, limit);
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Events, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Events);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(application, this);
}
gui::ListItem *DayEventsModel::getItem(gui::Order order)
M module-apps/application-calendar/models/DayEventsModel.hpp => module-apps/application-calendar/models/DayEventsModel.hpp +3 -1
@@ 9,7 9,9 @@
#include <ListItemProvider.hpp>
#include <module-db/Interface/EventsRecord.hpp>
-class DayEventsModel : public app::DatabaseModel<EventsRecord>, public gui::ListItemProvider
+class DayEventsModel : public app::DatabaseModel<EventsRecord>,
+ public gui::ListItemProvider,
+ public app::AsyncCallbackReceiver
{
app::Application *application = nullptr;
std::string dayMonthTitle;
M module-apps/application-calendar/windows/CalendarMainWindow.cpp => module-apps/application-calendar/windows/CalendarMainWindow.cpp +8 -7
@@ 17,7 17,8 @@
namespace gui
{
- CalendarMainWindow::CalendarMainWindow(app::Application *app, const std::string &name) : AppWindow(app, name)
+ CalendarMainWindow::CalendarMainWindow(app::Application *app, const std::string &name)
+ : AppWindow(app, name), app::AsyncCallbackReceiver{app}
{
auto appCalendar = dynamic_cast<app::ApplicationCalendar *>(application);
assert(appCalendar != nullptr);
@@ 185,9 186,9 @@ namespace gui
assert(application != nullptr);
app->setEquivalentToEmptyWindow(EquivalentWindow::AllEventsWindow);
auto query = std::make_unique<db::query::events::GetAll>();
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Events, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Events);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(application, this);
return true;
}
@@ 202,9 203,9 @@ namespace gui
auto filter_till = TimePointFromYearMonthDay(date_till);
LOG_DEBUG("filter: %s", TimePointToString(filter_till).c_str());
auto query = std::make_unique<db::query::events::GetFiltered>(filter_from, filter_till);
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Events, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Events);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(application, this);
}
auto CalendarMainWindow::handleQueryResponse(db::QueryResult *queryResult) -> bool
M module-apps/application-calendar/windows/CalendarMainWindow.hpp => module-apps/application-calendar/windows/CalendarMainWindow.hpp +1 -2
@@ 22,7 22,7 @@ namespace db
namespace gui
{
- class CalendarMainWindow : public gui::AppWindow
+ class CalendarMainWindow : public gui::AppWindow, public app::AsyncCallbackReceiver
{
bool isDayEmpty[31];
uint32_t offsetFromTop = 0;
@@ 40,7 40,6 @@ namespace gui
public:
CalendarMainWindow(app::Application *app, const std::string &name);
- ~CalendarMainWindow() override = default;
void rebuild() override;
void refresh();
void filterRequest();
M module-apps/application-calllog/ApplicationCallLog.cpp => module-apps/application-calllog/ApplicationCallLog.cpp +2 -2
@@ 135,9 135,9 @@ namespace app
DBServiceAPI::GetQuery(this,
db::Interface::Name::Notifications,
std::make_unique<db::query::notifications::Clear>(NotificationsRecord::Key::Calls));
-
- return DBServiceAPI::GetQuery(
+ const auto [succeed, _] = DBServiceAPI::GetQuery(
this, db::Interface::Name::Calllog, std::make_unique<db::query::calllog::SetAllRead>());
+ return succeed;
}
} /* namespace app */
M module-apps/application-desktop/ApplicationDesktop.cpp => module-apps/application-desktop/ApplicationDesktop.cpp +7 -3
@@ 203,7 203,11 @@ namespace app
msg->interface == db::Interface::Name::SMS) &&
msg->type != db::Query::Type::Read) {
requestNotReadNotifications();
- windowsFactory.build(this, app::window::name::desktop_menu);
+ if (auto menuWindow = dynamic_cast<gui::MenuWindow *>(getWindow(app::window::name::desktop_menu));
+ menuWindow != nullptr) {
+ menuWindow->refresh();
+ return true;
+ }
}
return false;
@@ 257,15 261,15 @@ namespace app
bool ApplicationDesktop::requestNotSeenNotifications()
{
- return DBServiceAPI::GetQuery(
+ const auto [succeed, _] = DBServiceAPI::GetQuery(
this, db::Interface::Name::Notifications, std::make_unique<db::query::notifications::GetAll>());
+ return succeed;
}
bool ApplicationDesktop::requestNotReadNotifications()
{
notifications.notRead.Calls = DBServiceAPI::CalllogGetCount(this, EntryState::UNREAD);
notifications.notRead.SMS = DBServiceAPI::ThreadGetCount(this, EntryState::UNREAD);
-
return true;
}
M => +71 -20
@@ 24,16 24,33 @@ namespace style::design
inline const auto notify_dot_x = 80;
inline const auto notify_dot_y = (64 - 50) / 2;
inline const auto grid_offset = 20;
}; // namespace style::design
} // namespace style::design
namespace
{
static constexpr auto deepRefreshDot = "dot_12px_hard_alpha_W_G";
static constexpr auto fastRefreshDot = "dot_12px_hard_alpha_W_M";
gui::Image *buildThumbnail(gui::RefreshModes mode)
{
gui::Image *thumbnail =
new gui::Image(mode == gui::RefreshModes::GUI_REFRESH_DEEP ? deepRefreshDot : fastRefreshDot);
thumbnail->setPosition(style::design::notify_dot_x, style::design::notify_dot_y);
return thumbnail;
}
} // namespace
namespace gui
{
inline const auto APP_SETTINGS_NEW = "ApplicationSettingsNew";
Tile::Tile(UTF8 icon, std::string title, std::function<bool(Item &)> activatedCallback, unsigned int notifications)
Tile::Tile(UTF8 icon,
std::string title,
std::function<bool(Item &)> activatedCallback,
std::function<bool()> hasNotificationsCallback)
{
setSize(style::design::tile_w, style::design::tile_h);
auto *it = new gui::Item();
auto it = new gui::Item();
it->setSize(style::design::tile_w, style::design::tile_h - 2 * style::design::tile_margin);
it->setPosition(area().x, area().y + style::design::tile_margin);
@@ 48,10 65,21 @@ namespace gui
desc->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Bottom));
desc->setText(utils::localize.get(title));
if (notifications > 0) {
auto thumbnail = new gui::Image("dot_12px_hard_alpha_W_G");
thumbnail->setPosition(style::design::notify_dot_x, style::design::notify_dot_y);
it->addWidget(thumbnail);
if (hasNotificationsCallback != nullptr) {
onNotificationsChangeCallback =
[this, it, hasNotifications = std::move(hasNotificationsCallback)](gui::RefreshModes mode) -> bool {
if (hasNotifications() && notificationThumbnail == nullptr) {
notificationThumbnail = buildThumbnail(mode);
it->addWidget(notificationThumbnail);
return true;
}
else if (!hasNotifications() && notificationThumbnail != nullptr) {
it->erase(notificationThumbnail);
notificationThumbnail = nullptr;
}
return false;
};
onNotificationsChangeCallback(gui::RefreshModes::GUI_REFRESH_DEEP);
}
this->activatedCallback = activatedCallback;
@@ 59,9 87,16 @@ namespace gui
this->setPenFocusWidth(style::window::default_border_focus_w);
this->setEdges(RectangleEdge::Top | RectangleEdge::Bottom);
addWidget(it);
};
}
bool Tile::onNotificationsChange(gui::RefreshModes mode)
{
if (onNotificationsChangeCallback != nullptr) {
return onNotificationsChangeCallback(mode);
}
return false;
}
MenuPage::MenuPage(gui::Item *parent, UTF8 title, std::vector<Tile *> tiles) : title(title)
MenuPage::MenuPage(gui::Item *parent, UTF8 title, std::vector<Tile *> tiles) : title(std::move(title))
{
if (parent) {
parent->addWidget(this);
@@ 83,6 118,17 @@ namespace gui
}
}
bool MenuPage::refresh(gui::RefreshModes mode)
{
bool visibleStateChanged = false;
for (auto child : children) {
if (auto tile = dynamic_cast<Tile *>(child); tile != nullptr) {
visibleStateChanged |= tile->onNotificationsChange(mode);
}
}
return visibleStateChanged;
}
MenuWindow::MenuWindow(app::Application *app) : AppWindow(app, app::window::name::desktop_menu)
{
buildInterface();
@@ 150,7 196,8 @@ namespace gui
app::manager::actions::Launch,
std::make_unique<app::ApplicationLaunchData>("ApplicationCallLog"));
},
app->notifications.notRead.Calls},
[=]() { return app->notifications.notRead.Calls > 0; }},
new gui::Tile("menu_contacts_W_G",
"app_desktop_menu_contacts",
[=](gui::Item &item) {
@@ 168,7 215,7 @@ namespace gui
app::manager::actions::Launch,
std::make_unique<app::ApplicationLaunchData>("ApplicationMessages"));
},
app->notifications.notRead.SMS},
[=]() { return app->notifications.notRead.SMS > 0; }},
new gui::Tile{"menu_music_player_W_G",
"app_desktop_menu_music",
[=](gui::Item &item) {
@@ 185,15 232,12 @@ namespace gui
app::manager::actions::Launch,
std::make_unique<app::ApplicationLaunchData>("ApplicationMeditation"));
}},
new gui::Tile{"menu_settings_W_G",
"app_desktop_menu_settings_new",
[=](gui::Item &item) {
new gui::Tile{"menu_settings_W_G", "app_desktop_menu_settings_new", [=](gui::Item &item) {
return app::manager::Controller::sendAction(
application,
app::manager::actions::Launch,
std::make_unique<app::ApplicationLaunchData>(APP_SETTINGS_NEW));
}},
});
}}});
toolsMenu = new MenuPage(
this,
@@ 254,9 298,6 @@ namespace gui
toolsMenu = nullptr;
}
void MenuWindow::onBeforeShow(ShowMode mode, SwitchData *data)
{}
bool MenuWindow::onInput(const InputEvent &inputEvent)
{
if ((inputEvent.state == InputEvent::State::keyReleasedShort) && (inputEvent.keyCode == KeyCode::KEY_RF) &&
@@ 278,7 319,17 @@ namespace gui
setTitle(page->title);
setFocusItem(page);
application->refreshWindow(gui::RefreshModes::GUI_REFRESH_DEEP);
}
void MenuWindow::refresh()
{
if (application->getCurrentWindow() == this) {
if (mainMenu->refresh(RefreshModes::GUI_REFRESH_FAST)) {
application->refreshWindow(RefreshModes::GUI_REFRESH_FAST);
}
}
else {
mainMenu->refresh(RefreshModes::GUI_REFRESH_DEEP);
}
}
} /* namespace gui */
M => +9 -2
@@ 22,7 22,13 @@ namespace gui
Tile(UTF8 icon,
std::string title,
std::function<bool(Item &)> activatedCallback,
unsigned int notifications = 0);
std::function<bool()> hasNotificationsCallback = nullptr);
bool onNotificationsChange(gui::RefreshModes);
private:
std::function<bool(gui::RefreshModes)> onNotificationsChangeCallback = nullptr;
gui::Image *notificationThumbnail = nullptr;
};
class MenuPage : public gui::GridLayout
@@ 36,6 42,7 @@ namespace gui
MenuPage(gui::Item *parent, UTF8 title, std::vector<Tile *> tiles);
/// set child which should be selected on start of desktop
void setFirstTimeSelection();
bool refresh(gui::RefreshModes mode);
};
class MenuWindow : public AppWindow
@@ 48,7 55,6 @@ namespace gui
public:
MenuWindow(app::Application *app);
void onBeforeShow(ShowMode mode, SwitchData *data) override;
bool onInput(const InputEvent &inputEvent) override;
void rebuild() override;
@@ 56,6 62,7 @@ namespace gui
void destroyInterface() override;
void switchMenu(MenuPage *page);
void refresh();
private:
void invalidate() noexcept;
M module-apps/application-desktop/windows/PowerOffWindow.cpp => module-apps/application-desktop/windows/PowerOffWindow.cpp +1 -0
@@ 173,6 173,7 @@ namespace gui
sys::SystemManager::CloseSystem(application);
return true;
};
+ powerOffTimer->start();
application->connect(std::move(powerOffTimer), this);
}
M module-apps/application-messages/ApplicationMessages.cpp => module-apps/application-messages/ApplicationMessages.cpp +36 -46
@@ 38,7 38,7 @@
namespace app
{
ApplicationMessages::ApplicationMessages(std::string name, std::string parent, StartInBackground startInBackground)
- : Application(name, parent, startInBackground, 4096 * 2)
+ : Application(name, parent, startInBackground, 4096 * 2), AsyncCallbackReceiver{this}
{
busChannels.push_back(sys::BusChannels::ServiceDBNotifications);
addActionReceiver(manager::actions::CreateSms, [this](auto &&data) {
@@ 51,9 51,6 @@ namespace app
});
}
- ApplicationMessages::~ApplicationMessages()
- {}
-
// Invoked upon receiving data message
sys::MessagePointer ApplicationMessages::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
{
@@ 84,26 81,8 @@ namespace app
// handle database response
if (resp != nullptr) {
handled = true;
- switch (resp->responseTo) {
- case MessageType::DBThreadGetLimitOffset:
- [[fallthrough]];
- case MessageType::DBSMSTemplateGetLimitOffset:
- if (getCurrentWindow()->onDatabaseMessage(resp)) {
- refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
- }
- break;
- case MessageType::DBQuery:
- if (auto queryResponse = dynamic_cast<db::QueryResponse *>(resp)) {
- auto result = queryResponse->getResult();
- if (result && result->hasListener()) {
- if (result->handle()) {
- refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
- }
- }
- }
- break;
- default:
- break;
+ if (auto command = callbackStorage->getCallback(resp); command->execute()) {
+ refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
}
}
@@ 193,7 172,8 @@ namespace app
LOG_DEBUG("Removing thread: %" PRIu32, record->ID);
auto query = std::make_unique<ContactGetByID>(record->contactID, true);
- query->setQueryListener(db::QueryCallback::fromFunction([this, record](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Contact);
+ task->setCallback([this, record](auto response) {
auto result = dynamic_cast<ContactGetByIDResult *>(response);
if (result != nullptr) {
const auto &contact = result->getResult();
@@ 206,8 186,9 @@ namespace app
return true;
}
return false;
- }));
- return DBServiceAPI::GetQuery(this, db::Interface::Name::Contact, std::move(query));
+ });
+ task->execute(this, this);
+ return true;
}
bool ApplicationMessages::onRemoveSmsThreadConfirmed(const ThreadRecord &record)
@@ 216,7 197,8 @@ namespace app
using db::query::ThreadRemoveResult;
auto query = std::make_unique<ThreadRemove>(record.ID);
- query->setQueryListener(db::QueryCallback::fromFunction([this, threadId = record.ID](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
+ task->setCallback([this, threadId = record.ID](auto response) {
const auto result = dynamic_cast<ThreadRemoveResult *>(response);
if ((result != nullptr) && result->success()) {
switchWindow(gui::name::window::main_window);
@@ 224,12 206,8 @@ namespace app
}
LOG_ERROR("ThreadRemove id=%" PRIu32 " failed", threadId);
return false;
- }));
-
- if (const auto ok = DBServiceAPI::GetQuery(this, db::Interface::Name::SMSThread, std::move(query)); !ok) {
- LOG_ERROR("Unable to query DBServiceAPI");
- return false;
- }
+ });
+ task->execute(this, this);
return true;
}
@@ 255,11 233,13 @@ namespace app
using db::query::ThreadGetByIDResult;
auto query = std::make_unique<SMSRemove>(record.ID);
- query->setQueryListener(db::QueryCallback::fromFunction([this, record](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMS);
+ task->setCallback([this, record](auto response) {
auto result = dynamic_cast<SMSRemoveResult *>(response);
if (result != nullptr && result->getResults()) {
auto query = std::make_unique<ThreadGetByID>(record.threadID);
- query->setQueryListener(db::QueryCallback::fromFunction([this](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
+ task->setCallback([this](auto response) {
const auto result = dynamic_cast<ThreadGetByIDResult *>(response);
if (result != nullptr) {
const auto thread = result->getRecord();
@@ 272,14 252,15 @@ namespace app
return true;
}
return false;
- }));
- return DBServiceAPI::GetQuery(this, db::Interface::Name::SMSThread, std::move(query));
+ });
+ task->execute(this, this);
+ return true;
}
LOG_ERROR("sSMSRemove id=%" PRIu32 " failed", record.ID);
return false;
- }));
-
- return DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::move(query));
+ });
+ task->execute(this, this);
+ return true;
}
bool ApplicationMessages::searchEmpty(const std::string &query)
@@ 320,7 301,9 @@ namespace app
record.date = utils::time::getCurrentTimestamp().getTime();
using db::query::SMSUpdate;
- return DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSUpdate>(record));
+ const auto [succeed, _] =
+ DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSUpdate>(record));
+ return succeed;
}
std::pair<SMSRecord, bool> ApplicationMessages::createDraft(const utils::PhoneNumber::View &number,
@@ 335,14 318,17 @@ namespace app
record.date = utils::time::getCurrentTimestamp().getTime();
using db::query::SMSAdd;
- const auto success = DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSAdd>(record));
+ const auto [success, _] =
+ DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSAdd>(record));
return std::make_pair(record, success);
}
bool ApplicationMessages::removeDraft(const SMSRecord &record)
{
using db::query::SMSRemove;
- return DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSRemove>(record.ID));
+ const auto [succeed, _] =
+ DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSRemove>(record.ID));
+ return succeed;
}
bool ApplicationMessages::sendSms(const utils::PhoneNumber::View &number, const UTF8 &body)
@@ 358,7 344,9 @@ namespace app
record.date = utils::time::getCurrentTimestamp().getTime();
using db::query::SMSAdd;
- return DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSAdd>(record));
+ const auto [succeed, _] =
+ DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSAdd>(record));
+ return succeed;
}
bool ApplicationMessages::resendSms(const SMSRecord &record)
@@ 370,7 358,9 @@ namespace app
// the the bottom, but this is correct
using db::query::SMSUpdate;
- return DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSUpdate>(resendRecord));
+ const auto [succeed, _] =
+ DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::make_unique<SMSUpdate>(resendRecord));
+ return succeed;
}
bool ApplicationMessages::handleSendSmsFromThread(const utils::PhoneNumber::View &number, const UTF8 &body)
M module-apps/application-messages/ApplicationMessages.hpp => module-apps/application-messages/ApplicationMessages.hpp +1 -2
@@ 35,13 35,12 @@ namespace app
inline constexpr auto name_messages = "ApplicationMessages";
- class ApplicationMessages : public app::Application
+ class ApplicationMessages : public app::Application, public app::AsyncCallbackReceiver
{
public:
ApplicationMessages(std::string name = name_messages,
std::string parent = {},
StartInBackground startInBackground = {false});
- virtual ~ApplicationMessages();
sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
sys::ReturnCodes InitHandler() override;
M module-apps/application-messages/models/BaseThreadsRecordModel.cpp => module-apps/application-messages/models/BaseThreadsRecordModel.cpp +0 -5
@@ 21,8 21,3 @@ bool BaseThreadsRecordModel::updateRecords(std::vector<ThreadListStruct> records
list->onProviderDataUpdate();
return true;
}
-
-void BaseThreadsRecordModel::requestRecords(uint32_t offset, uint32_t limit)
-{
- DBServiceAPI::ThreadGetLimitOffset(application, offset, limit);
-}
M module-apps/application-messages/models/BaseThreadsRecordModel.hpp => module-apps/application-messages/models/BaseThreadsRecordModel.hpp +0 -1
@@ 33,7 33,6 @@ class BaseThreadsRecordModel : public app::DatabaseModel<ThreadListStruct>, publ
unsigned int requestRecordsCount() override;
bool updateRecords(std::vector<ThreadListStruct> records) override;
- void requestRecords(const uint32_t offset, const uint32_t limit) override;
app::Application *getApplication(void)
{
M module-apps/application-messages/models/SMSTemplateModel.cpp => module-apps/application-messages/models/SMSTemplateModel.cpp +4 -4
@@ 8,7 8,7 @@
#include <service-db/DBServiceAPI.hpp>
#include <module-db/queries/messages/templates/QuerySMSTemplateGetForList.hpp>
-SMSTemplateModel::SMSTemplateModel(app::Application *app) : DatabaseModel(app)
+SMSTemplateModel::SMSTemplateModel(app::Application *app) : DatabaseModel(app), app::AsyncCallbackReceiver{app}
{}
unsigned int SMSTemplateModel::requestRecordsCount()
@@ 56,9 56,9 @@ gui::ListItem *SMSTemplateModel::getItem(gui::Order order)
void SMSTemplateModel::requestRecords(const uint32_t offset, const uint32_t limit)
{
auto query = std::make_unique<db::query::SMSTemplateGetForList>(offset, limit);
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::SMSTemplate, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSTemplate);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(application, this);
}
auto SMSTemplateModel::handleQueryResponse(db::QueryResult *queryResult) -> bool
M module-apps/application-messages/models/SMSTemplateModel.hpp => module-apps/application-messages/models/SMSTemplateModel.hpp +3 -2
@@ 9,12 9,13 @@
#include <Application.hpp>
#include <ListItemProvider.hpp>
-class SMSTemplateModel : public app::DatabaseModel<SMSTemplateRecord>, public gui::ListItemProvider
+class SMSTemplateModel : public app::DatabaseModel<SMSTemplateRecord>,
+ public gui::ListItemProvider,
+ public app::AsyncCallbackReceiver
{
public:
SMSTemplateModel() = delete;
SMSTemplateModel(app::Application *app);
- virtual ~SMSTemplateModel() = default;
unsigned int requestRecordsCount() override;
bool updateRecords(std::vector<SMSTemplateRecord> records) override;
M module-apps/application-messages/models/SMSThreadModel.cpp => module-apps/application-messages/models/SMSThreadModel.cpp +4 -4
@@ 12,7 12,7 @@
#include "SMSThreadModel.hpp"
#include "ListView.hpp"
-SMSThreadModel::SMSThreadModel(app::Application *app) : DatabaseModel(app)
+SMSThreadModel::SMSThreadModel(app::Application *app) : DatabaseModel(app), app::AsyncCallbackReceiver{app}
{
smsInput = new gui::SMSInputWidget(application);
}
@@ 52,9 52,9 @@ unsigned int SMSThreadModel::requestRecordsCount()
void SMSThreadModel::requestRecords(uint32_t offset, uint32_t limit)
{
auto query = std::make_unique<db::query::SMSGetForList>(smsThreadID, offset, limit, numberID);
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::SMS, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMS);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(application, this);
}
bool SMSThreadModel::updateRecords(std::vector<SMSRecord> records)
M module-apps/application-messages/models/SMSThreadModel.hpp => module-apps/application-messages/models/SMSThreadModel.hpp +3 -1
@@ 9,7 9,9 @@
#include "Interface/SMSRecord.hpp"
#include <application-messages/widgets/SMSInputWidget.hpp>
-class SMSThreadModel : public app::DatabaseModel<SMSRecord>, public gui::ListItemProvider
+class SMSThreadModel : public app::DatabaseModel<SMSRecord>,
+ public gui::ListItemProvider,
+ public app::AsyncCallbackReceiver
{
public:
unsigned int smsThreadID = 0;
M module-apps/application-messages/models/ThreadsModel.cpp => module-apps/application-messages/models/ThreadsModel.cpp +4 -4
@@ 15,7 15,7 @@
#include <module-db/queries/messages/threads/QueryThreadsGetForList.hpp>
#include <service-db/DBServiceAPI.hpp>
-ThreadsModel::ThreadsModel(app::Application *app) : BaseThreadsRecordModel(app)
+ThreadsModel::ThreadsModel(app::Application *app) : BaseThreadsRecordModel(app), app::AsyncCallbackReceiver{app}
{}
auto ThreadsModel::getMinimalItemHeight() const -> unsigned int
@@ 66,9 66,9 @@ auto ThreadsModel::getItem(gui::Order order) -> gui::ListItem *
void ThreadsModel::requestRecords(uint32_t offset, uint32_t limit)
{
auto query = std::make_unique<db::query::ThreadsGetForList>(offset, limit);
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(getApplication(), db::Interface::Name::SMSThread, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(getApplication(), this);
}
auto ThreadsModel::handleQueryResponse(db::QueryResult *queryResult) -> bool
M module-apps/application-messages/models/ThreadsModel.hpp => module-apps/application-messages/models/ThreadsModel.hpp +1 -1
@@ 6,7 6,7 @@
#include <module-db/Interface/ContactRecord.hpp>
#include "BaseThreadsRecordModel.hpp"
-class ThreadsModel : public BaseThreadsRecordModel
+class ThreadsModel : public BaseThreadsRecordModel, public app::AsyncCallbackReceiver
{
public:
explicit ThreadsModel(app::Application *app);
M module-apps/application-messages/models/ThreadsSearchResultsModel.cpp => module-apps/application-messages/models/ThreadsSearchResultsModel.cpp +5 -4
@@ 14,7 14,8 @@
namespace gui::model
{
- ThreadsSearchResultsModel::ThreadsSearchResultsModel(app::Application *app) : BaseThreadsRecordModel(app)
+ ThreadsSearchResultsModel::ThreadsSearchResultsModel(app::Application *app)
+ : BaseThreadsRecordModel(app), app::AsyncCallbackReceiver{app}
{}
auto ThreadsSearchResultsModel::getMinimalItemHeight() const -> unsigned int
@@ 45,9 46,9 @@ namespace gui::model
{
if (std::string(textToSearch).compare("") != 0) {
auto query = std::make_unique<db::query::ThreadsSearchForList>(textToSearch, offset, limit);
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(getApplication(), db::Interface::Name::SMSThread, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(application, this);
}
}
M module-apps/application-messages/models/ThreadsSearchResultsModel.hpp => module-apps/application-messages/models/ThreadsSearchResultsModel.hpp +1 -1
@@ 9,7 9,7 @@
namespace gui::model
{
- class ThreadsSearchResultsModel : public BaseThreadsRecordModel
+ class ThreadsSearchResultsModel : public BaseThreadsRecordModel, public app::AsyncCallbackReceiver
{
UTF8 textToSearch;
M module-apps/application-messages/windows/MessagesMainWindow.cpp => module-apps/application-messages/windows/MessagesMainWindow.cpp +6 -4
@@ 26,7 26,8 @@
namespace gui
{
- MessagesMainWindow::MessagesMainWindow(app::Application *app) : AppWindow(app, gui::name::window::main_window)
+ MessagesMainWindow::MessagesMainWindow(app::Application *app)
+ : AppWindow(app, gui::name::window::main_window), app::AsyncCallbackReceiver{app}
{
buildInterface();
}
@@ 112,7 113,8 @@ namespace gui
if (pdata != nullptr) {
using db::query::ThreadGetByContactID;
auto query = std::make_unique<ThreadGetByContactID>(pdata->result->ID);
- query->setQueryListener(db::QueryCallback::fromFunction([app = application](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Contact);
+ task->setCallback([app = application](auto response) {
using db::query::ThreadGetByContactIDResult;
const auto result = dynamic_cast<ThreadGetByContactIDResult *>(response);
if ((result != nullptr) && result->getRecord().has_value()) {
@@ 123,8 125,8 @@ namespace gui
}
LOG_FATAL("No thread and thread not created!");
return false;
- }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Contact, std::move(query));
+ });
+ task->execute(application, this);
}
}
M module-apps/application-messages/windows/MessagesMainWindow.hpp => module-apps/application-messages/windows/MessagesMainWindow.hpp +1 -1
@@ 17,7 17,7 @@
namespace gui
{
- class MessagesMainWindow : public AppWindow
+ class MessagesMainWindow : public AppWindow, public app::AsyncCallbackReceiver
{
protected:
Image *leftArrowImage = nullptr;
M module-apps/application-messages/windows/NewMessage.cpp => module-apps/application-messages/windows/NewMessage.cpp +17 -13
@@ 49,7 49,8 @@ namespace gui
std::unique_ptr<NewMessageWindow::MessageMemento> NewMessageWindow::memento =
std::make_unique<NewMessageWindow::MessageMemento>();
- NewMessageWindow::NewMessageWindow(app::Application *app) : AppWindow(app, name::window::new_sms)
+ NewMessageWindow::NewMessageWindow(app::Application *app)
+ : AppWindow(app, name::window::new_sms), app::AsyncCallbackReceiver{app}
{
buildInterface();
}
@@ 311,7 312,8 @@ namespace gui
{
auto number = contactRecord.numbers.front().number;
auto query = std::make_unique<db::query::ThreadGetByContactID>(contactRecord.ID);
- query->setQueryListener(db::QueryCallback::fromFunction([this, number](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
+ task->setCallback([this, number](auto response) {
const auto result = dynamic_cast<db::query::ThreadGetByContactIDResult *>(response);
if (result == nullptr) {
return false;
@@ 323,15 325,16 @@ namespace gui
return true;
}
return addDraftToExistingThread(thread->ID, number, message->getText());
- }));
-
- return DBServiceAPI::GetQuery(application, db::Interface::Name::SMSThread, std::move(query));
+ });
+ task->execute(application, this);
+ return true;
}
auto NewMessageWindow::addDraft(const utils::PhoneNumber &number) -> bool
{
auto query = std::make_unique<db::query::ThreadGetByNumber>(number.getView());
- query->setQueryListener(db::QueryCallback::fromFunction([this, number](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMSThread);
+ task->setCallback([this, number](auto response) {
const auto result = dynamic_cast<db::query::ThreadGetByNumberResult *>(response);
if (result == nullptr) {
return false;
@@ 343,9 346,9 @@ namespace gui
return true;
}
return addDraftToExistingThread(thread.ID, number.getView(), message->getText());
- }));
-
- return DBServiceAPI::GetQuery(application, db::Interface::Name::SMSThread, std::move(query));
+ });
+ task->execute(application, this);
+ return true;
}
auto NewMessageWindow::addDraftToExistingThread(unsigned int threadId,
@@ 353,7 356,8 @@ namespace gui
const UTF8 &text) -> bool
{
auto query = std::make_unique<db::query::SMSGetLastByThreadID>(threadId);
- query->setQueryListener(db::QueryCallback::fromFunction([this, number](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::SMS);
+ task->setCallback([this, number](auto response) {
const auto result = dynamic_cast<db::query::SMSGetLastByThreadIDResult *>(response);
if (result == nullptr) {
return false;
@@ 366,9 370,9 @@ namespace gui
}
storeMessageDraft(number, message->getText());
return true;
- }));
-
- return DBServiceAPI::GetQuery(application, db::Interface::Name::SMS, std::move(query));
+ });
+ task->execute(application, this);
+ return true;
}
void NewMessageWindow::storeMessageDraft(const utils::PhoneNumber::View &number, const UTF8 &text)
M module-apps/application-messages/windows/NewMessage.hpp => module-apps/application-messages/windows/NewMessage.hpp +2 -1
@@ 6,6 6,7 @@
#include <chrono>
#include <string>
+#include <AsyncTask.hpp>
#include <AppWindow.hpp>
#include <PhoneNumber.hpp>
#include <widgets/Text.hpp>
@@ 14,7 15,7 @@
namespace gui
{
- class NewMessageWindow : public AppWindow
+ class NewMessageWindow : public AppWindow, public app::AsyncCallbackReceiver
{
public:
explicit NewMessageWindow(app::Application *app);
M module-apps/application-messages/windows/SMSThreadViewWindow.cpp => module-apps/application-messages/windows/SMSThreadViewWindow.cpp +5 -4
@@ 24,7 24,8 @@ namespace gui
{
SMSThreadViewWindow::SMSThreadViewWindow(app::Application *app)
- : AppWindow(app, name::window::thread_view), smsModel{std::make_shared<SMSThreadModel>(this->application)}
+ : AppWindow(app, name::window::thread_view), app::AsyncCallbackReceiver{app},
+ smsModel{std::make_shared<SMSThreadModel>(app)}
{
AppWindow::buildInterface();
setTitle(utils::localize.get("app_messages_title_main"));
@@ 124,9 125,9 @@ namespace gui
auto SMSThreadViewWindow::requestContactName(unsigned int contactID) -> void
{
auto query = std::make_unique<db::query::ContactGetByID>(contactID, true);
- query->setQueryListener(db::QueryCallback::fromFunction(
- [this](auto response) { return handleContactNameQueryResponse(response); }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Contact, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Contact);
+ task->setCallback([this](auto response) { return handleContactNameQueryResponse(response); });
+ task->execute(application, this);
}
auto SMSThreadViewWindow::handleContactNameQueryResponse(db::QueryResult *queryResult) -> bool
M module-apps/application-messages/windows/SMSThreadViewWindow.hpp => module-apps/application-messages/windows/SMSThreadViewWindow.hpp +1 -1
@@ 14,7 14,7 @@
namespace gui
{
- class SMSThreadViewWindow : public AppWindow
+ class SMSThreadViewWindow : public AppWindow, public app::AsyncCallbackReceiver
{
private:
std::shared_ptr<SMSThreadModel> smsModel;
M module-apps/application-messages/windows/SearchStart.cpp => module-apps/application-messages/windows/SearchStart.cpp +5 -0
@@ 22,6 22,11 @@ namespace gui
body->setBoundingBox(bodySize());
addWidget(body);
auto text = inputBox(this, utils::localize.get("common_search_uc"), "search");
+ text->setInputMode(new InputMode(
+ {InputMode::ABC, InputMode::abc, InputMode::digit},
+ [=](const UTF8 &Text) { application->getCurrentWindow()->bottomBarTemporaryMode(Text); },
+ [=]() { application->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); },
+ [=]() { application->getCurrentWindow()->selectSpecialCharacter(); }));
inputCallback = [=](Item &, const InputEvent &inputEvent) -> bool {
auto app = dynamic_cast<app::ApplicationMessages *>(application);
M module-apps/application-notes/ApplicationNotes.cpp => module-apps/application-notes/ApplicationNotes.cpp +2 -12
@@ 56,18 56,8 @@ namespace app
}
if (resp != nullptr) {
- switch (resp->responseTo) {
- case MessageType::DBQuery:
- if (auto queryResponse = dynamic_cast<db::QueryResponse *>(resp); queryResponse != nullptr) {
- if (auto result = queryResponse->getResult(); result && result->hasListener()) {
- if (result->handle()) {
- refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
- }
- }
- }
- break;
- default:
- break;
+ if (auto command = callbackStorage->getCallback(resp); command->execute()) {
+ refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
}
return msgHandled();
}
M module-apps/application-notes/model/NotesRepository.cpp => module-apps/application-notes/model/NotesRepository.cpp +18 -13
@@ 12,13 12,15 @@
namespace app::notes
{
- NotesDBRepository::NotesDBRepository(Application *application) : application{application}
+ NotesDBRepository::NotesDBRepository(Application *application)
+ : app::AsyncCallbackReceiver{application}, application{application}
{}
void NotesDBRepository::get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback)
{
auto query = std::make_unique<db::query::QueryNotesGet>(offset, limit);
- query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Notes);
+ task->setCallback([callback](auto response) {
auto result = dynamic_cast<db::query::NotesGetResult *>(response);
if (result == nullptr) {
return false;
@@ 27,14 29,15 @@ namespace app::notes
callback(result->getRecords(), result->getCount());
}
return true;
- }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Notes, std::move(query));
+ });
+ task->execute(application, this);
}
void NotesDBRepository::getByText(const std::string &text, const OnFilteredCallback &callback)
{
auto query = std::make_unique<db::query::QueryNotesGetByText>(text);
- query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Notes);
+ task->setCallback([callback](auto response) {
auto result = dynamic_cast<db::query::NotesGetByTextResult *>(response);
if (result == nullptr) {
return false;
@@ 43,14 46,15 @@ namespace app::notes
callback(result->getRecords());
}
return true;
- }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Notes, std::move(query));
+ });
+ task->execute(application, this);
}
void NotesDBRepository::save(const NotesRecord ¬e, const OnResultCallback &callback)
{
auto query = std::make_unique<db::query::QueryNoteStore>(note);
- query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Notes);
+ task->setCallback([callback](auto response) {
auto result = dynamic_cast<db::query::NoteStoreResult *>(response);
if (result == nullptr) {
return false;
@@ 59,14 63,15 @@ namespace app::notes
callback(result->succeed());
}
return true;
- }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Notes, std::move(query));
+ });
+ task->execute(application, this);
}
void NotesDBRepository::remove(const NotesRecord ¬e, const OnResultCallback &callback)
{
auto query = std::make_unique<db::query::QueryNoteRemove>(note.ID);
- query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Notes);
+ task->setCallback([callback](auto response) {
auto result = dynamic_cast<db::query::NoteRemoveResult *>(response);
if (result == nullptr) {
return false;
@@ 75,7 80,7 @@ namespace app::notes
callback(result->succeed());
}
return true;
- }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Notes, std::move(query));
+ });
+ task->execute(application, this);
}
} // namespace app::notes
M module-apps/application-notes/model/NotesRepository.hpp => module-apps/application-notes/model/NotesRepository.hpp +1 -1
@@ 26,7 26,7 @@ namespace app::notes
virtual void remove(const NotesRecord ¬e, const OnResultCallback &callback) = 0;
};
- class NotesDBRepository : public AbstractNotesRepository
+ class NotesDBRepository : public AbstractNotesRepository, public app::AsyncCallbackReceiver
{
public:
explicit NotesDBRepository(Application *application);
M module-apps/application-phonebook/ApplicationPhonebook.cpp => module-apps/application-phonebook/ApplicationPhonebook.cpp +2 -16
@@ 80,22 80,8 @@ namespace app
// handle database response
if (resp != nullptr) {
handled = true;
- switch (resp->responseTo) {
- case MessageType::DBQuery: {
-
- if (auto queryResponse = dynamic_cast<db::QueryResponse *>(resp)) {
- auto result = queryResponse->getResult();
-
- if (result->hasListener()) {
- if (result->handle()) {
- refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
- }
- }
- }
-
- } break;
- default:
- break;
+ if (auto command = callbackStorage->getCallback(resp); command->execute()) {
+ refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
}
}
M module-apps/application-phonebook/models/PhonebookModel.cpp => module-apps/application-phonebook/models/PhonebookModel.cpp +7 -5
@@ 3,6 3,8 @@
#include <application-phonebook/ApplicationPhonebook.hpp>
#include <application-phonebook/windows/PhonebookContactDetails.hpp>
+#include <AsyncTask.hpp>
+
#include "ListView.hpp"
#include "PhonebookModel.hpp"
@@ 24,8 26,8 @@ PhonebookModel::PhonebookModel(app::Application *app,
std::string filter,
std::uint32_t groupFilter,
std::uint32_t displayMode)
- : DatabaseModel(app), queryFilter(std::move(filter)), queryGroupFilter(std::move(groupFilter)),
- queryDisplayMode(std::move(displayMode))
+ : DatabaseModel(app), app::AsyncCallbackReceiver{app}, queryFilter(std::move(filter)),
+ queryGroupFilter(std::move(groupFilter)), queryDisplayMode(std::move(displayMode))
{}
auto PhonebookModel::requestRecordsCount() -> unsigned int
@@ 60,9 62,9 @@ void PhonebookModel::requestRecords(const uint32_t offset, const uint32_t limit)
{
auto query =
std::make_unique<db::query::ContactGet>(offset, limit, queryFilter, queryGroupFilter, queryDisplayMode);
- query->setQueryListener(
- db::QueryCallback::fromFunction([this](auto response) { return handleQueryResponse(response); }));
- DBServiceAPI::GetQuery(application, db::Interface::Name::Contact, std::move(query));
+ auto task = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Contact);
+ task->setCallback([this](auto response) { return handleQueryResponse(response); });
+ task->execute(application, this);
}
auto PhonebookModel::requestLetterMap() -> ContactsMapData
M module-apps/application-phonebook/models/PhonebookModel.hpp => module-apps/application-phonebook/models/PhonebookModel.hpp +3 -2
@@ 17,7 17,9 @@
#include <string>
-class PhonebookModel : public app::DatabaseModel<ContactRecord>, public gui::ListItemProvider
+class PhonebookModel : public app::DatabaseModel<ContactRecord>,
+ public gui::ListItemProvider,
+ public app::AsyncCallbackReceiver
{
private:
std::string queryFilter;
@@ 31,7 33,6 @@ class PhonebookModel : public app::DatabaseModel<ContactRecord>, public gui::Lis
std::string filter = "",
std::uint32_t groupFilter = 0,
std::uint32_t displayMode = 0);
- ~PhonebookModel() override = default;
// virtual methods from DatabaseModel
auto updateRecords(std::vector<ContactRecord> records) -> bool override;
M module-apps/application-phonebook/windows/PhonebookNewContact.cpp => module-apps/application-phonebook/windows/PhonebookNewContact.cpp +21 -8
@@ 143,22 143,35 @@ namespace gui
{
LOG_DEBUG("%s", __FUNCTION__);
if (!contact->isTemporary()) {
- auto err = DBServiceAPI::verifyContact(application, *contact);
- LOG_INFO("Contact data verification result: \"%s\"", DBServiceAPI::getVerificationErrorString(err).c_str());
- switch (err) {
- case DBServiceAPI::noError:
+ auto result = DBServiceAPI::verifyContact(application, *contact);
+ switch (result) {
+ case DBServiceAPI::ContactVerificationResult::success:
break;
- case DBServiceAPI::emptyContactError:
+ case DBServiceAPI::ContactVerificationResult::emptyContact:
return false;
- case DBServiceAPI::primaryNumberError:
+ case DBServiceAPI::ContactVerificationResult::primaryNumberDuplicate:
showDialogDuplicatedNumber(contact->numbers[0].number);
return false;
- case DBServiceAPI::secondaryNumberError:
+ case DBServiceAPI::ContactVerificationResult::secondaryNumberDuplicate:
showDialogDuplicatedNumber(contact->numbers[1].number);
return false;
- case DBServiceAPI::speedDialError:
+ case DBServiceAPI::ContactVerificationResult::speedDialDuplicate:
showDialogDuplicatedSpeedDialNumber();
return false;
+ case DBServiceAPI::ContactVerificationResult::temporaryContactExists:
+ std::unique_ptr<ContactRecord> tempContact;
+ assert(!contact->numbers.empty());
+ for (auto number : contact->numbers) {
+ if (number.number.getEntered().size() > 0) {
+ tempContact = DBServiceAPI::MatchContactByPhoneNumber(application, number.number);
+ if (tempContact != nullptr) {
+ contact->ID = tempContact->ID;
+ contactAction = ContactAction::EditTemporary;
+ break;
+ }
+ }
+ }
+ return false;
}
}
else {
M module-apps/application-phonebook/windows/PhonebookSearch.cpp => module-apps/application-phonebook/windows/PhonebookSearch.cpp +33 -38
@@ 3,10 3,9 @@
#include "PhonebookSearch.hpp"
#include "application-phonebook/ApplicationPhonebook.hpp"
+#include "application-phonebook/data/PhonebookItemData.hpp"
#include "widgets/InputBox.hpp"
-#include <Utils.hpp>
-
namespace gui
{
PhonebookSearch::PhonebookSearch(app::Application *app) : AppWindow(app, gui::window::name::search)
@@ 22,6 21,12 @@ namespace gui
setTitle(utils::localize.get("app_phonebook_title_main"));
inputField = inputBox(this, utils::localize.get("common_search_uc"), "search");
+ inputField->setInputMode(new InputMode(
+ {InputMode::ABC, InputMode::abc, InputMode::digit},
+ [=](const UTF8 &Text) { application->getCurrentWindow()->bottomBarTemporaryMode(Text); },
+ [=]() { application->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); },
+ [=]() { application->getCurrentWindow()->selectSpecialCharacter(); }));
+
bottomBar->setActive(BottomBar::Side::LEFT, false);
bottomBar->setActive(BottomBar::Side::CENTER, true);
bottomBar->setActive(BottomBar::Side::RIGHT, true);
@@ 32,60 37,50 @@ namespace gui
setFocusItem(inputField);
}
- void PhonebookSearch::rebuild()
- {}
-
- void PhonebookSearch::destroyInterface()
- {
- AppWindow::destroyInterface();
- }
-
- void PhonebookSearch::onBeforeShow(ShowMode mode, SwitchData *data)
- {
- inputField->setText("");
- setFocusItem(inputField);
- }
-
- bool PhonebookSearch::handleSwitchData(SwitchData *data)
+ auto PhonebookSearch::handleSwitchData(SwitchData *data) -> bool
{
if (data == nullptr) {
LOG_ERROR("Received null pointer");
return false;
}
- PhonebookSearchQuery *item = dynamic_cast<PhonebookSearchQuery *>(data);
+ auto item = dynamic_cast<PhonebookSearchQuery *>(data);
+ assert(item != nullptr);
inputField->setText(item->getQuery());
return true;
}
- bool PhonebookSearch::onInput(const InputEvent &inputEvent)
+ void PhonebookSearch::onBeforeShow(ShowMode mode, SwitchData *data)
+ {
+ inputField->clear();
+ setFocusItem(inputField);
+ }
+
+ auto PhonebookSearch::onInput(const InputEvent &inputEvent) -> bool
{
- bool ret = AppWindow::onInput(inputEvent);
- if (ret) {
- return ret;
+ if (AppWindow::onInput(inputEvent)) {
+ return true;
}
-
- if ((inputEvent.state != InputEvent::State::keyReleasedShort) &&
- ((inputEvent.state != InputEvent::State::keyReleasedLong))) {
+ if (!inputEvent.isShortPress()) {
+ return false;
+ }
+ if (!inputEvent.is(gui::KeyCode::KEY_ENTER)) {
return false;
}
- if (inputEvent.keyCode == KeyCode::KEY_ENTER) {
- std::string searchFilter = utils::trim(inputField->getText());
-
- if (searchFilter.size() > 0) {
- auto app = dynamic_cast<app::ApplicationPhonebook *>(application);
- if (app == nullptr) {
- LOG_ERROR("Failed to get phonebook application.");
- return false;
- }
+ std::string searchFilter = utils::trim(inputField->getText());
+ if (searchFilter.empty()) {
+ return false;
+ }
- app->onSearchRequest(searchFilter);
- return true;
- }
+ auto app = dynamic_cast<app::ApplicationPhonebook *>(application);
+ if (app == nullptr) {
+ LOG_ERROR("Failed to get phonebook application.");
+ return false;
}
- return ret;
+ app->onSearchRequest(searchFilter);
+ return true;
}
} // namespace gui
M module-apps/application-phonebook/windows/PhonebookSearch.hpp => module-apps/application-phonebook/windows/PhonebookSearch.hpp +7 -13
@@ 3,11 3,8 @@
#pragma once
-#include "application-phonebook/data/PhonebookItemData.hpp"
-#include "application-phonebook/models/PhonebookModel.hpp"
-#include "application-phonebook/widgets/PhonebookListView.hpp"
-
#include <AppWindow.hpp>
+#include <ContactRecord.hpp>
#include <Text.hpp>
namespace gui
@@ 15,19 12,16 @@ namespace gui
class PhonebookSearch : public AppWindow
{
public:
- PhonebookSearch(app::Application *app);
- ~PhonebookSearch() override = default;
+ explicit PhonebookSearch(app::Application *app);
- bool onInput(const InputEvent &inputEvent) override;
- void onBeforeShow(ShowMode mode, SwitchData *data) override;
- bool handleSwitchData(SwitchData *data) override;
- void rebuild() override;
+ private:
void buildInterface() override;
- void destroyInterface() override;
+ auto handleSwitchData(SwitchData *data) -> bool override;
+ void onBeforeShow(ShowMode mode, SwitchData *data) override;
+ auto onInput(const InputEvent &inputEvent) -> bool override;
- private:
- Text *inputField = nullptr;
std::shared_ptr<ContactRecord> contact = nullptr;
+ Text *inputField = nullptr;
};
} /* namespace gui */
M module-apps/application-settings-new/ApplicationSettings.cpp => module-apps/application-settings-new/ApplicationSettings.cpp +53 -2
@@ 5,6 5,7 @@
#include "windows/AddDeviceWindow.hpp"
#include "windows/AllDevicesWindow.hpp"
+#include "windows/APNSettingsWindow.hpp"
#include "windows/BluetoothWindow.hpp"
#include "windows/SettingsMainWindow.hpp"
#include "windows/DisplayAndKeypadWindow.hpp"
@@ 32,6 33,11 @@
#include <service-cellular/CellularServiceAPI.hpp>
#include <service-db/Settings.hpp>
#include <module-services/service-bluetooth/service-bluetooth/messages/Status.hpp>
+#include <service-bluetooth/messages/BondedDevices.hpp>
+#include <service-bluetooth/messages/DeviceName.hpp>
+#include <application-settings-new/data/BondedDevicesData.hpp>
+#include <service-db/agents/settings/SystemSettings.hpp>
+#include <application-settings-new/data/PhoneNameData.hpp>
namespace app
{
@@ 51,11 57,15 @@ namespace app
}
settings->registerValueChange(settings::operators_on,
[this](const std::string &value) { operatorOnChanged(value); });
+
+ settings->registerValueChange(::settings::Cellular::volte_on,
+ [this](const std::string &value) { volteChanged(value); });
}
ApplicationSettingsNew::~ApplicationSettingsNew()
{
settings->unregisterValueChange(settings::operators_on);
+ settings->unregisterValueChange(::settings::Cellular::volte_on);
}
// Invoked upon receiving data message
@@ 91,7 101,7 @@ namespace app
currentWindow->rebuild();
}
}
- else if (auto responseStatusMsg = dynamic_cast<message::bluetooth::ResponseStatus *>(msgl);
+ else if (auto responseStatusMsg = dynamic_cast<::message::bluetooth::ResponseStatus *>(msgl);
nullptr != responseStatusMsg) {
if (gui::window::name::bluetooth == getCurrentWindow()->getName()) {
auto btStatusData = std::make_unique<gui::BluetoothStatusData>(responseStatusMsg->getStatus());
@@ 99,7 109,7 @@ namespace app
}
}
- return std::make_shared<sys::ResponseMessage>();
+ return sys::MessageNone{};
}
// Invoked during initialization
@@ 112,6 122,25 @@ namespace app
return ret;
}
+ connect(typeid(::message::bluetooth::ResponseDeviceName), [&](sys::Message *msg) {
+ auto responseDeviceNameMsg = static_cast<::message::bluetooth::ResponseDeviceName *>(msg);
+ if (gui::window::name::phone_name == getCurrentWindow()->getName()) {
+ auto phoneNameData = std::make_unique<gui::PhoneNameData>(responseDeviceNameMsg->getName());
+ switchWindow(gui::window::name::phone_name, std::move(phoneNameData));
+ }
+ return sys::MessageNone{};
+ });
+
+ connect(typeid(::message::bluetooth::ResponseBondedDevices), [&](sys::Message *msg) {
+ auto responseBondedDevicesMsg = static_cast<::message::bluetooth::ResponseBondedDevices *>(msg);
+ if (gui::window::name::all_devices == getCurrentWindow()->getName()) {
+ auto bondedDevicesData =
+ std::make_unique<gui::BondedDevicesData>(responseBondedDevicesMsg->getDevices());
+ switchWindow(gui::window::name::all_devices, std::move(bondedDevicesData));
+ }
+ return sys::MessageNone{};
+ });
+
createUserInterface();
setActiveWindow(gui::name::window::main_window);
@@ 165,6 194,9 @@ namespace app
return std::make_unique<gui::NetworkWindow>(
app, static_cast<ApplicationSettingsNew *>(app), static_cast<ApplicationSettingsNew *>(app));
});
+ windowsFactory.attach(gui::window::name::apn_settings, [](Application *app, const std::string &name) {
+ return std::make_unique<gui::APNSettingsWindow>(app);
+ });
windowsFactory.attach(gui::window::name::messages, [](Application *app, const std::string &name) {
return std::make_unique<gui::MessagesWindow>(app);
});
@@ 224,4 256,23 @@ namespace app
operatorsOn = value;
settings->setValue(settings::operators_on, std::to_string(value));
}
+
+ void ApplicationSettingsNew::setVoLTEOn(bool value)
+ {
+ voLteStateOn = value;
+ CellularServiceAPI::SetVoLTE(this, voLteStateOn);
+ };
+
+ bool ApplicationSettingsNew::getVoLTEOn() const noexcept
+ {
+ return voLteStateOn;
+ }
+
+ void ApplicationSettingsNew::volteChanged(const std::string &value)
+ {
+ if (!value.empty()) {
+ voLteStateOn = utils::getNumericValue<bool>(value);
+ }
+ }
+
} /* namespace app */
M module-apps/application-settings-new/ApplicationSettings.hpp => module-apps/application-settings-new/ApplicationSettings.hpp +7 -0
@@ 15,6 15,7 @@ namespace gui::window::name
inline constexpr auto add_device = "AddDevice";
inline constexpr auto network = "Network";
+ inline constexpr auto apn_settings = "APNSettings";
inline constexpr auto phone_modes = "PhoneModes";
inline constexpr auto apps_and_tools = "AppsAndTools";
inline constexpr auto security = "Security";
@@ 64,6 65,8 @@ namespace app
virtual ~OperatorsSettings() = default;
virtual void setOperatorsOn(bool value) = 0;
[[nodiscard]] virtual bool getOperatorsOn() const noexcept = 0;
+ virtual void setVoLTEOn(bool value) = 0;
+ [[nodiscard]] virtual bool getVoLTEOn() const noexcept = 0;
};
}; // namespace settingsInterface
@@ 93,12 96,16 @@ namespace app
void operatorOnChanged(const std::string &value);
void setOperatorsOn(bool value) override;
bool getOperatorsOn() const noexcept override;
+ void setVoLTEOn(bool value) override;
+ bool getVoLTEOn() const noexcept override;
+ void volteChanged(const std::string &value);
private:
Store::GSM::SIM selectedSim = Store::GSM::get()->selected;
std::string selectedSimNumber = {};
bsp::Board board = bsp::Board::none;
bool operatorsOn = false;
+ bool voLteStateOn = false;
};
template <> struct ManifestTraits<ApplicationSettingsNew>
M module-apps/application-settings-new/CMakeLists.txt => module-apps/application-settings-new/CMakeLists.txt +1 -0
@@ 19,6 19,7 @@ target_sources( ${PROJECT_NAME}
windows/SettingsMainWindow.cpp
windows/AddDeviceWindow.cpp
windows/AllDevicesWindow.cpp
+ windows/APNSettingsWindow.cpp
windows/BaseSettingsWindow.cpp
windows/BluetoothWindow.cpp
windows/FontSizeWindow.cpp
A module-apps/application-settings-new/data/BondedDevicesData.hpp => module-apps/application-settings-new/data/BondedDevicesData.hpp +24 -0
@@ 0,0 1,24 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <Device.hpp>
+
+namespace gui
+{
+
+ class BondedDevicesData : public SwitchData
+ {
+ public:
+ explicit BondedDevicesData(std::vector<Devicei> devices) : devices(std::move(devices))
+ {}
+ [[nodiscard]] auto getDevices() const noexcept -> const std::vector<Devicei> &
+ {
+ return devices;
+ }
+
+ private:
+ const std::vector<Devicei> devices;
+ };
+} // namespace gui
A module-apps/application-settings-new/data/PhoneNameData.hpp => module-apps/application-settings-new/data/PhoneNameData.hpp +22 -0
@@ 0,0 1,22 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+#include <string>
+#include <SwitchData.hpp>
+namespace gui
+{
+ class PhoneNameData : public SwitchData
+ {
+ public:
+ explicit PhoneNameData(std::string name) : name(std::move(name))
+ {}
+ [[nodiscard]] auto getName() const -> const std::string &
+ {
+ return name;
+ }
+
+ private:
+ const std::string name;
+ };
+} // namespace gui
A module-apps/application-settings-new/windows/APNSettingsWindow.cpp => module-apps/application-settings-new/windows/APNSettingsWindow.cpp +61 -0
@@ 0,0 1,61 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "APNSettingsWindow.hpp"
+#include "application-settings-new/ApplicationSettings.hpp"
+#include "application-settings-new/widgets/SettingsStyle.hpp"
+#include "OptionSetting.hpp"
+
+#include <InputEvent.hpp>
+
+namespace gui
+{
+
+ APNSettingsWindow::APNSettingsWindow(app::Application *app) : OptionWindow(app, gui::window::name::apn_settings)
+ {
+ buildInterface();
+ }
+
+ auto APNSettingsWindow::onInput(const InputEvent &inputEvent) -> bool
+ {
+
+ if (inputEvent.isShortPress()) {
+ if (inputEvent.is(KeyCode::KEY_LEFT)) {
+ // switch to new/edit APN window
+ }
+ }
+
+ return AppWindow::onInput(inputEvent);
+ }
+ void APNSettingsWindow::buildInterface()
+ {
+ setTitle(utils::localize.get("app_settings_network_apn_settings"));
+
+ topBar->setActive(TopBar::Elements::SIGNAL, false);
+ topBar->setActive(TopBar::Elements::BATTERY, false);
+ topBar->setActive(TopBar::Elements::SIM, false);
+
+ leftArrowImage = new gui::Image(this,
+ style::settings::window::leftArrowImage::x,
+ style::settings::window::leftArrowImage::y,
+ style::settings::window::leftArrowImage::w,
+ style::settings::window::leftArrowImage::h,
+ "arrow_left");
+ crossImage = new gui::Image(this,
+ style::settings::window::crossImage::x,
+ style::settings::window::crossImage::y,
+ style::settings::window::crossImage::w,
+ style::settings::window::crossImage::h,
+ "cross");
+ emptyListIcon = new Icon(this,
+ 0,
+ style::header::height,
+ style::window_width,
+ style::window_height - style::header::height - style::footer::height,
+ "phonebook_empty_grey_circle_W_G",
+ utils::localize.get("app_settings_network_apn_settings_no_apns"));
+
+ bottomBar->setActive(gui::BottomBar::Side::CENTER, false);
+ }
+
+} // namespace gui
A module-apps/application-settings-new/windows/APNSettingsWindow.hpp => module-apps/application-settings-new/windows/APNSettingsWindow.hpp +24 -0
@@ 0,0 1,24 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "OptionWindow.hpp"
+#include <Icon.hpp>
+
+namespace gui
+{
+ class APNSettingsWindow : public OptionWindow
+ {
+ public:
+ APNSettingsWindow(app::Application *app);
+
+ private:
+ void buildInterface() override;
+ auto onInput(const InputEvent &inputEvent) -> bool override;
+
+ Image *leftArrowImage = nullptr;
+ Image *crossImage = nullptr;
+ Icon *emptyListIcon = nullptr;
+ };
+}; // namespace gui
M module-apps/application-settings-new/windows/AllDevicesWindow.cpp => module-apps/application-settings-new/windows/AllDevicesWindow.cpp +15 -10
@@ 8,18 8,29 @@
#include "OptionSetting.hpp"
#include "DialogMetadata.hpp"
#include "DialogMetadataMessage.hpp"
+#include <Constants.hpp>
#include <InputEvent.hpp>
#include <i18n/i18n.hpp>
#include <service-bluetooth/BluetoothMessage.hpp>
-
+#include <service-bluetooth/messages/BondedDevices.hpp>
+#include "application-settings-new/data/BondedDevicesData.hpp"
namespace gui
{
AllDevicesWindow::AllDevicesWindow(app::Application *app) : OptionWindow(app, gui::window::name::all_devices)
{
setTitle(utils::localize.get("app_settings_bluetooth_all_devices"));
- addOptions(allDevicesOptionsList());
+ sys::Bus::SendUnicast(
+ std::make_shared<::message::bluetooth::RequestBondedDevices>(), service::name::bluetooth, application);
+ }
+
+ void AllDevicesWindow::onBeforeShow(ShowMode mode, SwitchData *data)
+ {
+ clearOptions();
+ if (const auto newData = dynamic_cast<BondedDevicesData *>(data); newData != nullptr) {
+ addOptions(allDevicesOptionsList(newData->getDevices()));
+ }
}
auto AllDevicesWindow::onInput(const InputEvent &inputEvent) -> bool
@@ 44,17 55,11 @@ namespace gui
return AppWindow::onInput(inputEvent);
}
- auto AllDevicesWindow::allDevicesOptionsList() -> std::list<Option>
+ auto AllDevicesWindow::allDevicesOptionsList(const std::vector<Devicei> &devicesList) -> std::list<Option>
{
std::list<gui::Option> optionsList;
- std::vector<Devicei> devices{Devicei("Paired_device1"),
- Devicei("Paired_device2"),
- Devicei("Paired_device3"),
- Devicei("Paired_device4"),
- Devicei("Paired_device5")};
-
- for (const auto &device : devices) {
+ for (const auto &device : devicesList) {
optionsList.emplace_back(std::make_unique<gui::OptionSettings>(
device.name,
[=](gui::Item &item) {
M module-apps/application-settings-new/windows/AllDevicesWindow.hpp => module-apps/application-settings-new/windows/AllDevicesWindow.hpp +7 -6
@@ 1,21 1,22 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
#pragma once
#include "OptionWindow.hpp"
-
+#include <Device.hpp>
namespace gui
{
class AllDevicesWindow : public OptionWindow
{
public:
- AllDevicesWindow(app::Application *app);
- auto onInput(const InputEvent &inputEvent) -> bool override;
+ explicit AllDevicesWindow(app::Application *app);
private:
+ auto allDevicesOptionsList(const std::vector<Devicei> &vector) -> std::list<Option>;
+ void onBeforeShow(ShowMode mode, SwitchData *data) override;
+ auto onInput(const InputEvent &inputEvent) -> bool override;
Image *leftArrowImage = nullptr;
Image *crossImage = nullptr;
- auto allDevicesOptionsList() -> std::list<Option>;
};
-}; // namespace gui
+
+} // namespace gui
M module-apps/application-settings-new/windows/BluetoothWindow.cpp => module-apps/application-settings-new/windows/BluetoothWindow.cpp +5 -5
@@ 18,7 18,7 @@ namespace gui
topBar->setActive(TopBar::Elements::BATTERY, false);
topBar->setActive(TopBar::Elements::SIM, false);
sys::Bus::SendUnicast(
- std::make_shared<message::bluetooth::RequestStatus>(), service::name::bluetooth, application);
+ std::make_shared<::message::bluetooth::RequestStatus>(), service::name::bluetooth, application);
}
void BluetoothWindow::onBeforeShow(ShowMode mode, SwitchData *data)
@@ 110,16 110,16 @@ namespace gui
BluetoothStatus btStatus;
if (isBluetoothSwitchOn) {
- btStatus.state = BluetoothStatus::BluetoothState::On;
+ btStatus.state = BluetoothStatus::State::On;
}
else {
- btStatus.state = BluetoothStatus::BluetoothState::Off;
+ btStatus.state = BluetoothStatus::State::Off;
}
btStatus.visibility = isPhoneVisibilitySwitchOn;
- message::bluetooth::SetStatus setStatus(btStatus);
+ ::message::bluetooth::SetStatus setStatus(btStatus);
sys::Bus::SendUnicast(
- std::make_shared<message::bluetooth::SetStatus>(setStatus), service::name::bluetooth, application);
+ std::make_shared<::message::bluetooth::SetStatus>(setStatus), service::name::bluetooth, application);
}
void BluetoothWindow::rebuildOptionList()
M module-apps/application-settings-new/windows/BluetoothWindow.hpp => module-apps/application-settings-new/windows/BluetoothWindow.hpp +1 -1
@@ 29,7 29,7 @@ namespace gui
{}
[[nodiscard]] auto getState() const noexcept -> bool
{
- if (status.state == BluetoothStatus::BluetoothState::On) {
+ if (status.state == BluetoothStatus::State::On) {
return true;
}
return false;
M module-apps/application-settings-new/windows/InputLanguageWindow.cpp => module-apps/application-settings-new/windows/InputLanguageWindow.cpp +1 -1
@@ 20,7 20,7 @@ namespace gui
auto InputLanguageWindow::buildOptionsList() -> std::list<gui::Option>
{
std::list<gui::Option> optionsList;
- const auto &langList = loader.getAvailableDisplayLanguages();
+ const auto &langList = profiles.getAvailableInputLanguages();
for (const auto &lang : langList) {
optionsList.emplace_back(std::make_unique<gui::OptionSettings>(
lang,
M module-apps/application-settings-new/windows/InputLanguageWindow.hpp => module-apps/application-settings-new/windows/InputLanguageWindow.hpp +2 -1
@@ 4,6 4,7 @@
#pragma once
#include "BaseSettingsWindow.hpp"
+#include "Translator.hpp"
namespace gui
{
@@ 18,6 19,6 @@ namespace gui
private:
Language selectedLang;
- utils::LangLoader loader;
+ Profiles profiles;
};
} // namespace gui
M module-apps/application-settings-new/windows/NetworkWindow.cpp => module-apps/application-settings-new/windows/NetworkWindow.cpp +26 -2
@@ 14,8 14,7 @@ namespace gui
app::settingsInterface::SimParams *simParams,
app::settingsInterface::OperatorsSettings *operatorsSettings)
: OptionWindow(app, gui::window::name::network), simParams(simParams), operatorsSettings(operatorsSettings)
- {
- }
+ {}
void NetworkWindow::onBeforeShow(ShowMode m, SwitchData *d)
{
rebuild();
@@ 41,6 40,8 @@ namespace gui
break;
}
auto operatorsOn = operatorsSettings->getOperatorsOn();
+ auto voLteOn = operatorsSettings->getVoLTEOn();
+
optList.emplace_back(std::make_unique<gui::OptionSettings>(
utils::translateI18("app_settings_network_active_card") + ":" + simStr + " / " + phoneNumber,
[=](gui::Item &item) {
@@ 76,6 77,7 @@ namespace gui
nullptr,
nullptr,
operatorsOn ? RightItem::On : RightItem::Off));
+
if (!operatorsOn) {
optList.emplace_back(std::make_unique<gui::OptionSettings>(
utils::translateI18("app_settings_network_all_operators"),
@@ 97,6 99,28 @@ namespace gui
nullptr,
nullptr));
+ optList.emplace_back(std::make_unique<gui::OptionSettings>(
+ utils::translateI18("app_settings_network_voice_over_lte"),
+ [=](gui::Item &item) {
+ operatorsSettings->setVoLTEOn(!voLteOn);
+ rebuild();
+ return true;
+ },
+ nullptr,
+ nullptr,
+ voLteOn ? RightItem::On : RightItem::Off));
+
+ optList.emplace_back(std::make_unique<gui::OptionSettings>(
+ utils::translateI18("app_settings_network_apn_settings"),
+ [=](gui::Item &item) {
+ this->application->switchWindow(gui::window::name::apn_settings, nullptr);
+ return true;
+ },
+ nullptr,
+ nullptr,
+ RightItem::ArrowWhite,
+ true));
+
bottomBar->setText(BottomBar::Side::CENTER, utils::localize.get(style::strings::common::select));
topBar->setActive(TopBar::Elements::SIGNAL, false);
M module-apps/application-settings-new/windows/NetworkWindow.hpp => module-apps/application-settings-new/windows/NetworkWindow.hpp +1 -1
@@ 25,7 25,7 @@ namespace gui
private:
auto netOptList() -> std::list<gui::Option>;
app::settingsInterface::SimParams *simParams;
- void rebuild();
+ void rebuild() override;
app::settingsInterface::OperatorsSettings *operatorsSettings;
public:
M module-apps/application-settings-new/windows/PhoneNameWindow.cpp => module-apps/application-settings-new/windows/PhoneNameWindow.cpp +30 -1
@@ 5,7 5,12 @@
#include "application-settings-new/ApplicationSettings.hpp"
#include "widgets/InputBox.hpp"
+#include <service-bluetooth/Constants.hpp>
+#include <service-bluetooth/messages/DeviceName.hpp>
+#include <service-bluetooth/messages/SetDeviceName.hpp>
+
#include <Utils.hpp>
+#include <application-settings-new/data/PhoneNameData.hpp>
namespace gui
{
@@ 30,12 35,36 @@ namespace gui
bottomBar->setText(BottomBar::Side::CENTER, utils::localize.get(style::strings::common::save));
bottomBar->setText(BottomBar::Side::RIGHT, utils::localize.get(style::strings::common::back));
+ sys::Bus::SendUnicast(
+ std::make_shared<::message::bluetooth::RequestDeviceName>(), service::name::bluetooth, application);
+
setFocusItem(inputField);
}
void PhoneNameWindow::onBeforeShow(ShowMode mode, SwitchData *data)
{
inputField->clear();
- setFocusItem(inputField);
+ if (const auto newData = dynamic_cast<PhoneNameData *>(data); data != nullptr) {
+ inputField->setText(newData->getName());
+ }
+ }
+
+ auto PhoneNameWindow::onInput(const InputEvent &inputEvent) -> bool
+ {
+ if (AppWindow::onInput(inputEvent)) {
+ return true;
+ }
+
+ if (!inputEvent.isShortPress()) {
+ return false;
+ }
+
+ if (inputEvent.is(gui::KeyCode::KEY_ENTER) && !inputField->isEmpty()) {
+ auto result = std::make_shared<::message::bluetooth::SetDeviceName>(inputField->getText());
+ sys::Bus::SendUnicast(std::move(result), service::name::bluetooth, application);
+ return true;
+ }
+
+ return false;
}
} // namespace gui
M module-apps/application-settings-new/windows/PhoneNameWindow.hpp => module-apps/application-settings-new/windows/PhoneNameWindow.hpp +4 -6
@@ 1,10 1,7 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
#pragma once
-
#include "AppWindow.hpp"
-
#include <Text.hpp>
namespace gui
@@ 12,11 9,12 @@ namespace gui
class PhoneNameWindow : public AppWindow
{
public:
- PhoneNameWindow(app::Application *app);
- void buildInterface() override;
- void onBeforeShow(ShowMode mode, SwitchData *data) override;
+ explicit PhoneNameWindow(app::Application *app);
private:
+ void buildInterface() override;
+ void onBeforeShow(ShowMode mode, SwitchData *data) override;
+ auto onInput(const InputEvent &inputEvent) -> bool override;
Text *inputField = nullptr;
};
M module-apps/application-settings/windows/Info.cpp => module-apps/application-settings/windows/Info.cpp +1 -6
@@ 49,12 49,7 @@ namespace gui
addAlignedLabelWithValue(box, "GIT branch:", std::string(GIT_BRANCH));
addAlignedLabelWithValue(box, "Version:", std::string(VERSION));
{
- boot::BootConfig bootCfg;
- bootCfg.load();
- addAlignedLabelWithValue(box,
- "Bootloader:",
- (bootCfg.bootloader_version().empty() ? utils::localize.get("not available")
- : bootCfg.bootloader_version()));
+ addAlignedLabelWithValue(box, "Bootloader:", utils::localize.get("not available"));
}
std::string firmwareVersion;
CellularServiceAPI::GetFirmwareVersion(getApplication(), firmwareVersion);
A module-apps/tests/CMakeLists.txt => module-apps/tests/CMakeLists.txt +9 -0
@@ 0,0 1,9 @@
+add_catch2_executable(
+ NAME
+ callback-storage-test
+ SRCS
+ tests-main.cpp
+ test-CallbackStorage.cpp
+ LIBS
+ ${PROJECT_NAME}
+)
A module-apps/tests/test-CallbackStorage.cpp => module-apps/tests/test-CallbackStorage.cpp +70 -0
@@ 0,0 1,70 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+
+#include "CallbackStorage.hpp"
+
+#include <functional>
+
+using namespace app;
+
+class TestCallbacksDeleter : public AsyncCallbacksDeleter
+{
+ public:
+ explicit TestCallbacksDeleter(CallbackStorage &storage) : storage{storage}
+ {}
+
+ void cancelCallbacks(AsyncCallbackReceiver *receiver) override
+ {
+ storage.removeAll(receiver);
+ }
+
+ private:
+ CallbackStorage &storage;
+};
+
+class TestReceiver : public AsyncCallbackReceiver
+{
+ public:
+ TestReceiver(TestCallbacksDeleter *deleter = nullptr) : AsyncCallbackReceiver(deleter)
+ {}
+};
+
+TEST_CASE("CallbackStorageTests")
+{
+ CallbackStorage storage;
+
+ SECTION("Get callback")
+ {
+ constexpr auto MessageId = 1;
+ sys::ResponseMessage response{};
+ response.uniID = MessageId;
+
+ TestReceiver receiver;
+ storage.registerCallback(MessageId, &receiver);
+ REQUIRE(storage.containsCallbackFor(&response));
+
+ [[maybe_unused]] auto callback = storage.getCallback(&response);
+ REQUIRE(!storage.containsCallbackFor(&response));
+ }
+
+ SECTION("Remove receiver")
+ {
+ constexpr auto MessageId = 2;
+ sys::ResponseMessage response{};
+ response.uniID = MessageId;
+
+ {
+ TestCallbacksDeleter deleter{storage};
+ TestReceiver receiver{&deleter};
+
+ storage.registerCallback(MessageId, &receiver);
+ REQUIRE(storage.containsCallbackFor(&response));
+ }
+
+ REQUIRE(!storage.containsCallbackFor(&response));
+ [[maybe_unused]] auto callback = storage.getCallback(&response);
+ REQUIRE(callback->execute() == false);
+ }
+}
A module-apps/tests/tests-main.cpp => module-apps/tests/tests-main.cpp +5 -0
@@ 0,0 1,5 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+#include <catch2/catch.hpp>
M module-audio/Audio/BluetoothProxyAudio.cpp => module-audio/Audio/BluetoothProxyAudio.cpp +18 -0
@@ 72,4 72,22 @@ namespace bsp
{
Stop();
}
+
+ void BluetoothProxyAudio::onDataReceive()
+ {}
+
+ void BluetoothProxyAudio::onDataSend()
+ {}
+
+ void BluetoothProxyAudio::enableInput()
+ {}
+
+ void BluetoothProxyAudio::enableOutput()
+ {}
+
+ void BluetoothProxyAudio::disableInput()
+ {}
+
+ void BluetoothProxyAudio::disableOutput()
+ {}
} // namespace bsp
M module-audio/Audio/BluetoothProxyAudio.hpp => module-audio/Audio/BluetoothProxyAudio.hpp +7 -0
@@ 28,6 28,13 @@ namespace bsp
AudioDevice::RetCode InputPathCtrl(InputPath inputPath) final;
bool IsFormatSupported(const Format &format) final;
+ void onDataReceive() final;
+ void onDataSend() final;
+ void enableInput() final;
+ void enableOutput() final;
+ void disableInput() final;
+ void disableOutput() final;
+
private:
audio::Stream &dataStreamOut;
audio::Stream &dataStreamIn;
M module-audio/Audio/Endpoint.cpp => module-audio/Audio/Endpoint.cpp +72 -14
@@ 7,18 7,21 @@
using namespace audio;
-void Endpoint::setStream(Stream &stream)
+Endpoint::Endpoint(const Capabilities &caps) : _caps(caps)
+{}
+
+const Endpoint::Capabilities &Endpoint::getCapabilities() const noexcept
{
- assert(_stream == nullptr);
- _stream = &stream;
+ return _caps;
}
-Stream *Endpoint::getStream() const noexcept
+void Endpoint::connectStream(Stream &stream)
{
- return _stream;
+ assert(_stream == nullptr);
+ _stream = &stream;
}
-void Endpoint::unsetStream()
+void Endpoint::disconnectStream()
{
assert(_stream != nullptr);
_stream = nullptr;
@@ 29,16 32,71 @@ bool Endpoint::isConnected() const noexcept
return _stream != nullptr;
}
-void Source::connect(Sink &sink, Stream &stream)
+StreamConnection::StreamConnection(Source *source, Sink *sink, Stream *stream)
+ : _sink(sink), _source(source), _stream(stream)
+{
+ assert(_sink != nullptr);
+ assert(_source != nullptr);
+ assert(_stream != nullptr);
+
+ _sink->connectStream(*_stream);
+ _source->connectStream(*_stream);
+}
+
+StreamConnection::~StreamConnection()
+{
+ destroy();
+}
+
+void StreamConnection::destroy()
+{
+ disable();
+ _sink->disconnectStream();
+ _source->disconnectStream();
+}
+
+void StreamConnection::enable()
{
- connectedSink = &sink;
- connectedSink->setStream(stream);
- setStream(stream);
+ if (enabled) {
+ return;
+ }
+
+ _stream->reset();
+ _sink->enableOutput();
+ _source->enableInput();
+
+ enabled = true;
}
-void Source::disconnectStream()
+void StreamConnection::disable()
{
- unsetStream();
- connectedSink->unsetStream();
- connectedSink = nullptr;
+ if (!enabled) {
+ return;
+ }
+
+ _source->disableInput();
+ _sink->disableOutput();
+ _stream->reset();
+
+ enabled = false;
+}
+
+bool StreamConnection::isEnabled() const noexcept
+{
+ return enabled;
+}
+
+Source *StreamConnection::getSource() const noexcept
+{
+ return _source;
+}
+
+Sink *StreamConnection::getSink() const noexcept
+{
+ return _sink;
+}
+
+Stream *StreamConnection::getStream() const noexcept
+{
+ return _stream;
}
M module-audio/Audio/Endpoint.hpp => module-audio/Audio/Endpoint.hpp +70 -9
@@ 10,26 10,87 @@ namespace audio
class Endpoint
{
public:
- void setStream(Stream &stream);
- Stream *getStream() const noexcept;
- void unsetStream();
+ struct Capabilities
+ {
+ bool usesDMA = false;
+ std::size_t maxBlockSize = 0;
+ };
+
+ Endpoint() = default;
+ Endpoint(const Capabilities &caps);
+
+ void connectStream(Stream &stream);
+ void disconnectStream();
bool isConnected() const noexcept;
- private:
+ [[nodiscard]] const Capabilities &getCapabilities() const noexcept;
+
+ protected:
+ Capabilities _caps;
Stream *_stream = nullptr;
};
class Sink : public Endpoint
- {};
+ {
+ public:
+ virtual void onDataSend() = 0;
+ virtual void enableOutput() = 0;
+ virtual void disableOutput() = 0;
+ };
class Source : public Endpoint
{
public:
- void connect(Sink &sink, Stream &stream);
- void disconnectStream();
+ virtual void onDataReceive() = 0;
+ virtual void enableInput() = 0;
+ virtual void disableInput() = 0;
+ };
- private:
- Sink *connectedSink = nullptr;
+ class IOProxy : public Sink, public Source
+ {
+ public:
+ inline bool isSinkConnected() const noexcept
+ {
+ return Sink::isConnected();
+ }
+
+ inline bool isSourceConnected() const noexcept
+ {
+ return Source::isConnected();
+ }
+
+ inline void connectOutputStream(Stream &stream)
+ {
+ Sink::connectStream(stream);
+ }
+
+ inline void connectInputStream(Stream &stream)
+ {
+ Source::connectStream(stream);
+ }
};
+ class StreamConnection
+ {
+ public:
+ StreamConnection() = default;
+ StreamConnection(Source *source, Sink *sink, Stream *stream);
+ ~StreamConnection();
+
+ void enable();
+ void disable();
+ void destroy();
+
+ [[nodiscard]] Source *getSource() const noexcept;
+ [[nodiscard]] Sink *getSink() const noexcept;
+ [[nodiscard]] Stream *getStream() const noexcept;
+
+ [[nodiscard]] bool isEnabled() const noexcept;
+
+ private:
+ bool enabled = false;
+ Sink *_sink = nullptr;
+ Source *_source = nullptr;
+ Stream *_stream = nullptr;
+ };
}; // namespace audio
M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +50 -61
@@ 16,8 16,6 @@ namespace audio
using namespace AudioServiceMessage;
using namespace utils;
-#define PERF_STATS_ON 0
-
PlaybackOperation::PlaybackOperation(const char *file, const audio::PlaybackType &playbackType, Callback callback)
: Operation(callback, playbackType), dec(nullptr)
{
@@ 26,6 24,13 @@ namespace audio
AddProfile(Profile::Type::PlaybackHeadphones, playbackType, false);
AddProfile(Profile::Type::PlaybackLoudspeaker, playbackType, true);
+ endOfFileCallback = [this]() {
+ state = State::Idle;
+ const auto req = AudioServiceMessage::EndOfFile(operationToken);
+ serviceCallback(&req);
+ return std::string();
+ };
+
auto defaultProfile = GetProfile(Profile::Type::PlaybackLoudspeaker);
if (!defaultProfile) {
throw AudioInitException("Error during initializing profile", RetCode::ProfileNotSet);
@@ 36,18 41,12 @@ namespace audio
if (dec == nullptr) {
throw AudioInitException("Error during initializing decoder", RetCode::FileDoesntExist);
}
+ tags = dec->fetchTags();
auto retCode = SwitchToPriorityProfile();
if (retCode != RetCode::Success) {
throw AudioInitException("Failed to switch audio profile", retCode);
}
-
- endOfFileCallback = [this]() {
- state = State::Idle;
- const auto req = AudioServiceMessage::EndOfFile(operationToken);
- serviceCallback(&req);
- return std::string();
- };
}
audio::RetCode PlaybackOperation::Start(audio::Token token)
@@ 55,31 54,21 @@ namespace audio
if (state == State::Active || state == State::Paused) {
return RetCode::InvokedInIncorrectState;
}
- operationToken = token;
- assert(dataStreamOut);
-
- dec->startDecodingWorker(*dataStreamOut, endOfFileCallback);
-
- if (!tags) {
- tags = dec->fetchTags();
- }
+ // create audio connection
+ outputConnection = std::make_unique<StreamConnection>(dec.get(), audioDevice.get(), dataStreamOut);
- state = State::Active;
+ // decoder worker soft start - must be called after connection setup
+ dec->startDecodingWorker(endOfFileCallback);
- if (tags->num_channel == channel::stereoSound) {
- currentProfile->SetInOutFlags(static_cast<uint32_t>(bsp::AudioDevice::Flags::OutputStereo));
- }
- else {
- currentProfile->SetInOutFlags(static_cast<uint32_t>(bsp::AudioDevice::Flags::OutputMono));
- if (currentProfile->GetOutputPath() == bsp::AudioDevice::OutputPath::Headphones) {
- currentProfile->SetOutputPath(bsp::AudioDevice::OutputPath::HeadphonesMono);
- }
- }
+ // start output device and enable audio connection
+ auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
+ outputConnection->enable();
- currentProfile->SetSampleRate(tags->sample_rate);
+ // update state and token
+ state = State::Active;
+ operationToken = token;
- auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
return GetDeviceError(ret);
}
@@ 89,29 78,32 @@ namespace audio
if (!audioDevice) {
return audio::RetCode::DeviceFailure;
}
+
+ // stop playback by destroying audio connection
+ outputConnection.reset();
+ dec->stopDecodingWorker();
+
return GetDeviceError(audioDevice->Stop());
}
audio::RetCode PlaybackOperation::Pause()
{
-
if (state == State::Paused || state == State::Idle) {
return RetCode::InvokedInIncorrectState;
}
-
state = State::Paused;
- return GetDeviceError(audioDevice->Stop());
+ outputConnection->disable();
+ return audio::RetCode::Success;
}
audio::RetCode PlaybackOperation::Resume()
{
-
if (state == State::Active || state == State::Idle) {
return RetCode::InvokedInIncorrectState;
}
- state = State::Active;
- auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
- return GetDeviceError(ret);
+ state = State::Active;
+ outputConnection->enable();
+ return audio::RetCode::Success;
}
audio::RetCode PlaybackOperation::SetOutputVolume(float vol)
@@ 154,41 146,40 @@ namespace audio
audio::RetCode PlaybackOperation::SwitchProfile(const Profile::Type type)
{
- uint32_t currentSampleRate = currentProfile->GetSampleRate();
- uint32_t currentInOutFlags = currentProfile->GetInOutFlags();
-
- auto ret = GetProfile(type);
- if (ret) {
- currentProfile = ret;
- }
- else {
+ auto newProfile = GetProfile(type);
+ if (newProfile == nullptr) {
return RetCode::UnsupportedProfile;
}
- if (dec->isConnected()) {
- dec->disconnectStream();
- }
-
- audioDevice = CreateDevice(currentProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
+ /// profile change - (re)create output device; stop audio first by
+ /// killing audio connection
+ outputConnection.reset();
+ audioDevice.reset();
+ audioDevice = CreateDevice(newProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
if (audioDevice == nullptr) {
LOG_ERROR("Error creating AudioDevice");
return RetCode::Failed;
}
- dec->connect(audioDevice->sink, *dataStreamOut);
-
- currentProfile->SetSampleRate(currentSampleRate);
- currentProfile->SetInOutFlags(currentInOutFlags);
+ // adjust new profile with information from file's tags
+ newProfile->SetSampleRate(tags->sample_rate);
+ if (tags->num_channel == channel::stereoSound) {
+ newProfile->SetInOutFlags(static_cast<uint32_t>(bsp::AudioDevice::Flags::OutputStereo));
+ }
+ else {
+ newProfile->SetInOutFlags(static_cast<uint32_t>(bsp::AudioDevice::Flags::OutputMono));
+ if (newProfile->GetOutputPath() == bsp::AudioDevice::OutputPath::Headphones) {
+ newProfile->SetOutputPath(bsp::AudioDevice::OutputPath::HeadphonesMono);
+ }
+ }
- switch (state) {
- case State::Idle:
- case State::Paused:
- break;
+ // store profile
+ currentProfile = newProfile;
- case State::Active:
+ if (state == State::Active) {
+ // playback in progress, restart
state = State::Idle;
Start(operationToken);
- break;
}
return audio::RetCode::Success;
@@ 197,8 188,6 @@ namespace audio
PlaybackOperation::~PlaybackOperation()
{
Stop();
- dataStreamOut->reset();
- dataStreamIn->reset();
}
} // namespace audio
M module-audio/Audio/Operation/PlaybackOperation.hpp => module-audio/Audio/Operation/PlaybackOperation.hpp +1 -0
@@ 41,6 41,7 @@ namespace audio
private:
std::unique_ptr<Decoder> dec;
std::unique_ptr<Tags> tags;
+ std::unique_ptr<StreamConnection> outputConnection = nullptr;
DecoderWorker::EndOfFileCallback endOfFileCallback;
};
M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +57 -44
@@ 25,17 25,6 @@ namespace audio
AddProfile(Profile::Type::RoutingHeadphones, PlaybackType::None, false);
AddProfile(Profile::Type::RoutingEarspeaker, PlaybackType::None, true);
AddProfile(Profile::Type::RoutingLoudspeaker, PlaybackType::None, true);
-
- auto defaultProfile = GetProfile(Profile::Type::RoutingEarspeaker);
- if (!defaultProfile) {
- throw AudioInitException("Error during initializing profile", RetCode::ProfileNotSet);
- }
- currentProfile = defaultProfile;
-
- auto retCode = SwitchToPriorityProfile();
- if (retCode != RetCode::Success) {
- throw AudioInitException("Failed to switch audio profile", retCode);
- }
}
audio::RetCode RouterOperation::SetOutputVolume(float vol)
@@ 60,23 49,37 @@ namespace audio
operationToken = token;
state = State::Active;
- if (audioDevice->IsFormatSupported(currentProfile->GetAudioFormat())) {
- auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
- if (ret != bsp::AudioDevice::RetCode::Success) {
- LOG_ERROR("Start error: %s", audio::str(audio::RetCode::DeviceFailure).c_str());
- }
+ // check if audio devices support desired audio format
+ if (!audioDevice->IsFormatSupported(currentProfile->GetAudioFormat())) {
+ return RetCode::InvalidFormat;
}
- else {
+
+ if (!audioDeviceCellular->IsFormatSupported(currentProfile->GetAudioFormat())) {
return RetCode::InvalidFormat;
}
- if (audioDeviceCellular->IsFormatSupported(currentProfile->GetAudioFormat())) {
- auto ret = audioDeviceCellular->Start(currentProfile->GetAudioFormat());
+ // try to run devices with the format
+ if (auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
+ ret != bsp::AudioDevice::RetCode::Success) {
return GetDeviceError(ret);
}
- else {
- return RetCode::InvalidFormat;
+
+ if (auto ret = audioDeviceCellular->Start(currentProfile->GetAudioFormat());
+ ret != bsp::AudioDevice::RetCode::Success) {
+ return GetDeviceError(ret);
}
+
+ // create audio connections
+ inputConnection =
+ std::make_unique<audio::StreamConnection>(audioDeviceCellular.get(), audioDevice.get(), dataStreamIn);
+ outputConnection =
+ std::make_unique<audio::StreamConnection>(audioDevice.get(), audioDeviceCellular.get(), dataStreamOut);
+
+ // enable audio connections
+ inputConnection->enable();
+ outputConnection->enable();
+
+ return audio::RetCode::Success;
}
audio::RetCode RouterOperation::Stop()
@@ 86,10 89,14 @@ namespace audio
}
state = State::Idle;
+ outputConnection.reset();
+ inputConnection.reset();
+
audioDevice->Stop();
audioDeviceCellular->Stop();
- dataStreamOut->reset();
- dataStreamIn->reset();
+
+ audioDevice.reset();
+ audioDeviceCellular.reset();
return RetCode::Success;
}
@@ 101,8 108,8 @@ namespace audio
}
state = State::Paused;
- audioDevice->Stop();
- audioDeviceCellular->Stop();
+ outputConnection->disable();
+ inputConnection->disable();
return RetCode::Success;
}
@@ 113,8 120,8 @@ namespace audio
}
state = State::Active;
- audioDevice->Start(currentProfile->GetAudioFormat());
- audioDeviceCellular->Start(currentProfile->GetAudioFormat());
+ inputConnection->enable();
+ outputConnection->enable();
return RetCode::Success;
}
@@ 152,37 159,38 @@ namespace audio
audio::RetCode RouterOperation::SwitchProfile(const audio::Profile::Type type)
{
- auto ret = GetProfile(type);
- if (ret) {
- currentProfile = ret;
- }
- else {
+ auto newProfile = GetProfile(type);
+ auto callInProgress = state == State::Active;
+
+ if (newProfile == nullptr) {
return RetCode::UnsupportedProfile;
}
- audioDevice = CreateDevice(currentProfile->GetAudioDeviceType(), nullptr).value_or(nullptr);
+ if (currentProfile && currentProfile->GetType() == newProfile->GetType()) {
+ return RetCode::Success;
+ }
+
+ if (callInProgress) {
+ Stop();
+ }
+
+ audioDevice = CreateDevice(newProfile->GetAudioDeviceType(), nullptr).value_or(nullptr);
if (audioDevice == nullptr) {
LOG_ERROR("Error creating AudioDevice");
return RetCode::Failed;
}
+
audioDeviceCellular = CreateDevice(bsp::AudioDevice::Type::Cellular, nullptr).value_or(nullptr);
if (audioDeviceCellular == nullptr) {
LOG_ERROR("Error creating AudioDeviceCellular");
return RetCode::Failed;
}
- audioDevice->source.connect(audioDeviceCellular->sink, *dataStreamIn);
- audioDeviceCellular->source.connect(audioDevice->sink, *dataStreamOut);
-
- switch (state) {
- case State::Idle:
- case State::Paused:
- break;
+ // store new profile
+ currentProfile = newProfile;
- case State::Active:
- state = State::Idle;
- Start(operationToken);
- break;
+ if (callInProgress) {
+ return Start(operationToken);
}
return RetCode::Success;
@@ 199,4 207,9 @@ namespace audio
return 0.0;
}
+ RouterOperation::~RouterOperation()
+ {
+ Stop();
+ }
+
} // namespace audio
M module-audio/Audio/Operation/RouterOperation.hpp => module-audio/Audio/Operation/RouterOperation.hpp +4 -1
@@ 8,6 8,7 @@
#include <Audio/encoder/Encoder.hpp>
#include <Audio/AudioCommon.hpp>
#include <Audio/Profiles/Profile.hpp>
+#include <Audio/Endpoint.hpp>
#include <bsp/audio/bsp_audio.hpp>
#include <mutex.hpp>
@@ 28,7 29,7 @@ namespace audio
public:
RouterOperation(const char *file, AudioServiceMessage::Callback callback);
- ~RouterOperation() = default;
+ ~RouterOperation();
audio::RetCode Start(audio::Token token) final;
audio::RetCode Stop() final;
@@ 47,6 48,8 @@ namespace audio
bool muteEnable = false;
std::unique_ptr<Encoder> enc;
std::unique_ptr<bsp::AudioDevice> audioDeviceCellular;
+ std::unique_ptr<StreamConnection> outputConnection;
+ std::unique_ptr<StreamConnection> inputConnection;
};
} // namespace audio
M module-audio/Audio/Profiles/ProfilePlaybackBluetoothA2DP.hpp => module-audio/Audio/Profiles/ProfilePlaybackBluetoothA2DP.hpp +0 -1
@@ 4,7 4,6 @@
#pragma once
#include "Profile.hpp"
-#include <module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp>
namespace audio
{
M module-audio/Audio/Stream.cpp => module-audio/Audio/Stream.cpp +15 -13
@@ 27,7 27,7 @@ bool Stream::push(void *data, std::size_t dataSize)
bool Stream::push(const Span &span)
{
- LockGuard lock();
+ LockGuard lock;
/// sanity - do not store buffers different than internal block size
if (span.dataSize != _blockSize) {
@@ 65,7 65,7 @@ bool Stream::push()
bool Stream::pop(Span &span)
{
- LockGuard lock();
+ LockGuard lock;
/// sanity - do not store buffers different than internal block size
if (span.dataSize != _blockSize) {
@@ 95,7 95,7 @@ bool Stream::pop(Span &span)
void Stream::consume()
{
- LockGuard lock();
+ LockGuard lock;
_blocksUsed -= _peekCount;
_peekCount = 0;
@@ 106,7 106,7 @@ void Stream::consume()
bool Stream::peek(Span &span)
{
- LockGuard lock();
+ LockGuard lock;
if (getPeekedCount() < getUsedBlockCount()) {
span = *_peekPosition++;
@@ 121,7 121,7 @@ bool Stream::peek(Span &span)
void Stream::unpeek()
{
- LockGuard lock();
+ LockGuard lock;
_peekPosition = _dataStart;
_peekCount = 0;
@@ 129,7 129,7 @@ void Stream::unpeek()
bool Stream::reserve(Span &span)
{
- LockGuard lock();
+ LockGuard lock;
if (getBlockCount() - getUsedBlockCount() > _reserveCount) {
span = *++_writeReservationPosition;
@@ 143,7 143,7 @@ bool Stream::reserve(Span &span)
void Stream::commit()
{
- LockGuard lock();
+ LockGuard lock;
_blocksUsed += _reserveCount;
_reserveCount = 0;
@@ 154,7 154,7 @@ void Stream::commit()
void Stream::release()
{
- LockGuard lock();
+ LockGuard lock;
_reserveCount = 0;
_writeReservationPosition = _dataEnd;
@@ 162,21 162,21 @@ void Stream::release()
std::size_t Stream::getBlockSize() const noexcept
{
- LockGuard lock();
+ LockGuard lock;
return _blockSize;
}
void Stream::registerListener(EventListener *listener)
{
- LockGuard lock();
+ LockGuard lock;
listeners.push_back(std::ref(listener));
}
void Stream::unregisterListeners(Stream::EventListener *listener)
{
- LockGuard lock();
+ LockGuard lock;
auto it = std::find(listeners.begin(), listeners.end(), listener);
if (it != listeners.end()) {
@@ 230,13 230,13 @@ std::size_t Stream::getReservedCount() const noexcept
bool Stream::isEmpty() const noexcept
{
- LockGuard lock();
+ LockGuard lock;
return getUsedBlockCount() == 0;
}
bool Stream::isFull() const noexcept
{
- LockGuard lock();
+ LockGuard lock;
return getUsedBlockCount() == getBlockCount();
}
@@ 247,6 247,8 @@ bool Stream::blocksAvailable() const noexcept
void Stream::reset()
{
+ LockGuard lock;
+
_dataStart = {_buffer.get(), _blockSize * _blockCount, _buffer.get(), _blockSize};
_dataEnd = _dataStart;
_peekPosition = _dataStart;
M module-audio/Audio/decoder/Decoder.cpp => module-audio/Audio/decoder/Decoder.cpp +26 -2
@@ 121,10 121,11 @@ namespace audio
memcpy(pcm, &workerBuffer[0], samplecount * 2 * sizeof(int16_t));
}
- void Decoder::startDecodingWorker(Stream &audioStream, DecoderWorker::EndOfFileCallback endOfFileCallback)
+ void Decoder::startDecodingWorker(DecoderWorker::EndOfFileCallback endOfFileCallback)
{
+ assert(_stream != nullptr);
if (!audioWorker) {
- audioWorker = std::make_unique<DecoderWorker>(audioStream, this, endOfFileCallback);
+ audioWorker = std::make_unique<DecoderWorker>(_stream, this, endOfFileCallback);
audioWorker->init();
audioWorker->run();
}
@@ 133,4 134,27 @@ namespace audio
}
}
+ void Decoder::stopDecodingWorker()
+ {
+ if (audioWorker) {
+ audioWorker->close();
+ }
+ audioWorker = nullptr;
+ }
+
+ void Decoder::onDataReceive()
+ {
+ audioWorker->enablePlayback();
+ }
+
+ void Decoder::enableInput()
+ {
+ audioWorker->enablePlayback();
+ }
+
+ void Decoder::disableInput()
+ {
+ audioWorker->disablePlayback();
+ }
+
} // namespace audio
M module-audio/Audio/decoder/Decoder.hpp => module-audio/Audio/decoder/Decoder.hpp +8 -2
@@ 80,8 80,6 @@ namespace audio
virtual uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) = 0;
- void startDecodingWorker(Stream &audioStream, DecoderWorker::EndOfFileCallback endOfFileCallback);
-
std::unique_ptr<Tags> fetchTags();
// Range 0 - 1
@@ 102,6 100,13 @@ namespace audio
return position;
}
+ void onDataReceive() override;
+ void enableInput() override;
+ void disableInput() override;
+
+ void startDecodingWorker(DecoderWorker::EndOfFileCallback endOfFileCallback);
+ void stopDecodingWorker();
+
// Factory method
static std::unique_ptr<Decoder> Create(const char *file);
@@ 126,6 131,7 @@ namespace audio
// decoding worker
std::unique_ptr<DecoderWorker> audioWorker;
+ DecoderWorker::EndOfFileCallback _endOfFileCallback;
};
} // namespace audio
M module-audio/Audio/decoder/DecoderWorker.cpp => module-audio/Audio/decoder/DecoderWorker.cpp +62 -19
@@ 4,15 4,15 @@
#include "DecoderWorker.hpp"
#include "Audio/decoder/Decoder.hpp"
-audio::DecoderWorker::DecoderWorker(Stream &audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback)
+audio::DecoderWorker::DecoderWorker(Stream *audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback)
: sys::Worker(DecoderWorker::workerName, DecoderWorker::workerPriority), audioStreamOut(audioStreamOut),
decoder(decoder), endOfFileCallback(endOfFileCallback),
- bufferSize(audioStreamOut.getBlockSize() / sizeof(BufferInternalType))
+ bufferSize(audioStreamOut->getBlockSize() / sizeof(BufferInternalType))
{}
audio::DecoderWorker::~DecoderWorker()
{
- audioStreamOut.unregisterListeners(queueListener.get());
+ audioStreamOut->unregisterListeners(queueListener.get());
}
auto audio::DecoderWorker::init(std::list<sys::WorkerQueueInfo> queues) -> bool
@@ 27,7 27,7 @@ auto audio::DecoderWorker::init(std::list<sys::WorkerQueueInfo> queues) -> bool
return false;
}
- audioStreamOut.registerListener(queueListener.get());
+ audioStreamOut->registerListener(queueListener.get());
decoderBuffer = std::make_unique<BufferInternalType[]>(bufferSize);
if (!decoderBuffer) {
@@ 39,8 39,9 @@ auto audio::DecoderWorker::init(std::list<sys::WorkerQueueInfo> queues) -> bool
bool audio::DecoderWorker::handleMessage(uint32_t queueID)
{
- auto queue = queues[queueID];
- if (queue->GetQueueName() == listenerQueueName && queueListener) {
+ auto queue = queues[queueID];
+ auto &queueName = queue->GetQueueName();
+ if (queueName == listenerQueueName && queueListener) {
auto event = queueListener->getEvent();
switch (event.second) {
@@ 55,22 56,64 @@ bool audio::DecoderWorker::handleMessage(uint32_t queueID)
case Stream::Event::StreamHalfUsed:
[[fallthrough]];
case Stream::Event::StreamEmpty:
- auto samplesRead = 0;
-
- while (!audioStreamOut.isFull()) {
- samplesRead = decoder->decode(bufferSize, decoderBuffer.get());
-
- if (samplesRead == 0) {
- endOfFileCallback();
- break;
- }
+ pushAudioData();
+ }
+ }
+ else if (queueName == SERVICE_QUEUE_NAME) {
+ auto &serviceQueue = getServiceQueue();
+ sys::WorkerCommand cmd;
- if (!audioStreamOut.push(decoderBuffer.get(), samplesRead * sizeof(BufferInternalType))) {
- LOG_FATAL("Decoder failed to push to stream.");
- break;
- }
+ if (serviceQueue.Dequeue(&cmd)) {
+ switch (static_cast<Command>(cmd.command)) {
+ case Command::EnablePlayback: {
+ playbackEnabled = true;
+ stateSemaphore.Give();
+ pushAudioData();
+ break;
+ }
+ case Command::DisablePlayback: {
+ playbackEnabled = false;
+ stateSemaphore.Give();
+ }
}
}
}
+
return true;
}
+
+void audio::DecoderWorker::pushAudioData()
+{
+ auto samplesRead = 0;
+
+ while (!audioStreamOut->isFull() && playbackEnabled) {
+ samplesRead = decoder->decode(bufferSize, decoderBuffer.get());
+
+ if (samplesRead == 0) {
+ endOfFileCallback();
+ break;
+ }
+
+ if (!audioStreamOut->push(decoderBuffer.get(), samplesRead * sizeof(BufferInternalType))) {
+ LOG_FATAL("Decoder failed to push to stream.");
+ break;
+ }
+ }
+}
+
+bool audio::DecoderWorker::enablePlayback()
+{
+ return sendCommand({.command = static_cast<uint32_t>(Command::EnablePlayback), .data = nullptr}) &&
+ stateChangeWait();
+}
+
+bool audio::DecoderWorker::disablePlayback()
+{
+ return sendCommand({.command = static_cast<uint32_t>(Command::DisablePlayback), .data = nullptr}) &&
+ stateChangeWait();
+}
+
+bool audio::DecoderWorker::stateChangeWait()
+{
+ return stateSemaphore.Take();
+}
M module-audio/Audio/decoder/DecoderWorker.hpp => module-audio/Audio/decoder/DecoderWorker.hpp +20 -5
@@ 3,9 3,11 @@
#pragma once
-#include <Service/Worker.hpp>
#include "Audio/StreamQueuedEventsListener.hpp"
+#include <Service/Worker.hpp>
+#include <semaphore.hpp>
+
namespace audio
{
class Decoder;
@@ 13,14 15,25 @@ namespace audio
{
public:
using EndOfFileCallback = std::function<void()>;
+ enum class Command
+ {
+ EnablePlayback,
+ DisablePlayback,
+ };
- DecoderWorker(Stream &audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback);
+ DecoderWorker(Stream *audioStreamOut, Decoder *decoder, EndOfFileCallback endOfFileCallback);
~DecoderWorker() override;
virtual auto init(std::list<sys::WorkerQueueInfo> queues = std::list<sys::WorkerQueueInfo>()) -> bool override;
- virtual auto handleMessage(uint32_t queueID) -> bool override;
+
+ auto enablePlayback() -> bool;
+ auto disablePlayback() -> bool;
private:
+ virtual auto handleMessage(uint32_t queueID) -> bool override;
+ void pushAudioData();
+ bool stateChangeWait();
+
using BufferInternalType = int16_t;
static constexpr auto workerName = "DecoderWorker";
@@ 28,10 41,12 @@ namespace audio
static constexpr auto listenerQueueName = "DecoderWorkerQueue";
static constexpr auto listenerQueueCapacity = 1024;
- Stream &audioStreamOut;
- Decoder *decoder = nullptr;
+ Stream *audioStreamOut = nullptr;
+ Decoder *decoder = nullptr;
EndOfFileCallback endOfFileCallback;
std::unique_ptr<StreamQueuedEventsListener> queueListener;
+ bool playbackEnabled = false;
+ cpp_freertos::BinarySemaphore stateSemaphore;
const int bufferSize;
std::unique_ptr<BufferInternalType[]> decoderBuffer;
M module-audio/Audio/decoder/decoderWAV.hpp => module-audio/Audio/decoder/decoderWAV.hpp +0 -1
@@ 44,4 44,3 @@ namespace audio
};
} // namespace audio
-
M module-bluetooth/Bluetooth/BluetoothWorker.cpp => module-bluetooth/Bluetooth/BluetoothWorker.cpp +78 -14
@@ 6,11 6,13 @@
#include "log/log.hpp"
#include "interface/profiles/A2DP/A2DP.hpp"
#include "interface/profiles/HSP/HSP.hpp"
+#include "BtKeysStorage.hpp"
extern "C"
{
#include "module-bluetooth/lib/btstack/src/btstack_util.h"
}
#include <btstack_run_loop_freertos.h>
+#include <service-bluetooth/ServiceBluetooth.hpp>
#if DEBUG_BLUETOOTH_HCI_COMS == 1
#define logHciComs(...) LOG_DEBUG(__VA_ARGS__)
@@ 41,15 43,23 @@ const char *c_str(Bt::Error::Code code)
}
return "";
}
+namespace queues
+{
+ constexpr inline auto io = "qBtIO";
+ constexpr inline auto cmd = "qBtCmds";
+
+} // namespace queues
BluetoothWorker::BluetoothWorker(sys::Service *service)
- : Worker(service), service(service), currentProfile(std::make_shared<Bt::HSP>())
+ : Worker(service), service(service), currentProfile(std::make_shared<Bt::HSP>()),
+ settings(static_cast<ServiceBluetooth *>(service)->settingsHolder)
{
init({
- {"qBtIO", sizeof(Bt::Message), 10},
- {"qBtWork", sizeof(Bt::EvtWorker), 10},
+ {queues::io, sizeof(Bt::Message), 10},
+ {queues::cmd, sizeof(Bt::Command), 10},
});
-};
+ static_cast<ServiceBluetooth *>(service)->workerQueue = Worker::getQueueHandleByName(queues::cmd);
+}
BluetoothWorker::~BluetoothWorker()
{
@@ 69,14 79,33 @@ bool BluetoothWorker::run()
is_running = true;
auto el = queues[queueIO_handle];
BlueKitchen::getInstance()->qHandle = el->GetQueueHandle();
+ Bt::KeyStorage::settings = settings;
Bt::initialize_stack();
Bt::register_hw_error_callback();
Bt::GAP::register_scan();
+
std::string name = "PurePhone";
- Bt::set_name(name);
- // set local namne
- Bt::GAP::set_visibility(true);
- // Bt::GAP::
+ auto settingsName = std::get<std::string>(settings->getValue(Bluetooth::Settings::DeviceName));
+ if (settingsName.empty()) {
+ LOG_ERROR("settings name empty!");
+ settings->setValue(Bluetooth::Settings::DeviceName, name);
+ settingsName = name;
+ }
+ Bt::set_name(settingsName);
+ Bt::GAP::set_visibility(
+ std::visit(Bluetooth::BoolVisitor(), settings->getValue(Bluetooth::Settings::Visibility)));
+
+ settings->setValue(Bluetooth::Settings::State, static_cast<int>(BluetoothStatus::State::On));
+ settings->onLinkKeyAdded = [this](std::string addr) {
+ for (auto &device : Bt::GAP::devices) {
+ if (bd_addr_to_str(device.address) == addr) {
+ // found paired device
+ pairedDevices.emplace_back(device);
+ settings->setValue(Bluetooth::Settings::BondedDevices, SettingsSerializer::toString(pairedDevices));
+ }
+ }
+ };
+
Bt::run_stack(&this->bt_worker_task);
return true;
}
@@ 106,13 135,10 @@ void BluetoothWorker::stopScan()
Bt::GAP::stop_scan();
}
-bool BluetoothWorker::toggleVisibility()
+void BluetoothWorker::setVisibility(bool visibility)
{
- static bool visibility = true;
Bt::GAP::set_visibility(visibility);
- visibility = !visibility;
-
- return visibility;
+ settings->setValue(Bluetooth::Settings::Visibility, visibility);
}
bool BluetoothWorker::start_pan()
@@ 124,15 150,53 @@ bool BluetoothWorker::start_pan()
}
return false;
}
+bool BluetoothWorker::handleCommand(QueueHandle_t queue)
+{
+ Bt::Command command;
+ if (xQueueReceive(queue, &command, 0) != pdTRUE) {
+ LOG_ERROR("Queue receive failure!");
+ return false;
+ }
+ switch (command) {
+ case Bt::PowerOn:
+ break;
+ case Bt::StartScan:
+ scan();
+ break;
+ case Bt::StopScan:
+ stopScan();
+ break;
+ case Bt::VisibilityOn:
+ setVisibility(true);
+ break;
+ case Bt::VisibilityOff:
+ setVisibility(false);
+ break;
+ case Bt::ConnectAudio:
+ establishAudioConnection();
+ break;
+ case Bt::DisconnectAudio:
+ disconnectAudioConnection();
+ break;
+ case Bt::PowerOff:
+ break;
+ }
+ return true;
+}
bool BluetoothWorker::handleMessage(uint32_t queueID)
{
-
QueueHandle_t queue = queues[queueID]->GetQueueHandle();
if (queueID == queueService) {
LOG_DEBUG("not interested");
return true;
}
+
+ if (queueID == queueCommands) {
+ handleCommand(queue);
+ return true;
+ }
+
if (queueID != queueIO_handle) {
LOG_ERROR("Wrong queue! %" PRIu32, queueID);
return false;
M module-bluetooth/Bluetooth/BluetoothWorker.hpp => module-bluetooth/Bluetooth/BluetoothWorker.hpp +22 -4
@@ 11,6 11,7 @@
#include <memory>
#include <task.h>
#include <vector>
+#include "service-bluetooth/SettingsHolder.hpp"
struct HCI;
@@ 19,7 20,7 @@ struct HCI;
namespace Bt
{
- enum Message : uint8_t
+ enum Message : std::uint8_t
{
/// asynchronous messages to use on event from irq
EvtSending, /// Bt stack ordered a write transaction and it is pending
@@ 33,6 34,18 @@ namespace Bt
EvtErrorRec, /// there was error o queue receive
};
+ enum Command : std::uint8_t
+ {
+ StartScan,
+ StopScan,
+ VisibilityOn,
+ VisibilityOff,
+ ConnectAudio,
+ DisconnectAudio,
+ PowerOn,
+ PowerOff,
+ };
+
inline const char *MessageCstr(Message what)
{
switch (what) {
@@ 70,8 83,9 @@ class BluetoothWorker : private sys::Worker
queueService = 0,
queueControl = 1,
queueIO_handle, /// bsp support queue
- queue_profiles, /// queue for communication between profile workers,
- /// main bt_worker_task should dispatch these in events
+ // queue_profiles, /// queue for communication between profile workers,
+ // /// main bt_worker_task should dispatch these in events
+ queueCommands,
};
TaskHandle_t bt_worker_task = nullptr;
@@ 91,11 105,13 @@ class BluetoothWorker : private sys::Worker
virtual bool handleMessage(uint32_t queueID);
+ bool handleCommand(QueueHandle_t queue);
+
bool run();
bool scan();
- bool toggleVisibility();
+ void setVisibility(bool visibility);
bool start_pan();
@@ 111,4 127,6 @@ class BluetoothWorker : private sys::Worker
void initAudioBT();
std::shared_ptr<Bt::Profile> currentProfile;
+ std::shared_ptr<Bluetooth::SettingsHolder> settings;
+ std::vector<Devicei> pairedDevices;
};
M module-bluetooth/Bluetooth/BtCommand.hpp => module-bluetooth/Bluetooth/BtCommand.hpp +1 -0
@@ 17,6 17,7 @@ namespace Bt
auto run_stack(TaskHandle_t *handle) -> Error;
namespace GAP
{
+ extern std::vector<Devicei> devices;
/// THIS have to be called prior to Bt system start!
auto register_scan() -> Error;
auto scan() -> Error;
M module-bluetooth/Bluetooth/BtKeysStorage.cpp => module-bluetooth/Bluetooth/BtKeysStorage.cpp +33 -56
@@ 2,59 2,22 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include <algorithm>
-#include <cstdio>
-#include <filesystem>
-#include <purefs/filesystem_paths.hpp>
-#include <gsl/gsl_util>
-
#include "BtKeysStorage.hpp"
-json11::Json Bt::KeyStorage::fileJson = json11::Json();
+json11::Json Bt::KeyStorage::keysJson = json11::Json();
btstack_link_key_db_t Bt::KeyStorage::keyStorage;
json11::Json::array Bt::KeyStorage::keys;
-std::string Bt::KeyStorage::fileContent;
+std::string Bt::KeyStorage::keysEntry;
+std::shared_ptr<Bluetooth::SettingsHolder> Bt::KeyStorage::settings = nullptr;
namespace Bt
{
- namespace
- {
- std::string loadFilesAsString(const std::filesystem::path &fileToLoad)
- {
- using namespace std::string_literals;
- static constexpr auto file_size_limit = 512LU * 1024LU;
- std::error_code ec;
- auto filesize = std::filesystem::file_size(fileToLoad, ec);
- if (ec || filesize > file_size_limit) {
- return ""s;
- }
- std::string contents(filesize, '\0');
- auto fp = fopen(fileToLoad.c_str(), "r");
- if (!fp) {
- return ""s;
- }
- auto cleanup = gsl::finally([fp] { fclose(fp); });
- const auto nitems = std::fread(contents.data(), contents.size(), 1, fp);
- return (nitems == 1) ? contents : ""s;
- }
-
- bool replaceWithString(const std::filesystem::path &fileToModify, const std::string &stringToWrite)
- {
- auto fp = std::fopen(fileToModify.c_str(), "w");
- if (!fp)
- return false;
- auto cleanup = gsl::finally([fp] { fclose(fp); });
- size_t dataWritten = std::fwrite(stringToWrite.data(), stringToWrite.size(), 1, fp);
- return dataWritten == 1;
- }
- } // namespace
-
namespace strings
{
- inline std::string keysFilename = purefs::dir::getUserDiskPath() / "btkeys.json";
- inline std::string keys = "keys";
- inline std::string link_key = "link_key";
- inline std::string bd_addr = "bd_addr";
- inline std::string type = "type";
+ constexpr inline auto keys = "keys";
+ constexpr inline auto link_key = "link_key";
+ constexpr inline auto bd_addr = "bd_addr";
+ constexpr inline auto type = "type";
} // namespace strings
auto KeyStorage::getKeyStorage() -> btstack_link_key_db_t *
@@ 75,28 38,33 @@ namespace Bt
void KeyStorage::openStorage()
{
LOG_INFO("opening storage from API");
- fileContent.clear();
- fileContent = loadFilesAsString(strings::keysFilename);
- if (fileContent.empty()) {
- LOG_WARN("opening empty key file!");
+ if (settings) {
+ keysEntry = std::visit(Bluetooth::StringVisitor(), settings->getValue(Bluetooth::Settings::BtKeys));
+ }
+ else {
+ LOG_ERROR("failed opening settings for BT!");
+ return;
+ }
+ if (keysEntry.empty()) {
+ LOG_WARN("opening empty key entry!");
return;
}
std::string err;
- fileJson = json11::Json::parse(fileContent.c_str(), err);
+ keysJson = json11::Json::parse(keysEntry.c_str(), err);
if (!err.empty()) {
LOG_ERROR("Error while parsing json: %s", err.c_str());
return;
}
- keys = std::move(fileJson[strings::keys].array_items());
+ keys = std::move(keysJson[strings::keys].array_items());
LOG_INFO("Imported keys: %d", static_cast<unsigned int>(keys.size()));
}
void KeyStorage::closeStorage()
{
LOG_INFO("closing storage from API");
- writeToFile();
+ writeSettings();
}
//
auto KeyStorage::getLinkKey(uint8_t *bd_addr, link_key_t link_key, link_key_type_t *type) -> int
@@ 128,7 96,10 @@ namespace Bt
{strings::link_key, std::string(reinterpret_cast<char *>(link_key))},
{strings::type, type}};
keys.emplace_back(keyEntry);
- writeToFile();
+ if (settings->onLinkKeyAdded) {
+ settings->onLinkKeyAdded(bd_addr_to_str(bd_addr));
+ }
+ writeSettings();
LOG_INFO("keys written to the file");
LOG_INFO("Keys in file: %d", (int)keys.size());
}
@@ 146,7 117,7 @@ namespace Bt
if (keysSize != keys.size()) {
LOG_INFO("Key successfully deleted");
}
- writeToFile();
+ writeSettings();
}
//
auto KeyStorage::iterator_init(btstack_link_key_iterator_t *it) -> int
@@ 164,11 135,17 @@ namespace Bt
{}
void KeyStorage::set_local_bd_addr(bd_addr_t bd_addr)
{}
- void KeyStorage::writeToFile()
+ void KeyStorage::writeSettings()
{
json11::Json finalJson = json11::Json::object{{strings::keys, keys}};
- fileContent = finalJson.dump();
- replaceWithString(strings::keysFilename, fileContent);
+ keysEntry = finalJson.dump();
+ if (settings) {
+ settings->setValue(Bluetooth::Settings::BtKeys, keysEntry);
+ }
+ else {
+ LOG_ERROR("failed to open settings to write!");
+ return;
+ }
}
} // namespace Bt
M module-bluetooth/Bluetooth/BtKeysStorage.hpp => module-bluetooth/Bluetooth/BtKeysStorage.hpp +5 -4
@@ 7,7 7,7 @@
#include <btstack_util.h>
#include <json/json11.hpp>
-#include <vfs.hpp>
+#include <service-bluetooth/SettingsHolder.hpp>
namespace Bt
{
@@ 16,6 16,7 @@ namespace Bt
{
public:
static auto getKeyStorage() -> btstack_link_key_db_t *;
+ static std::shared_ptr<Bluetooth::SettingsHolder> settings;
private:
static void openStorage();
@@ 30,11 31,11 @@ namespace Bt
link_key_type_t *type) -> int;
static void iterator_done(btstack_link_key_iterator_t *it);
static void set_local_bd_addr(bd_addr_t bd_addr);
- static void writeToFile();
- static json11::Json fileJson;
+ static void writeSettings();
+ static json11::Json keysJson;
static btstack_link_key_db_t keyStorage;
static json11::Json::array keys;
- static std::string fileContent;
+ static std::string keysEntry;
};
} // namespace Bt
M module-bluetooth/Bluetooth/BtstackWorker.cpp => module-bluetooth/Bluetooth/BtstackWorker.cpp +0 -1
@@ 195,7 195,6 @@ namespace Bt
hci_init(transport, (void *)&config);
hci_set_link_key_db(KeyStorage::getKeyStorage());
-
hci_event_callback_registration.callback = &hci_packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
LOG_DEBUG("BT worker run success");
M module-bluetooth/Bluetooth/interface/profiles/GAP.cpp => module-bluetooth/Bluetooth/interface/profiles/GAP.cpp +23 -20
@@ 9,6 9,7 @@
#include <log/log.hpp>
#include <service-bluetooth/BluetoothMessage.hpp>
#include <vector>
+#include <BtCommand.hpp>
extern "C"
{
@@ 18,8 19,7 @@ extern "C"
btstack_packet_callback_registration_t cb_handler;
-std::vector<Devicei> devices;
-
+std::vector<Devicei> Bt::GAP::devices;
static auto start_scan() -> int;
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
@@ 44,6 44,7 @@ enum STATE state = INIT;
namespace Bt::GAP
{
+
static sys::Service *ownerService = nullptr;
void setOwnerService(sys::Service *service)
@@ 97,6 98,7 @@ namespace Bt::GAP
}
return false;
}
+
} // namespace Bt::GAP
#define INQUIRY_INTERVAL 5
@@ 109,8 111,8 @@ static auto start_scan() -> int
static auto has_more_remote_name_requests() -> int
{
int i;
- for (i = 0; i < devices.size(); i++) {
- if (devices[i].state == REMOTE_NAME_REQUEST) {
+ for (i = 0; i < Bt::GAP::devices.size(); i++) {
+ if (Bt::GAP::devices[i].state == REMOTE_NAME_REQUEST) {
return 1;
}
}
@@ 120,13 122,14 @@ static auto has_more_remote_name_requests() -> int
static void do_next_remote_name_request()
{
int i;
- for (i = 0; i < devices.size(); i++) {
+ for (i = 0; i < Bt::GAP::devices.size(); i++) {
// remote name request
- if (devices[i].state == REMOTE_NAME_REQUEST) {
- devices[i].state = REMOTE_NAME_INQUIRED;
- LOG_INFO("Get remote name of %s...", bd_addr_to_str(devices[i].address));
- gap_remote_name_request(
- devices[i].address, devices[i].pageScanRepetitionMode, devices[i].clockOffset | 0x8000);
+ if (Bt::GAP::devices[i].state == REMOTE_NAME_REQUEST) {
+ Bt::GAP::devices[i].state = REMOTE_NAME_INQUIRED;
+ LOG_INFO("Get remote name of %s...", bd_addr_to_str(Bt::GAP::devices[i].address));
+ gap_remote_name_request(Bt::GAP::devices[i].address,
+ Bt::GAP::devices[i].pageScanRepetitionMode,
+ Bt::GAP::devices[i].clockOffset | 0x8000);
return;
}
}
@@ 186,7 189,7 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe
case GAP_EVENT_INQUIRY_RESULT: {
gap_event_inquiry_result_get_bd_addr(packet, addr);
- index = getDeviceIndexForAddress(devices, addr);
+ index = getDeviceIndexForAddress(Bt::GAP::devices, addr);
if (index >= 0) {
break; // already in our list
}
@@ 215,36 218,36 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packe
else {
dev.state = REMOTE_NAME_REQUEST;
}
- devices.push_back(dev);
- auto msg = std::make_shared<BluetoothScanResultMessage>(devices);
+ Bt::GAP::devices.push_back(dev);
+ auto msg = std::make_shared<BluetoothScanResultMessage>(Bt::GAP::devices);
sys::Bus::SendUnicast(msg, "ApplicationSettings", Bt::GAP::ownerService);
sys::Bus::SendUnicast(msg, "ApplicationSettingsNew", Bt::GAP::ownerService);
} break;
case GAP_EVENT_INQUIRY_COMPLETE:
- for (i = 0; i < devices.size(); i++) {
+ for (i = 0; i < Bt::GAP::devices.size(); i++) {
// retry remote name request
- if (devices[i].state == REMOTE_NAME_INQUIRED)
- devices[i].state = REMOTE_NAME_REQUEST;
+ if (Bt::GAP::devices[i].state == REMOTE_NAME_INQUIRED)
+ Bt::GAP::devices[i].state = REMOTE_NAME_REQUEST;
}
continue_remote_names();
break;
case HCI_EVENT_REMOTE_NAME_REQUEST_COMPLETE: {
reverse_bd_addr(&packet[3], addr);
- index = getDeviceIndexForAddress(devices, addr);
+ index = getDeviceIndexForAddress(Bt::GAP::devices, addr);
if (index >= 0) {
if (packet[2] == 0) {
LOG_INFO("Name: '%s'", &packet[9]);
- devices[index].state = REMOTE_NAME_FETCHED;
- devices[index].name = reinterpret_cast<char *>(&packet[9]);
+ Bt::GAP::devices[index].state = REMOTE_NAME_FETCHED;
+ Bt::GAP::devices[index].name = reinterpret_cast<char *>(&packet[9]);
}
else {
LOG_INFO("Failed to get name: page timeout");
}
}
- if (index + 1 == devices.size()) {
+ if (index + 1 == Bt::GAP::devices.size()) {
LOG_INFO("Scanned all");
state = DONE;
gap_inquiry_stop();
M module-bsp/board/linux/audio/LinuxCellularAudio.cpp => module-bsp/board/linux/audio/LinuxCellularAudio.cpp +21 -2
@@ 95,7 95,7 @@ namespace bsp
}
if (inputBuffer) {
- int16_t *pBuff = reinterpret_cast<int16_t *>(const_cast<void *>(inputBuffer));
+ int16_t *pBuff = reinterpret_cast<int16_t *>(const_cast<void *>(inputBuffer));
constexpr float scaleFactor = .1f;
std::transform(pBuff, pBuff + framesToFetch, pBuff, [ptr, &scaleFactor](int16_t c) -> int16_t {
return static_cast<float>(c * ptr->currentFormat.inputGain * scaleFactor);
@@ 111,7 111,7 @@ namespace bsp
// Scale output buffer
if (outputBuffer) {
- int16_t *pBuff = reinterpret_cast<int16_t *>(outputBuffer);
+ int16_t *pBuff = reinterpret_cast<int16_t *>(outputBuffer);
constexpr float scaleFactor = .1f;
std::transform(pBuff, pBuff + framesToFetch, pBuff, [ptr](int16_t c) -> int16_t {
return static_cast<float>(c * ptr->currentFormat.outputVolume / 10.f);
@@ 202,4 202,23 @@ namespace bsp
return false;
}
}
+
+ void LinuxCellularAudio::onDataReceive()
+ {}
+
+ void LinuxCellularAudio::onDataSend()
+ {}
+
+ void LinuxCellularAudio::enableInput()
+ {}
+
+ void LinuxCellularAudio::enableOutput()
+ {}
+
+ void LinuxCellularAudio::disableInput()
+ {}
+
+ void LinuxCellularAudio::disableOutput()
+ {}
+
} // namespace bsp
M module-bsp/board/linux/audio/LinuxCellularAudio.hpp => module-bsp/board/linux/audio/LinuxCellularAudio.hpp +7 -0
@@ 31,6 31,13 @@ namespace bsp
bool IsFormatSupported(const Format &format) override final;
+ void onDataReceive() final;
+ void onDataSend() final;
+ void enableInput() final;
+ void enableOutput() final;
+ void disableInput() final;
+ void disableOutput() final;
+
private:
PaStream *stream;
M module-bsp/board/linux/audio/linux_audiocodec.cpp => module-bsp/board/linux/audio/linux_audiocodec.cpp +21 -2
@@ 93,7 93,7 @@ namespace bsp
}
if (inputBuffer) {
- int16_t *pBuff = reinterpret_cast<int16_t *>(const_cast<void *>(inputBuffer));
+ int16_t *pBuff = reinterpret_cast<int16_t *>(const_cast<void *>(inputBuffer));
constexpr float scaleFactor = .1f;
std::transform(pBuff, pBuff + framesToFetch, pBuff, [ptr, &scaleFactor](int16_t c) -> int16_t {
return static_cast<float>(c * ptr->currentFormat.inputGain * scaleFactor);
@@ 109,7 109,7 @@ namespace bsp
// Scale output buffer
if (outputBuffer) {
- int16_t *pBuff = reinterpret_cast<int16_t *>(outputBuffer);
+ int16_t *pBuff = reinterpret_cast<int16_t *>(outputBuffer);
constexpr float scaleFactor = .1f;
std::transform(pBuff, pBuff + framesToFetch, pBuff, [ptr, &scaleFactor](int16_t c) -> int16_t {
return static_cast<float>(c * ptr->currentFormat.outputVolume * scaleFactor);
@@ 200,4 200,23 @@ namespace bsp
return false;
}
}
+
+ void LinuxAudiocodec::onDataReceive()
+ {}
+
+ void LinuxAudiocodec::onDataSend()
+ {}
+
+ void LinuxAudiocodec::enableInput()
+ {}
+
+ void LinuxAudiocodec::enableOutput()
+ {}
+
+ void LinuxAudiocodec::disableInput()
+ {}
+
+ void LinuxAudiocodec::disableOutput()
+ {}
+
} // namespace bsp
M module-bsp/board/linux/audio/linux_audiocodec.hpp => module-bsp/board/linux/audio/linux_audiocodec.hpp +7 -0
@@ 24,6 24,13 @@ namespace bsp
AudioDevice::RetCode InputPathCtrl(InputPath inputPath) override final;
bool IsFormatSupported(const Format &format) override final;
+ void onDataReceive() final;
+ void onDataSend() final;
+ void enableInput() final;
+ void enableOutput() final;
+ void disableInput() final;
+ void disableOutput() final;
+
private:
PaStream *stream;
M module-bsp/board/linux/lpm/LinuxLPM.cpp => module-bsp/board/linux/lpm/LinuxLPM.cpp +5 -0
@@ 29,4 29,9 @@ namespace bsp
{
currentOscSource = source;
}
+
+ void LinuxLPM::SwitchPll2State(bsp::LowPowerMode::Pll2State state)
+ {
+ currentPll2State = state;
+ }
} // namespace bsp
M module-bsp/board/linux/lpm/LinuxLPM.h => module-bsp/board/linux/lpm/LinuxLPM.h +1 -0
@@ 17,6 17,7 @@ namespace bsp
int32_t Reboot() override final;
void SetCpuFrequency(CpuFrequency freq) final;
void SwitchOscillatorSource(OscillatorSource source) final;
+ void SwitchPll2State(Pll2State state) final;
};
} // namespace bsp
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp => module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp +8 -59
@@ 26,7 26,8 @@ namespace bsp
sai_edma_handle_t RT1051Audiocodec::rxHandle = {};
RT1051Audiocodec::RT1051Audiocodec(bsp::AudioDevice::audioCallback_t callback)
- : AudioDevice(callback), saiInFormat{}, saiOutFormat{}, codecParams{}, codec{}
+ : SAIAudioDevice(callback, BOARD_AUDIOCODEC_SAIx, &rxHandle, &txHandle), saiInFormat{}, saiOutFormat{},
+ codecParams{}, codec{}
{
isInitialized = true;
}
@@ 99,11 100,11 @@ namespace bsp
return AudioDevice::RetCode::Failure;
}
- codec.Stop();
-
InStop();
OutStop();
+ codec.Stop();
+
state = State::Stopped;
vTaskDelay(codecSettleTime);
@@ 238,17 239,6 @@ namespace bsp
/* Reset SAI Rx internal logic */
SAI_RxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);
-
- if (!source.isConnected()) {
- LOG_FATAL("No output stream connected!");
- return;
- }
-
- /// initiate first read
- audio::Stream::Span dataSpan;
- source.getStream()->reserve(dataSpan);
- auto xfer = sai_transfer_t{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
- SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle, &xfer);
}
void RT1051Audiocodec::OutStart()
@@ 279,15 269,6 @@ namespace bsp
/* Reset SAI Tx internal logic */
SAI_TxSoftwareReset(BOARD_AUDIOCODEC_SAIx, kSAI_ResetTypeSoftware);
-
- if (!sink.isConnected()) {
- LOG_FATAL("No input stream connected!");
- return;
- }
-
- auto nullSpan = sink.getStream()->getNullSpan();
- auto xfer = sai_transfer_t{.data = nullSpan.data, .dataSize = nullSpan.dataSize};
- SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle, &xfer);
}
void RT1051Audiocodec::OutStop()
@@ 297,9 278,6 @@ namespace bsp
SAI_TransferTerminateSendEDMA(BOARD_AUDIOCODEC_SAIx, &txHandle);
}
memset(&txHandle, 0, sizeof(txHandle));
- if (sink.isConnected()) {
- sink.getStream()->unpeek();
- }
}
void RT1051Audiocodec::InStop()
@@ 309,47 287,18 @@ namespace bsp
SAI_TransferAbortReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &rxHandle);
}
memset(&rxHandle, 0, sizeof(rxHandle));
- if (source.isConnected()) {
- source.getStream()->release();
- }
}
void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
- audio::Stream::Span dataSpan;
- auto self = static_cast<RT1051Audiocodec *>(userData);
- auto &source = self->source;
-
- /// exit if not connected to the stream
- if (!source.isConnected()) {
- return;
- }
-
- /// reserve space for the next read commiting previously reserved block before
- source.getStream()->commit();
- source.getStream()->reserve(dataSpan);
-
- sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
- SAI_TransferReceiveEDMA(BOARD_AUDIOCODEC_SAIx, &self->rxHandle, &xfer);
+ auto self = static_cast<RT1051Audiocodec *>(userData);
+ self->onDataReceive();
}
void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
- audio::Stream::Span dataSpan;
- auto self = static_cast<RT1051Audiocodec *>(userData);
- auto &sink = self->sink;
-
- /// exit if not connected to the stream
- if (!sink.isConnected()) {
- return;
- }
-
- /// pop previous read and peek next
- sink.getStream()->consume();
- sink.getStream()->peek(dataSpan);
-
- sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
- SAI_TransferSendEDMA(BOARD_AUDIOCODEC_SAIx, &self->txHandle, &xfer);
+ auto self = static_cast<RT1051Audiocodec *>(userData);
+ self->onDataSend();
}
} // namespace bsp
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.hpp => module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.hpp +2 -2
@@ 3,7 3,7 @@
#pragma once
-#include "bsp/audio/bsp_audio.hpp"
+#include "SAIAudioDevice.hpp"
#include "fsl_sai_edma.h"
#include "FreeRTOS.h"
@@ 24,7 24,7 @@ namespace bsp
void txAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
void rxAudioCodecCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
- class RT1051Audiocodec : public AudioDevice
+ class RT1051Audiocodec : public SAIAudioDevice
{
public:
D module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.cpp => module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.cpp +0 -104
@@ 1,104 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#include "RT1051BluetoothAudio.hpp"
-
-namespace bsp
-{
- RT1051BluetoothAudio::RT1051BluetoothAudio(AudioDevice::audioCallback_t callback) : AudioDevice(callback)
- {
- isInitialized = true;
- LOG_INFO("Device created!");
- }
- AudioDevice::RetCode RT1051BluetoothAudio::Start(const AudioDevice::Format &format)
- {
- LOG_INFO("Start");
- Init();
- if ((format.flags & static_cast<uint32_t>(AudioDevice::Flags::OutputMono)) != 0u) {
- OutStart();
- }
- else if ((format.flags & static_cast<uint32_t>(AudioDevice::Flags::OutputStereo)) != 0u) {
- OutStart();
- }
- return AudioDevice::RetCode::Success;
- }
- AudioDevice::RetCode RT1051BluetoothAudio::Stop()
- {
- return AudioDevice::RetCode::Success;
- }
- AudioDevice::RetCode RT1051BluetoothAudio::OutputVolumeCtrl(float vol)
- {
- return AudioDevice::RetCode::Success;
- }
- AudioDevice::RetCode RT1051BluetoothAudio::InputGainCtrl(float gain)
- {
- return AudioDevice::RetCode::Success;
- }
- AudioDevice::RetCode RT1051BluetoothAudio::OutputPathCtrl(AudioDevice::OutputPath outputPath)
- {
- return AudioDevice::RetCode::Success;
- }
- AudioDevice::RetCode RT1051BluetoothAudio::InputPathCtrl(AudioDevice::InputPath inputPath)
- {
- return AudioDevice::RetCode::Success;
- }
- bool RT1051BluetoothAudio::IsFormatSupported(const AudioDevice::Format &format)
- {
- return true;
- }
- void outBluetoothAudioWorkerTask(void *pvp)
- {
- auto *inst = static_cast<RT1051BluetoothAudio *>(pvp);
- auto dataSize = inst->metadata.samplesPerFrame;
- auto fatalError = false;
-
- if (inst->sourceQueue == nullptr) {
- LOG_FATAL("sourceQueue nullptr");
- fatalError = true;
- }
-
- while (!fatalError) {
- auto framesFetched = inst->GetAudioCallback()(nullptr, inst->audioData.data.data(), dataSize);
- if (framesFetched == 0) {
- break;
- }
- else if (framesFetched < inst->audioData.data.size()) {
- std::fill(inst->audioData.data.begin() + framesFetched, inst->audioData.data.end(), 0);
- }
-
- if (inst->sourceQueue != nullptr) {
- xQueueSend(inst->sourceQueue, inst->audioData.data.data(), 2);
- }
- else {
- LOG_ERROR("Queue nullptr");
- vTaskDelay(2);
- }
- }
-
- inst->OutStop();
- inst->outWorkerThread = nullptr;
- vTaskDelete(nullptr);
- }
-
- void RT1051BluetoothAudio::OutStart()
- {
- if (xTaskCreate(outBluetoothAudioWorkerTask,
- "outbluetoothaudio",
- stackSize,
- this,
- tskIDLE_PRIORITY,
- &outWorkerThread) != pdPASS) {
- LOG_ERROR("Error during creating output bluetooth audio task");
- }
- }
- void RT1051BluetoothAudio::OutStop()
- {}
-
- void RT1051BluetoothAudio::Init()
- {}
-
- RT1051BluetoothAudio::~RT1051BluetoothAudio()
- {
- Stop();
- }
-} // namespace bsp
D module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp => module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp +0 -42
@@ 1,42 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include "FreeRTOS.h"
-#include "macros.h"
-#include "task.h"
-#include "bsp/audio/bsp_audio.hpp"
-#include <module-bluetooth/Bluetooth/Device.hpp>
-#include <mutex.hpp>
-
-namespace bsp
-{
- class RT1051BluetoothAudio final : public AudioDevice
- {
- static constexpr uint16_t stackSize = 1024;
-
- public:
- RT1051BluetoothAudio(AudioDevice::audioCallback_t callback);
- ~RT1051BluetoothAudio() final;
-
- AudioDevice::RetCode Start(const Format &format) final;
- AudioDevice::RetCode Stop() final;
- AudioDevice::RetCode OutputVolumeCtrl(float vol) final;
- AudioDevice::RetCode InputGainCtrl(float gain) final;
- AudioDevice::RetCode OutputPathCtrl(OutputPath outputPath) final;
- AudioDevice::RetCode InputPathCtrl(InputPath inputPath) final;
- bool IsFormatSupported(const Format &format) final;
-
- TaskHandle_t outWorkerThread;
- AudioData_t audioData;
- QueueHandle_t sourceQueue;
- DeviceMetadata_t metadata;
- void OutStop();
-
- private:
- void Init();
- void OutStart();
- };
-
-} // namespace bsp
M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp => module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.cpp +6 -56
@@ 19,7 19,8 @@ namespace bsp
sai_edma_handle_t RT1051CellularAudio::rxHandle = {};
RT1051CellularAudio::RT1051CellularAudio(bsp::AudioDevice::audioCallback_t callback)
- : AudioDevice(callback), saiInFormat{}, saiOutFormat{}, config{}
+ : SAIAudioDevice(callback, BOARD_CELLULAR_AUDIO_SAIx, &rxHandle, &txHandle), saiInFormat{},
+ saiOutFormat{}, config{}
{
isInitialized = true;
}
@@ 199,17 200,6 @@ namespace bsp
/* Reset SAI Rx internal logic */
SAI_RxSoftwareReset(BOARD_CELLULAR_AUDIO_SAIx, kSAI_ResetTypeSoftware);
-
- if (!source.isConnected()) {
- LOG_FATAL("No output stream connected!");
- return;
- }
-
- /// initiate first read
- audio::Stream::Span dataSpan;
- source.getStream()->reserve(dataSpan);
- auto xfer = sai_transfer_t{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
- SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &rxHandle, &xfer);
}
void RT1051CellularAudio::OutStart()
@@ 233,7 223,6 @@ namespace bsp
txCellularCallback,
this,
reinterpret_cast<edma_handle_t *>(txDMAHandle->GetHandle()));
-
SAI_TransferTxSetFormatEDMA(
BOARD_CELLULAR_AUDIO_SAIx, &txHandle, &sai_format, mclkSourceClockHz, mclkSourceClockHz);
@@ 241,15 230,6 @@ namespace bsp
/* Reset SAI Tx internal logic */
SAI_TxSoftwareReset(BOARD_CELLULAR_AUDIO_SAIx, kSAI_ResetTypeSoftware);
-
- if (!sink.isConnected()) {
- LOG_FATAL("No input stream connected!");
- return;
- }
-
- auto nullSpan = sink.getStream()->getNullSpan();
- auto xfer = sai_transfer_t{.data = nullSpan.data, .dataSize = nullSpan.dataSize};
- SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &txHandle, &xfer);
}
void RT1051CellularAudio::OutStop()
@@ 259,9 239,6 @@ namespace bsp
SAI_TransferTerminateSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &txHandle);
}
memset(&txHandle, 0, sizeof(txHandle));
- if (sink.isConnected()) {
- sink.getStream()->unpeek();
- }
}
void RT1051CellularAudio::InStop()
@@ 271,47 248,20 @@ namespace bsp
SAI_TransferAbortReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &rxHandle);
}
memset(&rxHandle, 0, sizeof(rxHandle));
- if (source.isConnected()) {
- source.getStream()->release();
- }
}
void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
- audio::Stream::Span dataSpan;
- auto self = static_cast<RT1051CellularAudio *>(userData);
- auto &source = self->source;
-
- /// exit if not connected to the stream
- if (!source.isConnected()) {
- return;
- }
-
- /// reserve space for the next read commiting previously reserved block before
- source.getStream()->commit();
- source.getStream()->reserve(dataSpan);
+ auto self = static_cast<RT1051CellularAudio *>(userData);
- sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
- SAI_TransferReceiveEDMA(BOARD_CELLULAR_AUDIO_SAIx, &self->rxHandle, &xfer);
+ self->onDataReceive();
}
void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData)
{
- audio::Stream::Span dataSpan;
- auto self = static_cast<RT1051CellularAudio *>(userData);
- auto &sink = self->sink;
-
- /// exit if not connected to the stream
- if (!sink.isConnected()) {
- return;
- }
-
- /// pop previous read and peek next
- sink.getStream()->consume();
- sink.getStream()->peek(dataSpan);
+ auto self = static_cast<RT1051CellularAudio *>(userData);
- sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
- SAI_TransferSendEDMA(BOARD_CELLULAR_AUDIO_SAIx, &self->txHandle, &xfer);
+ self->onDataSend();
}
} // namespace bsp
M module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp => module-bsp/board/rt1051/bsp/audio/RT1051CellularAudio.hpp +2 -2
@@ 4,7 4,7 @@
#ifndef PUREPHONE_RT1051CELLULARAUDIO_HPP
#define PUREPHONE_RT1051CELLULARAUDIO_HPP
-#include "bsp/audio/bsp_audio.hpp"
+#include "SAIAudioDevice.hpp"
#include "fsl_sai_edma.h"
#include "FreeRTOS.h"
@@ 23,7 23,7 @@ namespace bsp
void txCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
void rxCellularCallback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData);
- class RT1051CellularAudio : public AudioDevice
+ class RT1051CellularAudio : public SAIAudioDevice
{
public:
A module-bsp/board/rt1051/bsp/audio/SAIAudioDevice.cpp => module-bsp/board/rt1051/bsp/audio/SAIAudioDevice.cpp +97 -0
@@ 0,0 1,97 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "SAIAudioDevice.hpp"
+
+using namespace bsp;
+
+SAIAudioDevice::SAIAudioDevice(AudioDevice::audioCallback_t callback,
+ I2S_Type *base,
+ sai_edma_handle_t *rxHandle,
+ sai_edma_handle_t *txHandle)
+ : AudioDevice(callback), _base(base), rx(rxHandle), tx(txHandle)
+{}
+
+void SAIAudioDevice::initiateRxTransfer()
+{
+ audio::Stream::Span dataSpan;
+
+ Source::_stream->reserve(dataSpan);
+ auto xfer = sai_transfer_t{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferReceiveEDMA(_base, rx, &xfer);
+}
+
+void SAIAudioDevice::initiateTxTransfer()
+{
+ auto nullSpan = Sink::_stream->getNullSpan();
+ auto xfer = sai_transfer_t{.data = nullSpan.data, .dataSize = nullSpan.dataSize};
+ SAI_TransferSendEDMA(_base, tx, &xfer);
+}
+
+void SAIAudioDevice::onDataSend()
+{
+ audio::Stream::Span dataSpan;
+
+ if (!txEnabled || !isSinkConnected()) {
+ return;
+ }
+
+ /// pop previous read and peek next
+ Sink::_stream->consume();
+ Sink::_stream->peek(dataSpan);
+
+ sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferSendEDMA(_base, tx, &xfer);
+}
+
+void SAIAudioDevice::onDataReceive()
+{
+ audio::Stream::Span dataSpan;
+
+ if (!rxEnabled || !isSourceConnected()) {
+ return;
+ }
+
+ /// reserve space for the next read commiting previously reserved block before
+ Source::_stream->commit();
+ Source::_stream->reserve(dataSpan);
+
+ sai_transfer_t xfer{.data = dataSpan.data, .dataSize = dataSpan.dataSize};
+ SAI_TransferReceiveEDMA(_base, rx, &xfer);
+}
+
+void SAIAudioDevice::enableInput()
+{
+ if (!isSourceConnected()) {
+ LOG_FATAL("No output stream connected!");
+ return;
+ }
+
+ rxEnabled = true;
+
+ /// initiate first read
+ initiateRxTransfer();
+}
+
+void SAIAudioDevice::enableOutput()
+{
+ if (!isSinkConnected()) {
+ LOG_FATAL("No input stream connected!");
+ return;
+ }
+
+ txEnabled = true;
+
+ /// initiate first write
+ initiateTxTransfer();
+}
+
+void SAIAudioDevice::disableInput()
+{
+ rxEnabled = false;
+}
+
+void SAIAudioDevice::disableOutput()
+{
+ txEnabled = false;
+}
A module-bsp/board/rt1051/bsp/audio/SAIAudioDevice.hpp => module-bsp/board/rt1051/bsp/audio/SAIAudioDevice.hpp +38 -0
@@ 0,0 1,38 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "bsp/audio/bsp_audio.hpp"
+
+#include "fsl_sai_edma.h"
+
+namespace bsp
+{
+
+ class SAIAudioDevice : public bsp::AudioDevice
+ {
+ public:
+ SAIAudioDevice(AudioDevice::audioCallback_t callback,
+ I2S_Type *base,
+ sai_edma_handle_t *rxHandle,
+ sai_edma_handle_t *txHandle);
+
+ void onDataSend() override;
+ void onDataReceive() override;
+ void enableInput() override;
+ void enableOutput() override;
+ void disableInput() override;
+ void disableOutput() override;
+
+ protected:
+ void initiateRxTransfer();
+ void initiateTxTransfer();
+ I2S_Type *_base;
+ sai_edma_handle_t *rx = nullptr;
+ sai_edma_handle_t *tx = nullptr;
+ bool txEnabled = false;
+ bool rxEnabled = false;
+ };
+
+} // namespace bsp
M module-bsp/board/rt1051/bsp/lpm/RT1051LPM.cpp => module-bsp/board/rt1051/bsp/lpm/RT1051LPM.cpp +24 -0
@@ 7,6 7,7 @@
#include "log/log.hpp"
#include "bsp/BoardDefinitions.hpp"
#include "bsp/watchdog/watchdog.hpp"
+#include <clock_config.h>
#include <fsl_clock.h>
#include <fsl_dcdc.h>
@@ 101,6 102,29 @@ namespace bsp
currentOscSource = source;
}
+ void RT1051LPM::SwitchPll2State(bsp::LowPowerMode::Pll2State state)
+ {
+ if (state == bsp::LowPowerMode::Pll2State::Disable) {
+ if (IsClockEnabled(kCLOCK_Lpspi1) || IsClockEnabled(kCLOCK_Lpspi2) || IsClockEnabled(kCLOCK_Lpspi3) ||
+ IsClockEnabled(kCLOCK_Lpspi4) || IsClockEnabled(kCLOCK_Usdhc1) || IsClockEnabled(kCLOCK_Usdhc2)) {
+ return;
+ }
+
+ /// First switch external RAM clock
+ CLOCK_SetMux(kCLOCK_SemcMux, 0);
+ /// Then turn off PLL2
+ clkPLL2setup(CLK_DISABLE);
+ }
+ else {
+ /// First turn on PLL2
+ clkPLL2setup(CLK_ENABLE);
+ /// Then switch external RAM clock
+ CLOCK_SetMux(kCLOCK_SemcMux, 1);
+ }
+
+ currentPll2State = state;
+ }
+
bool RT1051LPM::IsClockEnabled(clock_ip_name_t name) const noexcept
{
const auto index = static_cast<uint32_t>(name) >> CCM_TupleShift;
M module-bsp/board/rt1051/bsp/lpm/RT1051LPM.hpp => module-bsp/board/rt1051/bsp/lpm/RT1051LPM.hpp +2 -1
@@ 14,7 14,7 @@ namespace bsp
inline constexpr uint8_t OscillatorReadyCounterValue{127};
inline constexpr uint8_t CCM_TupleShift{8};
inline constexpr uint8_t CCM_TupleMask{0x1F};
- inline constexpr uint32_t ClockNeededRunWaitMode{3};
+ inline constexpr uint32_t ClockNeededRunWaitMode{kCLOCK_ClockNeededRunWait};
class RT1051LPM : public LowPowerMode
{
@@ 24,6 24,7 @@ namespace bsp
int32_t Reboot() override final;
void SetCpuFrequency(CpuFrequency freq) final;
void SwitchOscillatorSource(OscillatorSource source) final;
+ void SwitchPll2State(Pll2State state) final;
private:
[[nodiscard]] bool IsClockEnabled(clock_ip_name_t name) const noexcept;
M module-bsp/bsp/audio/bsp_audio.cpp => module-bsp/bsp/audio/bsp_audio.cpp +24 -28
@@ 4,7 4,10 @@
#include "board/rt1051/bsp/audio/RT1051Audiocodec.hpp"
#include "board/rt1051/bsp/audio/RT1051CellularAudio.hpp"
-#include "board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp"
+
+#include <Audio/Stream.hpp>
+
+#include <cassert>
#elif defined(TARGET_Linux)
#include "audio/linux_audiocodec.hpp"
@@ 13,60 16,53 @@
#error "Unsupported target"
#endif
-namespace bsp{
-
- std::optional<std::unique_ptr<AudioDevice>> AudioDevice::Create(bsp::AudioDevice::Type type, audioCallback_t callback) {
+namespace bsp
+{
+ std::optional<std::unique_ptr<AudioDevice>> AudioDevice::Create(bsp::AudioDevice::Type type,
+ audioCallback_t callback)
+ {
std::unique_ptr<AudioDevice> inst;
- switch(type){
+ switch (type) {
- case Type ::Audiocodec:
- {
+ case Type ::Audiocodec: {
#if defined(TARGET_RT1051)
- inst = std::make_unique<bsp::RT1051Audiocodec>(callback);
+ inst = std::make_unique<bsp::RT1051Audiocodec>(callback);
#elif defined(TARGET_Linux)
- inst = std::make_unique<bsp::LinuxAudiocodec>(callback );
+ inst = std::make_unique<bsp::LinuxAudiocodec>(callback);
#else
- #error "Unsupported target"
+#error "Unsupported target"
#endif
- }
- break;
+ } break;
-
- case Type ::Bluetooth:
- {
+ case Type ::Bluetooth: {
#if defined(TARGET_RT1051)
- inst = std::make_unique<bsp::RT1051BluetoothAudio>(callback);
+ inst = nullptr;
#elif defined(TARGET_Linux)
#else
#error "Unsupported target"
#endif
- }
- break;
+ } break;
- case Type::Cellular:
- {
+ case Type::Cellular: {
#if defined(TARGET_RT1051)
- inst = std::make_unique<bsp::RT1051CellularAudio>(callback);
+ inst = std::make_unique<bsp::RT1051CellularAudio>(callback);
#elif defined(TARGET_Linux)
- inst = std::make_unique<bsp::LinuxCellularAudio>(callback );
+ inst = std::make_unique<bsp::LinuxCellularAudio>(callback);
#else
#error "Unsupported target"
#endif
- }
- break;
-
+ } break;
}
- if(inst->isInitialized){
+ if (inst->isInitialized) {
return inst;
}
return {};
}
-
-}
+} // namespace bsp
M module-bsp/bsp/audio/bsp_audio.hpp => module-bsp/bsp/audio/bsp_audio.hpp +1 -4
@@ 9,7 9,7 @@
namespace bsp
{
- class AudioDevice
+ class AudioDevice : public audio::IOProxy
{
public:
@@ 140,9 140,6 @@ namespace bsp
return callback;
}
- audio::Sink sink;
- audio::Source source;
-
protected:
Format currentFormat;
audioCallback_t callback = nullptr;
M module-bsp/bsp/lpm/bsp_lpm.cpp => module-bsp/bsp/lpm/bsp_lpm.cpp +5 -0
@@ 35,4 35,9 @@ namespace bsp{
{
return currentOscSource;
}
+
+ LowPowerMode::Pll2State LowPowerMode::GetCurrentPll2State() const noexcept
+ {
+ return currentPll2State;
+ }
}
M module-bsp/bsp/lpm/bsp_lpm.hpp => module-bsp/bsp/lpm/bsp_lpm.hpp +9 -0
@@ 23,6 23,11 @@ namespace bsp {
External,
Internal
};
+ enum class Pll2State
+ {
+ Enable,
+ Disable
+ };
LowPowerMode() = default;
virtual ~LowPowerMode() = default;
@@ 38,9 43,13 @@ namespace bsp {
virtual void SwitchOscillatorSource(OscillatorSource source) = 0;
[[nodiscard]] OscillatorSource GetCurrentOscillatorSource() const noexcept;
+ virtual void SwitchPll2State(Pll2State state) = 0;
+ [[nodiscard]] Pll2State GetCurrentPll2State() const noexcept;
+
protected:
CpuFrequency currentFrequency = CpuFrequency::Level_6;
OscillatorSource currentOscSource = OscillatorSource::External;
+ Pll2State currentPll2State = Pll2State::Enable;
};
} // namespace bsp
M module-bsp/targets/Target_RT1051.cmake => module-bsp/targets/Target_RT1051.cmake +1 -1
@@ 58,7 58,7 @@ set(BOARD_SOURCES ${BOARD_SOURCES}
"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/battery-charger/battery_charger.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/RT1051Audiocodec.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/RT1051CellularAudio.cpp"
- "${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/RT1051BluetoothAudio.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/SAIAudioDevice.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/CodecMAX98090.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/qfilter.c"
"${USB_SRC}"
M module-gui/gui/input/Profile.cpp => module-gui/gui/input/Profile.cpp +10 -2
@@ 10,14 10,22 @@
namespace gui
{
- Profile::Profile(const std::string &filepath) : name(filepath), inputChars(createJson(filepath))
- {}
+ Profile::Profile(const std::filesystem::path &filepath)
+ {
+ name = filepath.stem();
+ inputChars = createJson(filepath);
+ }
const std::string &Profile::getName() noexcept
{
return name;
}
+ const json11::Json &Profile::getJson() const noexcept
+ {
+ return inputChars;
+ }
+
const json11::Json Profile::createJson(const std::string &filepath)
{
auto fd = std::fopen(filepath.c_str(), "r");
M module-gui/gui/input/Profile.hpp => module-gui/gui/input/Profile.hpp +3 -1
@@ 7,6 7,7 @@
#include <vector>
#include <map>
#include "json/json11.hpp"
+#include <filesystem>
namespace gui
{
@@ 22,10 23,11 @@ namespace gui
public:
static constexpr uint32_t none_key = 0;
Profile() = default;
- explicit Profile(const std::string &filepath);
+ explicit Profile(const std::filesystem::path &filepath);
[[nodiscard]] const std::string &getName() noexcept;
[[nodiscard]] uint32_t getCharKey(bsp::KeyCodes code, uint32_t times);
+ [[nodiscard]] const json11::Json &getJson() const noexcept;
};
} /* namespace gui */
M module-gui/gui/input/Translator.cpp => module-gui/gui/input/Translator.cpp +42 -19
@@ 4,16 4,17 @@
#include "Translator.hpp"
#include "log/log.hpp"
#include <algorithm>
-#include <log/log.hpp>
#include <filesystem>
+#include "i18n/i18n.hpp"
namespace gui
{
- namespace
+ namespace filetype
{
- constexpr auto profilesFolder = "assets/profiles";
- constexpr auto extension = ".json";
- } // namespace
+ constexpr auto jsonKey = "filetype";
+ constexpr auto normal = "normal";
+ constexpr auto special = "special";
+ } // namespace filetype
void recon_long_press(InputEvent &evt, const RawKey &key, const RawKey &prev_key_press, uint32_t time)
{
@@ 202,24 203,46 @@ namespace gui
}
}
- std::vector<std::string> Profiles::getProfilesPaths()
+ std::vector<std::string> Profiles::getProfilesNames()
{
- std::vector<std::string> profileFiles;
- LOG_INFO("Scanning %s profiles folder: %s", extension, profilesFolder);
+ std::vector<std::string> profilesNames;
+ LOG_INFO("Scanning %s profiles folder: %s",
+ utils::files::jsonExtension,
+ utils::localize.InputLanguageDirPath.c_str());
- for (const auto &entry : std::filesystem::directory_iterator(profilesFolder)) {
- profileFiles.push_back(std::filesystem::path(entry.path()));
+ for (const auto &entry : std::filesystem::directory_iterator(utils::localize.InputLanguageDirPath)) {
+ profilesNames.push_back(std::filesystem::path(entry.path().stem()));
}
- LOG_INFO("Total number of profiles: %u", static_cast<unsigned int>(profileFiles.size()));
- return profileFiles;
+ LOG_INFO("Total number of profiles: %u", static_cast<unsigned int>(profilesNames.size()));
+ return profilesNames;
+ }
+
+ std::vector<std::string> Profiles::getAvailableInputLanguages()
+ {
+ std::vector<std::string> profilesNames = getProfilesNames(), availableProfiles;
+
+ for (auto &name : profilesNames) {
+ auto profile = get().profilesList[name];
+ if (profile.getJson()[filetype::jsonKey] == filetype::normal) {
+ auto breakSignPosition = name.find_last_of(utils::files::breakSign);
+ std::string displayedLanguageName = name.substr(0, breakSignPosition);
+
+ if (std::find(availableProfiles.begin(), availableProfiles.end(), displayedLanguageName) ==
+ availableProfiles.end()) {
+ availableProfiles.push_back(displayedLanguageName);
+ }
+ }
+ }
+ return availableProfiles;
}
void Profiles::init()
{
- std::vector<std::string> profileFilesPaths = getProfilesPaths();
- for (std::string filePath : profileFilesPaths) {
- if (std::size(filePath)) {
+ std::vector<std::string> profilesNames = getProfilesNames();
+ for (const auto &profileName : profilesNames) {
+ if (!profileName.empty()) {
+ auto filePath = utils::localize.InputLanguageDirPath / (profileName + utils::files::jsonExtension);
loadProfile(filePath);
}
}
@@ 240,16 263,16 @@ namespace gui
Profile &Profiles::get(const std::string &name)
{
- auto filepath = std::string(profilesFolder) + "/" + name + extension;
+ std::filesystem::path filepath = utils::localize.InputLanguageDirPath / (name + utils::files::jsonExtension);
// if profile not in profile map -> load
- if (std::size(filepath) == 0) {
+ if (filepath.empty()) {
LOG_ERROR("Request for nonexistent profile: %s", filepath.c_str());
return get().empty;
}
- if (get().profilesList.find(filepath) == get().profilesList.end()) {
+ if (get().profilesList.find(name) == get().profilesList.end()) {
get().loadProfile(filepath);
}
- return get().profilesList[filepath];
+ return get().profilesList[name];
}
} /* namespace gui */
M module-gui/gui/input/Translator.hpp => module-gui/gui/input/Translator.hpp +2 -1
@@ 57,13 57,14 @@ namespace gui
std::map<std::string, gui::Profile> profilesList = {};
void loadProfile(const std::string &filepath);
- std::vector<std::string> getProfilesPaths();
+ std::vector<std::string> getProfilesNames();
void init();
Profile empty;
static Profiles &get();
public:
+ std::vector<std::string> getAvailableInputLanguages();
Profile &get(const std::string &name);
};
M module-gui/gui/widgets/BoxLayout.cpp => module-gui/gui/widgets/BoxLayout.cpp +1 -1
@@ 31,7 31,7 @@ namespace gui
if (handleNavigation(inputEvent)) {
return true;
}
- if (borderCallback && borderCallback(inputEvent)) {
+ if (borderCallback && isInputNavigation(inputEvent) && borderCallback(inputEvent)) {
outOfDrawAreaItems.clear();
return true;
}
M module-gui/gui/widgets/GridLayout.cpp => module-gui/gui/widgets/GridLayout.cpp +49 -9
@@ 19,25 19,28 @@ GridLayout::GridLayout(
if (inputEvent.state != InputEvent::State::keyReleasedShort) {
return false;
}
+
+ auto it = this->getNavigationFocusedItem();
+ auto distance = std::distance(children.begin(), it);
switch (inputEvent.keyCode) {
case KeyCode::KEY_UP: {
- auto it = this->getNavigationFocusedItem();
- this->setFocusItem((*std::next(it, (this->rowSize - 1) * this->colSize)));
+ auto realRowSize = calculateRowSizeForBorderTransition(distance);
+ this->setFocusItem((*std::next(it, (realRowSize - 1) * this->colSize)));
return true;
}
case KeyCode::KEY_DOWN: {
- auto it = this->getNavigationFocusedItem();
- this->setFocusItem((*std::prev(it, (this->rowSize - 1) * this->colSize)));
+ auto realRowSize = calculateRowSizeForBorderTransition(distance);
+ this->setFocusItem((*std::prev(it, (realRowSize - 1) * this->colSize)));
return true;
}
case KeyCode::KEY_LEFT: {
- auto it = this->getNavigationFocusedItem();
- this->setFocusItem((*std::next(it, this->colSize - 1)));
+ auto realColSize = calculateColumnSizeForBorderTransition(distance);
+ this->setFocusItem((*std::next(it, realColSize - 1)));
return true;
}
case KeyCode::KEY_RIGHT: {
- auto it = this->getNavigationFocusedItem();
- this->setFocusItem((*std::prev(it, this->colSize - 1)));
+ auto realColSize = calculateColumnSizeForBorderTransition(distance);
+ this->setFocusItem((*std::prev(it, realColSize - 1)));
return true;
}
default: {
@@ 47,6 50,35 @@ GridLayout::GridLayout(
};
}
+uint32_t GridLayout::calculateColumnSizeForBorderTransition(const uint32_t currentPosition)
+{
+ auto realColSize = colSize;
+ if (elementsInIncompletedLastRow) {
+ if (((currentPosition / colSize) + 1) >= rowSize)
+ realColSize = elementsInIncompletedLastRow;
+ }
+ return realColSize;
+}
+uint32_t GridLayout::calculateRowSizeForBorderTransition(const uint32_t currentPosition)
+{
+ auto realRowSize = rowSize;
+ if (elementsInIncompletedLastRow) {
+ if (((currentPosition % (colSize)) + 1) > elementsInIncompletedLastRow)
+ realRowSize--;
+ }
+ return realRowSize;
+}
+
+void GridLayout::handleItemsOutOfGridLayoutArea(uint32_t maxItemsInArea)
+{
+ for (auto i = maxItemsInArea; i < children.size(); i++) {
+ auto it = std::next(children.begin(), i);
+ if ((*it)->visible) {
+ addToOutOfDrawAreaList(*it);
+ }
+ }
+}
+
void GridLayout::resizeItems()
{
if (grid.x == 0 || grid.y == 0) {
@@ 56,8 88,15 @@ void GridLayout::resizeItems()
uint32_t el_in_x = area().w / grid.x;
uint32_t el_in_y = area().h / grid.y;
+ elementsInIncompletedLastRow = 0;
colSize = children.size() < area().w / grid.x ? children.size() : area().w / grid.x;
- rowSize = colSize != 0 ? children.size() / colSize : 1;
+ rowSize = colSize != 0 ? (children.size() / colSize) : 1;
+ if (colSize > 1 && (static_cast<double>(children.size()) / colSize) > 1.0) {
+ elementsInIncompletedLastRow = children.size() % colSize;
+ }
+ if (elementsInIncompletedLastRow > 0) {
+ rowSize++;
+ }
uint32_t strech_x = 0;
uint32_t strech_y = 0;
@@ 71,6 110,7 @@ void GridLayout::resizeItems()
LOG_ERROR("More children than possible to show: %u > %" PRIu32 "",
static_cast<unsigned int>(children.size()),
max_elements);
+ handleItemsOutOfGridLayoutArea(max_elements);
return;
}
if (el_in_x > 2)
M module-gui/gui/widgets/GridLayout.hpp => module-gui/gui/widgets/GridLayout.hpp +9 -0
@@ 34,6 34,15 @@ namespace gui
uint32_t rowSize = 0;
uint32_t colSize = 0;
+ ///> elementsInIncompletedLastRow describes how many items has been put to last row,
+ /// in case when items for last row is not equal to colSize
+ uint32_t elementsInIncompletedLastRow = 0;
+
+ private:
+ uint32_t calculateColumnSizeForBorderTransition(const uint32_t currentPosition);
+ uint32_t calculateRowSizeForBorderTransition(const uint32_t currentPosition);
+
+ void handleItemsOutOfGridLayoutArea(uint32_t maxItemsInArea);
};
}; // namespace gui
M module-gui/gui/widgets/Icon.cpp => module-gui/gui/widgets/Icon.cpp +5 -10
@@ 18,11 18,10 @@ namespace style
} // namespace img
namespace text
{
- constexpr uint32_t x = 40;
- constexpr uint32_t y = 247;
- constexpr uint32_t w = 400;
- constexpr uint32_t h = 66;
- constexpr Color color = {7, 0};
+ constexpr uint32_t x = 40;
+ constexpr uint32_t y = 247;
+ constexpr uint32_t w = 400;
+ constexpr uint32_t h = 80;
} // namespace text
} // namespace style
@@ 41,13 40,9 @@ Icon::Icon(Item *parent,
new Image(this, style::img::x, style::img::y, imageName);
text = new Text(this, style::text::x, style::text::y, style::text::w, style::text::h);
+ text->setRichText(str);
text->setTextType(TextType::MultiLine);
text->setEditMode(EditMode::Browse);
text->setEdges(RectangleEdge::None);
text->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
-
- auto format = TextFormat(Font(27).raw(), Color(7, 0));
- for (auto &el : textToTextBlocks(str, format)) {
- text->addText(el);
- }
}
M module-gui/gui/widgets/InputMode.cpp => module-gui/gui/widgets/InputMode.cpp +10 -5
@@ 8,10 8,10 @@
/// input mode strings - as these are stored in json (in files...)
const std::map<InputMode::Mode, std::string> input_mode = {
- {InputMode::digit, "common_kbd_numeric"},
- {InputMode::ABC, "common_kbd_upper"},
- {InputMode::abc, "common_kbd_lower"},
- {InputMode::phone, "common_kbd_phone"},
+ {InputMode::digit, "numeric"},
+ {InputMode::ABC, "upper"},
+ {InputMode::abc, "lower"},
+ {InputMode::phone, "phone"},
};
static std::string getInputName(InputMode::Mode m)
@@ 61,7 61,12 @@ void InputMode::next()
const std::string &InputMode::get()
{
- return utils::localize.getInputLanguage(input_mode.at(modeNow()));
+ auto actualInputMode = input_mode.at(modeNow());
+ if (actualInputMode == input_mode.find(InputMode::digit)->second ||
+ actualInputMode == input_mode.find(InputMode::phone)->second) {
+ return input_mode.at(modeNow());
+ }
+ return utils::localize.getInputLanguage(actualInputMode);
}
void InputMode::show_input_type()
M module-gui/gui/widgets/Item.cpp => module-gui/gui/widgets/Item.cpp +5 -5
@@ 30,6 30,11 @@ namespace gui
}
}
+ bool isInputNavigation(const InputEvent &evt)
+ {
+ return inputToNavigation(evt) != NavigationDirection::NONE;
+ }
+
Item::Item()
: focus{false}, type{ItemType::ITEM}, parent{nullptr}, radius{0}, visible{true},
verticalPolicy{LayoutVerticalPolicy::LAYOUT_POLICY_VERTICAL_EXPAND},
@@ 510,11 515,6 @@ namespace gui
return false;
}
- bool Item::onContent()
- {
- return false;
- }
-
auto Item::onTimer(Timer &timer) -> bool
{
if (timerCallback != nullptr) {
M module-gui/gui/widgets/Item.hpp => module-gui/gui/widgets/Item.hpp +1 -8
@@ 139,9 139,6 @@ namespace gui
/// @param `this` item
/// @param `InputEvent`
std::function<bool(Item &, const InputEvent &inputEvent)> inputCallback;
- /// callback when element insides are changed
- /// @param `this` item
- std::function<bool(Item &)> contentCallback;
/// callback when timer is called on Item and onTimer is executed
/// @param `this` item
/// @param `timer` which triggered this callback
@@ 183,10 180,6 @@ namespace gui
/// calls: none, inconsistent api
/// @note TODO should be fixed so that api would be consistent
virtual bool onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim);
- /// (should be) called each time content in item was changed, added for gui::Text widget
- /// calls: none, inconsistent behaviour
- /// @note TODO should be fixed so that api would be consistent
- virtual bool onContent();
/// called on Timer event in application, triggeres timerCallback
/// @param timer timer element which triggered this action
virtual bool onTimer(Timer &timer);
@@ 363,5 356,5 @@ namespace gui
};
NavigationDirection inputToNavigation(const InputEvent &evt);
-
+ bool isInputNavigation(const InputEvent &evt);
} /* namespace gui */
M module-gui/test/test-google/CMakeLists.txt => module-gui/test/test-google/CMakeLists.txt +1 -0
@@ 6,6 6,7 @@ include_directories(${gmock_SOURCE_DIR}/include ${gmock_SOURCE_DIR})
add_executable(${PROJECT_NAME} EXCLUDE_FROM_ALL
test-gui-listview.cpp
test-gui-boxlayout.cpp
+ test-gui-gridlayout.cpp
test-gui-depthfirst-itemtree.cpp
test-gui-visitor-call.cpp
test-gui-dom-dump.cpp
A module-gui/test/test-google/test-gui-gridlayout.cpp => module-gui/test/test-google/test-gui-gridlayout.cpp +248 -0
@@ 0,0 1,248 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "gtest/gtest.h"
+#include <module-utils/log/log.hpp>
+#include <module-gui/gui/widgets/BoxLayout.hpp>
+#include <module-gui/gui/widgets/GridLayout.hpp>
+#include <gui/input/InputEvent.hpp>
+
+namespace testStyle
+{
+ const inline uint32_t box_x = 0;
+ const inline uint32_t box_y = 0;
+ const inline uint32_t box_w = 600;
+ const inline uint32_t box_h = 200;
+
+ const inline uint32_t grid_item_w = 50;
+ const inline uint32_t grid_item_h = 50;
+} // namespace testStyle
+
+class TestItem : public gui::Rect
+{
+ public:
+ unsigned int ID = 0;
+ TestItem(Item *parent, uint32_t x, uint32_t y, uint32_t w, uint32_t h) : Rect(parent, x, y, w, h){};
+};
+
+class GridLayoutTesting : public ::testing::Test
+{
+ protected:
+ void SetUp() override
+ {
+ gridLayout = new gui::GridLayout(testStyle::box_x,
+ testStyle::box_y,
+ testStyle::box_w,
+ testStyle::box_h,
+ {testStyle::grid_item_w, testStyle::grid_item_h});
+
+ ASSERT_EQ(0, gridLayout->children.size()) << "GridLayout should be empty";
+ }
+
+ void TearDown() override
+ {
+ delete gridLayout;
+ }
+
+ void moveNTimes(gui::BoxLayout *Box, unsigned int n, gui::KeyCode key)
+ {
+ for (unsigned int i = 0; i < n; i++) {
+ Box->onInput(gui::InputEvent({}, gui::InputEvent::State::keyReleasedShort, key));
+ }
+ }
+
+ void addNItems(gui::BoxLayout *Box,
+ unsigned int n,
+ uint32_t item_w,
+ uint32_t item_h,
+ const gui::Margins &margins = gui::Margins())
+ {
+ for (unsigned int i = 1; i <= n; i++) {
+
+ auto item = new TestItem(nullptr, 0, 0, item_w, item_h);
+ item->ID = i;
+ item->visible = true;
+ item->setMargins(margins);
+
+ Box->addWidget(item);
+ }
+ }
+
+ gui::GridLayout *gridLayout = nullptr;
+
+ ///> GridLayout test constants
+ const uint32_t expRowSize = testStyle::box_h / testStyle::grid_item_h;
+ const uint32_t expColSize = testStyle::box_w / testStyle::grid_item_w;
+ const uint32_t allElementsCount = expRowSize * expColSize;
+};
+
+TEST_F(GridLayoutTesting, Constructor_Destructor_Test)
+{
+ // Check that there are no memory leaks - done by fixture setup and teardown.
+}
+
+TEST_F(GridLayoutTesting, Fill_GridLayout_Test)
+{
+ for (uint32_t i = 0; i < allElementsCount; i++) {
+ addNItems(gridLayout, 1, testStyle::grid_item_w, testStyle::grid_item_h);
+ ASSERT_EQ(i + 1, gridLayout->children.size()) << "GridLayout should contain " << i << " elements";
+ ASSERT_TRUE(gridLayout->children.back()->visible) << "Last element should be visible";
+ }
+ addNItems(gridLayout, 1, testStyle::grid_item_w, testStyle::grid_item_h);
+ ASSERT_EQ(allElementsCount + 1, gridLayout->children.size())
+ << "GridLayout should contain " << allElementsCount + 1 << " elements";
+ ASSERT_FALSE(gridLayout->children.back()->visible) << "Last element shouldn't be visible";
+}
+
+TEST_F(GridLayoutTesting, Navigate_Test)
+{
+ ///> Test for grid layout with 48 elements
+ ///> | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
+ ///> | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
+ ///> | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
+ ///> | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
+ addNItems(gridLayout, allElementsCount, testStyle::grid_item_w, testStyle::grid_item_h);
+ gridLayout->setFocus(true);
+ ASSERT_EQ(gridLayout->rowSize, 4) << "row size is not " << 4 << " as expected";
+ ASSERT_EQ(gridLayout->colSize, 12) << "col size is not " << 12 << " as expected";
+ ASSERT_EQ(allElementsCount, gridLayout->children.size())
+ << "GridLayout should contain " << allElementsCount << " elements";
+
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 2, gui::KeyCode::KEY_RIGHT);
+ ASSERT_EQ(3, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 3 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_DOWN);
+ ASSERT_EQ(3 + expColSize, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 3 + expColSize << " should have focus";
+ moveNTimes(gridLayout, 2, gui::KeyCode::KEY_LEFT);
+ ASSERT_EQ(1 + expColSize, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 + expColSize << " should have focus";
+}
+///> TODO: Enable this test when issue with setFocus will be resolved
+TEST_F(GridLayoutTesting, DISABLED_Border_Callback_Test)
+{
+ ///> Test for grid layout with 46 elements
+ ///> | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
+ ///> | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
+ ///> | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
+ ///> | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
+ addNItems(gridLayout, allElementsCount - 2, testStyle::grid_item_w, testStyle::grid_item_h);
+ gridLayout->setFocus(true);
+
+ ASSERT_EQ(gridLayout->rowSize, 4) << "row size is not " << 4 << " as expected";
+ ASSERT_EQ(gridLayout->colSize, 12) << "col size is not " << 12 << " as expected";
+ ASSERT_EQ(allElementsCount - 2, gridLayout->children.size())
+ << "GridLayout should contain " << allElementsCount - 2 << " elements";
+
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_UP);
+ ASSERT_EQ(37, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 37 << " should have focus";
+ moveNTimes(gridLayout, 9, gui::KeyCode::KEY_RIGHT);
+ ASSERT_EQ(46, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 46 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_RIGHT);
+ ASSERT_EQ(37, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 37 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_LEFT);
+ ASSERT_EQ(46, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 37 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_DOWN);
+ ASSERT_EQ(10, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 10 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_RIGHT);
+ ASSERT_EQ(11, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 11 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_UP);
+ ASSERT_EQ(35, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 35 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_DOWN);
+ ASSERT_EQ(11, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 11 << " should have focus";
+
+ ///> Test for grid layout with 1 element
+ ///> | 1 |
+ gridLayout->erase();
+ addNItems(gridLayout, 1, testStyle::grid_item_w, testStyle::grid_item_h);
+ ASSERT_EQ(gridLayout->children.size(), 1) << "elements size is not " << 1 << " as expected";
+ ASSERT_EQ(gridLayout->rowSize, 1) << "row size is not " << 1 << " as expected";
+ ASSERT_EQ(gridLayout->colSize, 1) << "col size is not " << 1 << " as expected";
+ gridLayout->setFocus(true);
+
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_UP);
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_DOWN);
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_LEFT);
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_RIGHT);
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+
+ ///> Test for grid layout with 2 elements
+ ///> | 1 | 2 |
+ gridLayout->erase();
+ addNItems(gridLayout, 2, testStyle::grid_item_w, testStyle::grid_item_h);
+ ASSERT_EQ(gridLayout->children.size(), 2) << "elements size is not " << 2 << " as expected";
+ ASSERT_EQ(gridLayout->rowSize, 1) << "row size is not " << 1 << " as expected";
+ ASSERT_EQ(gridLayout->colSize, 2) << "col size is not " << 2 << " as expected";
+ gridLayout->setFocus(true);
+
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_UP);
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_DOWN);
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_LEFT);
+ ASSERT_EQ(2, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 2 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_UP);
+ ASSERT_EQ(2, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 2 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_DOWN);
+ ASSERT_EQ(2, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 2 << " should have focus";
+ moveNTimes(gridLayout, 1, gui::KeyCode::KEY_RIGHT);
+ ASSERT_EQ(1, dynamic_cast<TestItem *>(gridLayout->getFocusItem())->ID)
+ << "element with ID " << 1 << " should have focus";
+}
+
+TEST_F(GridLayoutTesting, Items_Position_Test)
+{
+ ///> Test for grid layout with 48 elements
+ ///> | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
+ ///> | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
+ ///> | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
+ ///> | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
+ addNItems(gridLayout, allElementsCount, testStyle::grid_item_w, testStyle::grid_item_h);
+ ASSERT_EQ(allElementsCount, gridLayout->children.size()) << "GridLayout should contain 48 elements";
+
+ for (auto item : gridLayout->children) {
+ auto currColumn = (dynamic_cast<TestItem *>(item)->ID - 1) % expColSize;
+ auto currRow = (dynamic_cast<TestItem *>(item)->ID - 1) / expColSize;
+ auto currPos_X = currColumn * testStyle::grid_item_w;
+ auto currPos_Y = currRow * testStyle::grid_item_h;
+ ASSERT_EQ(item->getPosition(gui::Axis::Y), currPos_Y)
+ << "Y coordinate value for " << dynamic_cast<TestItem *>(item)->ID << " is invalid";
+ ASSERT_EQ(item->getPosition(gui::Axis::X), currPos_X)
+ << "X coordinate value for " << dynamic_cast<TestItem *>(item)->ID << " is invalid";
+ }
+ gridLayout->erase();
+ addNItems(gridLayout, allElementsCount + 1, testStyle::grid_item_w, testStyle::grid_item_h);
+ ASSERT_EQ(allElementsCount + 1, gridLayout->children.size()) << "GridLayout should contain 49 elements";
+
+ ///> Make sure that not visible elements has not been moved
+ ASSERT_EQ((*gridLayout->children.rbegin())->getPosition(gui::Axis::Y), 0);
+ ASSERT_EQ((*gridLayout->children.rbegin())->getPosition(gui::Axis::X), 0);
+}
M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +2 -2
@@ 66,9 66,9 @@ sys::ReturnCodes ServiceAudio::InitHandler()
// ROUTING
{dbPath(Setting::Gain, PlaybackType::None, Profile::Type::RoutingBluetoothHSP), "20"},
- {dbPath(Setting::Gain, PlaybackType::None, Profile::Type::RoutingEarspeaker), "20"},
+ {dbPath(Setting::Gain, PlaybackType::None, Profile::Type::RoutingEarspeaker), "0"},
{dbPath(Setting::Gain, PlaybackType::None, Profile::Type::RoutingLoudspeaker), "20"},
- {dbPath(Setting::Gain, PlaybackType::None, Profile::Type::RoutingHeadphones), "50"},
+ {dbPath(Setting::Gain, PlaybackType::None, Profile::Type::RoutingHeadphones), "0"},
{dbPath(Setting::Volume, PlaybackType::None, Profile::Type::RoutingBluetoothHSP), defaultVolumeHigh},
{dbPath(Setting::Volume, PlaybackType::None, Profile::Type::RoutingEarspeaker), defaultVolumeHigh},
M module-services/service-bluetooth/CMakeLists.txt => module-services/service-bluetooth/CMakeLists.txt +1 -0
@@ 5,6 5,7 @@ message( "${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}" )
set(SOURCES
ServiceBluetooth.cpp
service-bluetooth/SettingsHolder.cpp
+ service-bluetooth/SettingsSerializer.cpp
)
add_library(${PROJECT_NAME} STATIC ${SOURCES})
M module-services/service-bluetooth/ServiceBluetooth.cpp => module-services/service-bluetooth/ServiceBluetooth.cpp +62 -21
@@ 15,6 15,7 @@
#include <service-db/Settings.hpp>
#include "service-bluetooth/messages/Status.hpp"
#include "service-bluetooth/messages/SetStatus.hpp"
+#include <service-bluetooth/messages/BondedDevices.hpp>
#include <log/log.hpp>
@@ 24,7 25,7 @@
ServiceBluetooth::ServiceBluetooth() : sys::Service(service::name::bluetooth)
{
auto settings = std::make_unique<settings::Settings>(this);
- settingsHolder = std::make_unique<Bluetooth::SettingsHolder>(std::move(settings));
+ settingsHolder = std::make_shared<Bluetooth::SettingsHolder>(std::move(settings));
LOG_INFO("[ServiceBluetooth] Initializing");
}
@@ 39,8 40,54 @@ sys::ReturnCodes ServiceBluetooth::InitHandler()
{
LOG_ERROR("Bluetooth experimental!");
worker = std::make_unique<BluetoothWorker>(this);
- btStatus.state = BluetoothStatus::BluetoothState::On;
- btStatus.visibility = true;
+
+ connect(message::bluetooth::RequestBondedDevices(), [&](sys::Message *msg) {
+ auto bondedDevicesStr =
+ std::visit(Bluetooth::StringVisitor(), this->settingsHolder->getValue(Bluetooth::Settings::BondedDevices));
+
+ return std::make_shared<message::bluetooth::ResponseBondedDevices>(
+ SettingsSerializer::fromString(bondedDevicesStr));
+ });
+
+ connect(message::bluetooth::RequestStatus(), [&](sys::Message *msg) {
+ BluetoothStatus status;
+
+ auto state = std::visit(Bluetooth::IntVisitor(), settingsHolder->getValue(Bluetooth::Settings::State));
+ auto visibility =
+ std::visit(Bluetooth::BoolVisitor(), settingsHolder->getValue(Bluetooth::Settings::Visibility));
+ status.state = static_cast<BluetoothStatus::State>(state);
+ status.visibility = visibility;
+
+ return std::make_shared<message::bluetooth::ResponseStatus>(status);
+ });
+
+ connect(typeid(message::bluetooth::SetStatus), [&](sys::Message *msg) {
+ auto setStatusMsg = static_cast<message::bluetooth::SetStatus *>(msg);
+ auto btStatus = setStatusMsg->getStatus();
+ worker->setVisibility(btStatus.visibility);
+
+ switch (btStatus.state) {
+ case BluetoothStatus::State::On:
+ worker->run();
+ break;
+ case BluetoothStatus::State::Off:
+ // TODO
+ break;
+ default:
+ break;
+ }
+
+ return std::make_shared<message::bluetooth::ResponseStatus>(btStatus);
+ });
+
+ settingsHolder->onStateChange = [this]() {
+ auto initialState =
+ std::visit(Bluetooth::IntVisitor(), this->settingsHolder->getValue(Bluetooth::Settings::State));
+ if (static_cast<BluetoothStatus::State>(initialState) == BluetoothStatus::State::On) {
+ this->worker->run();
+ }
+ };
+
return sys::ReturnCodes::Success;
}
@@ 52,17 99,6 @@ sys::ReturnCodes ServiceBluetooth::DeinitHandler()
sys::MessagePointer ServiceBluetooth::DataReceivedHandler(sys::DataMessage *msg, sys::ResponseMessage *resp)
{
- // mock response on message::bluetooth::RequestStatus
- if (auto requestStatusMsg = dynamic_cast<message::bluetooth::RequestStatus *>(msg); nullptr != requestStatusMsg) {
- sys::Bus::SendUnicast(std::make_shared<message::bluetooth::ResponseStatus>(btStatus), msg->sender, this);
- }
-
- // temporary solution for handling message::bluetooth::SetStatus
- if (auto setStatusMsg = dynamic_cast<message::bluetooth::SetStatus *>(msg); nullptr != setStatusMsg) {
- btStatus = setStatusMsg->getStatus();
- sys::Bus::SendBroadcast(std::make_shared<message::bluetooth::ResponseStatus>(btStatus), this);
- }
-
try {
switch (static_cast<MessageType>(msg->messageType)) {
case MessageType::BluetoothRequest: {
@@ 81,7 117,7 @@ sys::MessagePointer ServiceBluetooth::DataReceivedHandler(sys::DataMessage *msg,
return std::make_shared<sys::ResponseMessage>(sys::ReturnCodes::Failure);
}
case BluetoothMessage::StopScan:
- worker->stopScan();
+ sendWorkerCommand(Bt::StopScan);
break;
case BluetoothMessage::PAN: {
/// TODO request lwip first...
@@ 97,15 133,17 @@ sys::MessagePointer ServiceBluetooth::DataReceivedHandler(sys::DataMessage *msg,
worker->start_pan();
// }
} break;
- case BluetoothMessage::Visible:
- worker->toggleVisibility();
- break;
+ case BluetoothMessage::Visible: {
+ static bool visibility = true;
+ worker->setVisibility(visibility);
+ visibility = !visibility;
+ } break;
case BluetoothMessage::Play:
- worker->establishAudioConnection();
+ sendWorkerCommand(Bt::ConnectAudio);
break;
case BluetoothMessage::Stop:
- worker->disconnectAudioConnection();
+ sendWorkerCommand(Bt::DisconnectAudio);
break;
default:
@@ 139,4 177,7 @@ sys::ReturnCodes ServiceBluetooth::SwitchPowerModeHandler(const sys::ServicePowe
LOG_ERROR("TODO");
return sys::ReturnCodes::Success;
}
-
+void ServiceBluetooth::sendWorkerCommand(Bt::Command command)
+{
+ xQueueSend(workerQueue, &command, portMAX_DELAY);
+}
M module-services/service-bluetooth/service-bluetooth/BluetoothMessage.hpp => module-services/service-bluetooth/service-bluetooth/BluetoothMessage.hpp +1 -1
@@ 21,7 21,7 @@ extern "C"
struct BluetoothStatus
{
- enum class BluetoothState
+ enum class State
{
On,
Off,
M module-services/service-bluetooth/service-bluetooth/ServiceBluetooth.hpp => module-services/service-bluetooth/service-bluetooth/ServiceBluetooth.hpp +3 -2
@@ 29,9 29,10 @@ class ServiceBluetooth : public sys::Service
sys::ReturnCodes InitHandler() override;
sys::ReturnCodes DeinitHandler() override;
virtual sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override;
+ void sendWorkerCommand(Bt::Command command);
+ QueueHandle_t workerQueue = nullptr;
+ std::shared_ptr<Bluetooth::SettingsHolder> settingsHolder;
private:
std::unique_ptr<BluetoothWorker> worker;
- std::unique_ptr<Bluetooth::SettingsHolder> settingsHolder;
- BluetoothStatus btStatus; // will be replaced with settings storage introduced in [EGD-4579]
};
M module-services/service-bluetooth/service-bluetooth/SettingsHolder.cpp => module-services/service-bluetooth/service-bluetooth/SettingsHolder.cpp +17 -9
@@ 2,14 2,14 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "SettingsHolder.hpp"
-#include <log/log.hpp>
namespace Bluetooth
{
std::map<Settings, std::string> SettingsHolder::settingString{
{Settings::DeviceName, settings::Bluetooth::deviceName},
{Settings::Visibility, settings::Bluetooth::deviceVisibility},
- {Settings::Power, settings::Bluetooth::state},
+ {Settings::State, settings::Bluetooth::state},
{Settings::BondedDevices, settings::Bluetooth::bondedDevices},
+ {Settings::BtKeys, settings::Bluetooth::btKeys},
};
auto SettingsHolder::getValue(const Settings setting) -> SettingEntry
@@ 21,25 21,33 @@ namespace Bluetooth
settingsMap[newSetting] = value;
settingsProvider->setValue(settingString[newSetting], std::visit(StringVisitor(), value));
+ LOG_INFO("setting %s set: %s", settingString[newSetting].c_str(), std::visit(StringVisitor(), value).c_str());
}
SettingsHolder::SettingsHolder(std::unique_ptr<settings::Settings> settingsPtr)
: settingsProvider(std::move(settingsPtr))
{
- settingsProvider->registerValueChange(settings::Bluetooth::state, [this](std::string value) {
- setValue(Settings::Power, value);
- this->settingsProvider->unregisterValueChange(settings::Bluetooth::state);
- });
settingsProvider->registerValueChange(settings::Bluetooth::deviceVisibility, [this](std::string value) {
setValue(Settings::Visibility, value);
- this->settingsProvider->unregisterValueChange(settings::Bluetooth::deviceVisibility);
+ settingsProvider->unregisterValueChange(settings::Bluetooth::deviceVisibility);
});
settingsProvider->registerValueChange(settings::Bluetooth::deviceName, [this](std::string value) {
setValue(Settings::DeviceName, value);
- this->settingsProvider->unregisterValueChange(settings::Bluetooth::deviceName);
+ settingsProvider->unregisterValueChange(settings::Bluetooth::deviceName);
});
settingsProvider->registerValueChange(settings::Bluetooth::bondedDevices, [this](std::string value) {
setValue(Settings::BondedDevices, value);
- this->settingsProvider->unregisterValueChange(settings::Bluetooth::bondedDevices);
+ settingsProvider->unregisterValueChange(settings::Bluetooth::bondedDevices);
+ });
+ settingsProvider->registerValueChange(settings::Bluetooth::btKeys, [this](std::string value) {
+ setValue(Settings::BtKeys, value);
+ settingsProvider->unregisterValueChange(settings::Bluetooth::btKeys);
+ });
+ settingsProvider->registerValueChange(settings::Bluetooth::state, [this](std::string value) {
+ setValue(Settings::State, value);
+ settingsProvider->unregisterValueChange(settings::Bluetooth::state);
+ if (onStateChange) {
+ onStateChange();
+ }
});
}
} // namespace Bluetooth
M module-services/service-bluetooth/service-bluetooth/SettingsHolder.hpp => module-services/service-bluetooth/service-bluetooth/SettingsHolder.hpp +41 -3
@@ 1,12 1,13 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
+#pragma once
#include <json/json11.hpp>
#include <string>
#include <variant>
#include <service-db/Settings.hpp>
#include <agents/settings/SystemSettings.hpp>
#include <module-utils/Utils.hpp>
+#include "SettingsSerializer.hpp"
namespace Bluetooth
{
@@ 15,8 16,9 @@ namespace Bluetooth
{
DeviceName,
Visibility,
- Power,
- BondedDevices
+ State,
+ BondedDevices,
+ BtKeys
};
constexpr inline auto trueStr = "true";
@@ 39,12 41,48 @@ namespace Bluetooth
}
};
+ struct IntVisitor
+ {
+ auto operator()(const std::string &input) const -> int
+ {
+ int value;
+ utils::toNumeric(input, value);
+ return value;
+ }
+ auto operator()(bool input) const -> int
+ {
+ return input;
+ }
+ auto operator()(int input) const -> int
+ {
+ return input;
+ }
+ };
+
+ struct BoolVisitor
+ {
+ auto operator()(const std::string &input) const -> bool
+ {
+ return input == trueStr;
+ }
+ auto operator()(bool input) const -> bool
+ {
+ return input;
+ }
+ auto operator()(int input) const -> bool
+ {
+ return input != 0;
+ }
+ };
+
class SettingsHolder
{
public:
explicit SettingsHolder(std::unique_ptr<settings::Settings> settingsPtr);
auto getValue(const Settings setting) -> SettingEntry;
void setValue(const Settings &newSetting, const SettingEntry &value);
+ std::function<void()> onStateChange;
+ std::function<void(std::string addr)> onLinkKeyAdded;
private:
static std::map<Settings, std::string> settingString;
A module-services/service-bluetooth/service-bluetooth/SettingsSerializer.cpp => module-services/service-bluetooth/service-bluetooth/SettingsSerializer.cpp +47 -0
@@ 0,0 1,47 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "SettingsSerializer.hpp"
+#include "btstack_util.h"
+#include <log/log.hpp>
+
+namespace strings
+{
+ constexpr inline auto addr = "addr";
+ constexpr inline auto name = "name";
+ constexpr inline auto devices = "devices";
+} // namespace strings
+
+auto SettingsSerializer::toString(const std::vector<Devicei> &devices) -> std::string
+{
+ json11::Json::array devicesJson;
+ for (auto &device : devices) {
+ auto deviceEntry =
+ json11::Json::object{{strings::addr, bd_addr_to_str(device.address)}, {strings::name, device.name}};
+ devicesJson.emplace_back(deviceEntry);
+ }
+ json11::Json finalJson = json11::Json::object{{strings::devices, devicesJson}};
+
+ return finalJson.dump();
+}
+auto SettingsSerializer::fromString(const std::string &jsonStr) -> std::vector<Devicei>
+{
+ json11::Json devicesJson;
+ std::string err;
+ devicesJson = json11::Json::parse(jsonStr.c_str(), err);
+ if (!err.empty()) {
+ LOG_ERROR("Failed parsing device string!");
+ return std::vector<Devicei>();
+ }
+ json11::Json::array devicesArray;
+ devicesArray = std::move(devicesJson[strings::devices].array_items());
+
+ std::vector<Devicei> devicesVector;
+ for (auto &device : devicesArray) {
+ Devicei temp;
+ sscanf_bd_addr(device[strings::addr].string_value().c_str(), temp.address);
+ temp.name = device[strings::name].string_value();
+ devicesVector.emplace_back(temp);
+ }
+ return devicesVector;
+}
A module-services/service-bluetooth/service-bluetooth/SettingsSerializer.hpp => module-services/service-bluetooth/service-bluetooth/SettingsSerializer.hpp +14 -0
@@ 0,0 1,14 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <json/json11.hpp>
+#include <Device.hpp>
+
+class SettingsSerializer
+{
+ public:
+ static auto toString(const std::vector<Devicei> &devices) -> std::string;
+ static auto fromString(const std::string &jsonStr) -> std::vector<Devicei>;
+};
M module-services/service-cellular/CellularServiceAPI.cpp => module-services/service-cellular/CellularServiceAPI.cpp +6 -0
@@ 341,3 341,9 @@ bool CellularServiceAPI::GetDataTransfer(sys::Service *serv)
return sys::Bus::SendUnicast(
std::make_shared<CellularGetDataTransferMessage>(), ServiceCellular::serviceName, serv);
}
+
+bool CellularServiceAPI::SetVoLTE(sys::Service *serv, bool voLTE)
+{
+ return sys::Bus::SendUnicast(
+ std::make_shared<CellularChangeVoLTEDataMessage>(voLTE), ServiceCellular::serviceName, serv);
+}
M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +26 -2
@@ 198,7 198,8 @@ ServiceCellular::ServiceCellular() : sys::Service(serviceName, "", cellularStack
sys::Bus::SendMulticast(msg.value(), sys::BusChannels::ServiceCellularNotifications, this);
};
registerMessageHandlers();
-
+ settings->registerValueChange(settings::Cellular::volte_on,
+ [this](const std::string &value) { volteChanged(value); });
packetData = std::make_unique<packet_data::PacketData>(*this);
packetData->loadAPNSettings();
}
@@ 206,6 207,7 @@ ServiceCellular::ServiceCellular() : sys::Service(serviceName, "", cellularStack
ServiceCellular::~ServiceCellular()
{
LOG_INFO("[ServiceCellular] Cleaning resources");
+ settings->unregisterValueChange(settings::Cellular::volte_on);
}
// this static function will be replaced by Settings API
@@ 290,10 292,12 @@ void ServiceCellular::registerMessageHandlers()
bsp::cellular::sim::hotswap_trigger();
return std::make_shared<CellularResponseMessage>(true);
});
+
connect(typeid(CellularStartOperatorsScanMessage), [&](sys::Message *request) -> sys::MessagePointer {
auto msg = static_cast<CellularStartOperatorsScanMessage *>(request);
return handleCellularStartOperatorsScan(msg);
});
+
connect(typeid(CellularGetActiveContextsMessage), [&](sys::Message *request) -> sys::MessagePointer {
auto msg = static_cast<CellularGetActiveContextsMessage *>(request);
return handleCellularGetActiveContextsMessage(msg);
@@ 333,6 337,14 @@ void ServiceCellular::registerMessageHandlers()
auto msg = static_cast<CellularGetActiveContextsMessage *>(request);
return handleCellularGetActiveContextsMessage(msg);
});
+
+ connect(typeid(CellularChangeVoLTEDataMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularChangeVoLTEDataMessage *>(request);
+ volteOn = msg->getVoLTEon();
+ settings->setValue(settings::Cellular::volte_on, std::to_string(volteOn));
+ return std::make_shared<CellularResponseMessage>(true);
+ });
+
handle_CellularGetChannelMessage();
}
@@ 1032,6 1044,9 @@ sys::MessagePointer ServiceCellular::DataReceivedHandler(sys::DataMessage *msgl,
if (auto response = dynamic_cast<db::query::SMSSearchByTypeResult *>(result.get())) {
responseHandled = handle(response);
}
+ else if (result->hasListener()) {
+ responseHandled = result->handle();
+ }
}
if (responseHandled) {
return std::make_shared<sys::ResponseMessage>();
@@ 1808,7 1823,8 @@ bool ServiceCellular::dbAddSMSRecord(const SMSRecord &record)
onSMSReceived();
return true;
}));
- return DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::move(query));
+ const auto [succeed, _] = DBServiceAPI::GetQuery(this, db::Interface::Name::SMS, std::move(query));
+ return succeed;
}
void ServiceCellular::onSMSReceived()
@@ 2040,6 2056,7 @@ std::shared_ptr<cellular::RawCommandRespAsync> ServiceCellular::handleCellularSt
sys::Bus::SendUnicast(ret, msg->sender, this);
return ret;
}
+
bool ServiceCellular::handle_apn_conf_procedure()
{
LOG_DEBUG("APN on modem configuration");
@@ 2131,3 2148,10 @@ std::shared_ptr<CellularSetOperatorResponse> ServiceCellular::handleCellularSetO
return std::make_shared<CellularSetOperatorResponse>(
networkSettings.setOperator(msg->getMode(), msg->getFormat(), msg->getName()));
}
+
+void ServiceCellular::volteChanged(const std::string &value)
+{
+ if (!value.empty()) {
+ volteOn = utils::getNumericValue<bool>(value);
+ }
+}
M module-services/service-cellular/service-cellular/CellularMessage.hpp => module-services/service-cellular/service-cellular/CellularMessage.hpp +20 -0
@@ 695,6 695,26 @@ class CellularSetOperatorResponse : public CellularResponseMessage
{}
};
+class CellularVoLTEDataMessage : public CellularMessage
+{
+ bool VoLTEon = false;
+
+ public:
+ explicit CellularVoLTEDataMessage(bool VoLTEon) : CellularMessage{MessageType::CellularSetVoLTE}, VoLTEon{VoLTEon}
+ {}
+ [[nodiscard]] bool getVoLTEon() const noexcept
+ {
+ return VoLTEon;
+ }
+};
+
+class CellularChangeVoLTEDataMessage : public CellularVoLTEDataMessage
+{
+ public:
+ explicit CellularChangeVoLTEDataMessage(bool VoLTEon) : CellularVoLTEDataMessage{VoLTEon}
+ {}
+};
+
namespace cellular
{
M module-services/service-cellular/service-cellular/CellularServiceAPI.hpp => module-services/service-cellular/service-cellular/CellularServiceAPI.hpp +2 -0
@@ 111,4 111,6 @@ namespace CellularServiceAPI
bool SetAPN(sys::Service *serv, packet_data::APN::Config apnConfig);
bool SetDataTransfer(sys::Service *serv, packet_data::DataTransfer dt);
bool GetDataTransfer(sys::Service *serv);
+ bool SetVoLTE(sys::Service *serv, bool value);
+
}; // namespace CellularServiceAPI
M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +3 -0
@@ 293,4 293,7 @@ class ServiceCellular : public sys::Service
friend class NetworkSettings;
friend class packet_data::PDPContext;
friend class packet_data::PacketData;
+
+ void volteChanged(const std::string &value);
+ bool volteOn = false;
};
M module-services/service-db/DBServiceAPI.cpp => module-services/service-db/DBServiceAPI.cpp +104 -159
@@ 3,8 3,6 @@
#include "service-db/DBServiceAPI.hpp"
#include "service-db/DBThreadMessage.hpp"
-#include "service-db/DBSMSMessage.hpp"
-#include "service-db/DBSMSTemplateMessage.hpp"
#include "service-db/DBContactMessage.hpp"
#include "service-db/DBNotesMessage.hpp"
#include "service-db/DBCalllogMessage.hpp"
@@ 13,24 11,18 @@
#include "service-db/QueryMessage.hpp"
#include "service-db/DBServiceName.hpp"
-#include <AlarmsRecord.hpp>
#include <CalllogRecord.hpp>
-#include <Common/Query.hpp>
#include <ContactRecord.hpp>
#include <MessageType.hpp>
#include <PhoneNumber.hpp>
-#include <SMSRecord.hpp>
#include <SMSTemplateRecord.hpp>
#include <Service/Bus.hpp>
#include <Service/Common.hpp>
#include <Tables/CountryCodesTable.hpp>
-#include <Tables/Record.hpp>
#include <ThreadRecord.hpp>
#include <Utils.hpp>
-#include <ext/alloc_traits.h>
#include <log/log.hpp>
#include <queries/messages/threads/QueryThreadGetByNumber.hpp>
-#include <queries/phonebook/QueryNumberGetByID.hpp>
#include <utility>
#include <cassert>
@@ 41,9 33,9 @@ namespace sys
} // namespace sys
struct NotesRecord;
-std::unique_ptr<ThreadRecord> DBServiceAPI::ThreadGetByNumber(sys::Service *serv,
- const utils::PhoneNumber::View &phoneNumber,
- std::uint32_t timeout)
+auto DBServiceAPI::ThreadGetByNumber(sys::Service *serv,
+ const utils::PhoneNumber::View &phoneNumber,
+ std::uint32_t timeout) -> std::unique_ptr<ThreadRecord>
{
auto [code, msg] = DBServiceAPI::GetQueryWithReply(
serv, db::Interface::Name::SMSThread, std::make_unique<db::query::ThreadGetByNumber>(phoneNumber), timeout);
@@ 56,73 48,62 @@ std::unique_ptr<ThreadRecord> DBServiceAPI::ThreadGetByNumber(sys::Service *serv
auto threadResult = dynamic_cast<db::query::ThreadGetByNumberResult *>(threadResponse.get());
assert(threadResult != nullptr);
- return std::make_unique<ThreadRecord>(std::move(threadResult->getThread()));
+ return std::make_unique<ThreadRecord>(threadResult->getThread());
}
return nullptr;
}
-bool DBServiceAPI::ThreadGetLimitOffset(sys::Service *serv, uint32_t offset, uint32_t limit)
-{
- std::shared_ptr<DBThreadMessage> msg = std::make_shared<DBThreadMessage>(MessageType::DBThreadGetLimitOffset);
- msg->offset = offset;
- msg->limit = limit;
-
- sys::Bus::SendUnicast(msg, service::name::db, serv);
-
- return true;
-}
-
-uint32_t DBServiceAPI::ThreadGetCount(sys::Service *serv, EntryState state)
+auto DBServiceAPI::ThreadGetCount(sys::Service *serv, EntryState state) -> uint32_t
{
auto msg = std::make_shared<DBThreadGetCountMessage>(state);
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBThreadResponseMessage *threadResponse = reinterpret_cast<DBThreadResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (threadResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto threadResponse = dynamic_cast<DBThreadResponseMessage *>(ret.second.get());
+ assert(threadResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (threadResponse->retCode == 1)) {
return threadResponse->count;
}
- else {
- return false;
- }
+ return 0;
}
-std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactGetByID(sys::Service *serv, uint32_t contactID)
+auto DBServiceAPI::ContactGetByID(sys::Service *serv, uint32_t contactID) -> std::unique_ptr<std::vector<ContactRecord>>
{
ContactRecord rec;
rec.ID = contactID;
std::shared_ptr<DBContactMessage> msg = std::make_shared<DBContactMessage>(MessageType::DBContactGetByID, rec);
- return std::move(ContactGetByIDCommon(serv, std::move(msg)));
+ return ContactGetByIDCommon(serv, std::move(msg));
}
-std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactGetByIDWithTemporary(sys::Service *serv,
- uint32_t contactID)
+auto DBServiceAPI::ContactGetByIDWithTemporary(sys::Service *serv, uint32_t contactID)
+ -> std::unique_ptr<std::vector<ContactRecord>>
{
ContactRecord rec;
rec.ID = contactID;
std::shared_ptr<DBContactMessage> msg = std::make_shared<DBContactMessage>(MessageType::DBContactGetByID, rec);
msg->withTemporary = true;
- return std::move(ContactGetByIDCommon(serv, std::move(msg)));
+ return ContactGetByIDCommon(serv, std::move(msg));
}
-std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactGetByIDCommon(
- sys::Service *serv, std::shared_ptr<DBContactMessage> contactMsg)
+auto DBServiceAPI::ContactGetByIDCommon(sys::Service *serv, std::shared_ptr<DBContactMessage> contactMsg)
+ -> std::unique_ptr<std::vector<ContactRecord>>
{
- auto ret = sys::Bus::SendUnicast(contactMsg, service::name::db, serv, DefaultTimeoutInMs);
- DBContactResponseMessage *contactResponse = reinterpret_cast<DBContactResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(contactMsg, service::name::db, serv, DefaultTimeoutInMs);
+ auto contactResponse = dynamic_cast<DBContactResponseMessage *>(ret.second.get());
+ assert(contactResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode != 0)) {
return std::move(contactResponse->records);
}
- else {
- return std::make_unique<std::vector<ContactRecord>>();
- }
+ return std::make_unique<std::vector<ContactRecord>>();
}
-std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactGetBySpeeddial(sys::Service *serv, UTF8 speeddial)
+auto DBServiceAPI::ContactGetBySpeeddial(sys::Service *serv, UTF8 speeddial)
+ -> std::unique_ptr<std::vector<ContactRecord>>
{
- if (speeddial.length() == 0)
+ if (speeddial.length() == 0) {
return std::make_unique<std::vector<ContactRecord>>();
+ }
ContactRecord rec;
rec.speeddial = speeddial;
@@ 130,17 111,11 @@ std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactGetBySpeeddial(
std::shared_ptr<DBContactMessage> msg =
std::make_shared<DBContactMessage>(MessageType::DBContactGetBySpeedDial, rec);
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBContactResponseMessage *contactResponse = reinterpret_cast<DBContactResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode == true)) {
- return std::move(contactResponse->records);
- }
- else {
- return std::make_unique<std::vector<ContactRecord>>();
- }
+ return ContactGetByIDCommon(serv, msg);
}
-std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactGetByPhoneNumber(sys::Service *serv, UTF8 phoneNumber)
+auto DBServiceAPI::ContactGetByPhoneNumber(sys::Service *serv, UTF8 phoneNumber)
+ -> std::unique_ptr<std::vector<ContactRecord>>
{
ContactRecord rec;
auto number = ContactRecord::Number(utils::PhoneNumber(phoneNumber).getView(), ContactNumberType ::PAGER);
@@ 148,123 123,99 @@ std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactGetByPhoneNumbe
std::shared_ptr<DBContactMessage> msg = std::make_shared<DBContactMessage>(MessageType::DBContactGetByNumber, rec);
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- auto *contactResponse = dynamic_cast<DBContactResponseMessage *>(ret.second.get());
-
- if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode == true)) {
- return std::move(contactResponse->records);
- }
- else {
- return std::make_unique<std::vector<ContactRecord>>();
- }
+ return ContactGetByIDCommon(serv, msg);
}
-std::unique_ptr<ContactRecord> DBServiceAPI::MatchContactByPhoneNumber(sys::Service *serv,
- const utils::PhoneNumber::View &numberView)
+auto DBServiceAPI::MatchContactByPhoneNumber(sys::Service *serv, const utils::PhoneNumber::View &numberView)
+ -> std::unique_ptr<ContactRecord>
{
auto msg = std::make_shared<DBContactNumberMessage>(numberView);
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- auto *contactResponse = dynamic_cast<DBContactNumberResponseMessage *>(ret.second.get());
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto contactResponse = dynamic_cast<DBContactNumberResponseMessage *>(ret.second.get());
assert(contactResponse);
return std::move(contactResponse->contact);
}
-DBServiceAPI::ContactVerificationError DBServiceAPI::verifyContact(sys::Service *serv, const ContactRecord &rec)
+auto DBServiceAPI::verifyContact(sys::Service *serv, const ContactRecord &rec)
+ -> DBServiceAPI::ContactVerificationResult
{
if (rec.primaryName.length() == 0 && rec.alternativeName.length() == 0 && rec.numbers.empty() &&
rec.mail.length() == 0 && rec.address.length() == 0 && rec.note.length() == 0) {
- return emptyContactError;
+ return ContactVerificationResult::emptyContact;
}
auto retSpeedDial = ContactGetBySpeeddial(serv, rec.speeddial);
if (!retSpeedDial->empty() && (*retSpeedDial)[0].ID != rec.ID) {
- return speedDialError;
+ return ContactVerificationResult::speedDialDuplicate;
}
if (rec.numbers.size() > 0 && rec.numbers[0].number.getEntered().size() > 0) {
auto retPhone1 = MatchContactByPhoneNumber(serv, rec.numbers[0].number);
if (retPhone1 && retPhone1->ID != rec.ID) {
- return primaryNumberError;
+ if (retPhone1->isTemporary()) {
+ return ContactVerificationResult::temporaryContactExists;
+ }
+ return ContactVerificationResult::primaryNumberDuplicate;
}
}
if (rec.numbers.size() > 1 && rec.numbers[1].number.getEntered().size() > 0) {
auto retPhone2 = MatchContactByPhoneNumber(serv, rec.numbers[1].number);
if (retPhone2 && retPhone2->ID != rec.ID) {
- return secondaryNumberError;
+ if (retPhone2->isTemporary()) {
+ return ContactVerificationResult::temporaryContactExists;
+ }
+ return ContactVerificationResult::secondaryNumberDuplicate;
}
}
- return noError;
+ return ContactVerificationResult::success;
}
-std::string DBServiceAPI::getVerificationErrorString(const ContactVerificationError err)
-{
- switch (err) {
- case noError:
- return "No error occurred";
- case emptyContactError:
- return "Contact record is empty";
- case speedDialError:
- return "Invalid or duplicate speed dial number";
- case primaryNumberError:
- return "Invalid or duplicate primary number";
- case secondaryNumberError:
- return "Invalid or duplicate secondary number";
- default:
- return "Unknown error";
- }
-}
-
-bool DBServiceAPI::ContactAdd(sys::Service *serv, const ContactRecord &rec)
+auto DBServiceAPI::ContactAdd(sys::Service *serv, const ContactRecord &rec) -> bool
{
std::shared_ptr<DBContactMessage> msg = std::make_shared<DBContactMessage>(MessageType::DBContactAdd, rec);
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBContactResponseMessage *contactResponse = reinterpret_cast<DBContactResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto contactResponse = dynamic_cast<DBContactResponseMessage *>(ret.second.get());
+ assert(contactResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode != 0)) {
return true;
}
- else {
- return false;
- }
+ return false;
}
-bool DBServiceAPI::ContactRemove(sys::Service *serv, uint32_t id)
+auto DBServiceAPI::ContactRemove(sys::Service *serv, uint32_t id) -> bool
{
std::shared_ptr<DBContactMessage> msg = std::make_shared<DBContactMessage>(MessageType::DBContactRemove);
msg->id = id;
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBContactResponseMessage *contactResponse = reinterpret_cast<DBContactResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto contactResponse = dynamic_cast<DBContactResponseMessage *>(ret.second.get());
+ assert(contactResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode != 0)) {
return true;
}
- else {
- return false;
- }
+ return false;
}
-bool DBServiceAPI::ContactUpdate(sys::Service *serv, const ContactRecord &rec)
+auto DBServiceAPI::ContactUpdate(sys::Service *serv, const ContactRecord &rec) -> bool
{
std::shared_ptr<DBContactMessage> msg = std::make_shared<DBContactMessage>(MessageType::DBContactUpdate, rec);
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBContactResponseMessage *contactResponse = reinterpret_cast<DBContactResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto contactResponse = dynamic_cast<DBContactResponseMessage *>(ret.second.get());
+ assert(contactResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode != 0)) {
return true;
}
- else {
- return false;
- }
+ return false;
}
-std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactSearch(sys::Service *serv,
- UTF8 primaryName,
- UTF8 alternativeName,
- UTF8 number)
+auto DBServiceAPI::ContactSearch(sys::Service *serv, UTF8 primaryName, UTF8 alternativeName, UTF8 number)
+ -> std::unique_ptr<std::vector<ContactRecord>>
{
std::shared_ptr<DBContactSearchMessage> msg =
std::make_shared<DBContactSearchMessage>(MessageType::DBContactSearch,
@@ 272,25 223,25 @@ std::unique_ptr<std::vector<ContactRecord>> DBServiceAPI::ContactSearch(sys::Ser
(alternativeName.length() > 0) ? alternativeName.c_str() : "",
(number.length() > 0) ? number.c_str() : "");
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBContactResponseMessage *contactResponse = reinterpret_cast<DBContactResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto contactResponse = dynamic_cast<DBContactResponseMessage *>(ret.second.get());
+ assert(contactResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (contactResponse->retCode != 0)) {
return std::move(contactResponse->records);
}
- else {
- return std::make_unique<std::vector<ContactRecord>>();
- }
+ return std::make_unique<std::vector<ContactRecord>>();
}
-CalllogRecord DBServiceAPI::CalllogAdd(sys::Service *serv, const CalllogRecord &rec)
+auto DBServiceAPI::CalllogAdd(sys::Service *serv, const CalllogRecord &rec) -> CalllogRecord
{
std::shared_ptr<DBCalllogMessage> msg = std::make_shared<DBCalllogMessage>(MessageType::DBCalllogAdd, rec);
LOG_DEBUG("CalllogAdd %s", utils::to_string(rec).c_str());
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBCalllogResponseMessage *calllogResponse = reinterpret_cast<DBCalllogResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto calllogResponse = dynamic_cast<DBCalllogResponseMessage *>(ret.second.get());
+ assert(calllogResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode != 0)) {
auto records = *calllogResponse->records;
if (!records.empty()) {
return records[0];
@@ 300,52 251,49 @@ CalllogRecord DBServiceAPI::CalllogAdd(sys::Service *serv, const CalllogRecord &
return CalllogRecord();
}
-bool DBServiceAPI::CalllogRemove(sys::Service *serv, uint32_t id)
+auto DBServiceAPI::CalllogRemove(sys::Service *serv, uint32_t id) -> bool
{
std::shared_ptr<DBCalllogMessage> msg = std::make_shared<DBCalllogMessage>(MessageType::DBCalllogRemove);
msg->id = id;
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBCalllogResponseMessage *calllogResponse = reinterpret_cast<DBCalllogResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto calllogResponse = dynamic_cast<DBCalllogResponseMessage *>(ret.second.get());
+ assert(calllogResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode != 0)) {
return true;
}
- else {
- return false;
- }
+ return false;
}
-bool DBServiceAPI::CalllogUpdate(sys::Service *serv, const CalllogRecord &rec)
+auto DBServiceAPI::CalllogUpdate(sys::Service *serv, const CalllogRecord &rec) -> bool
{
std::shared_ptr<DBCalllogMessage> msg = std::make_shared<DBCalllogMessage>(MessageType::DBCalllogUpdate, rec);
LOG_DEBUG("CalllogUpdate %s", utils::to_string(rec).c_str());
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBCalllogResponseMessage *calllogResponse = reinterpret_cast<DBCalllogResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto calllogResponse = dynamic_cast<DBCalllogResponseMessage *>(ret.second.get());
+ assert(calllogResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode != 0)) {
return true;
}
- else {
- return false;
- }
+ return false;
}
-uint32_t DBServiceAPI::CalllogGetCount(sys::Service *serv, EntryState state)
+auto DBServiceAPI::CalllogGetCount(sys::Service *serv, EntryState state) -> uint32_t
{
std::shared_ptr<DBCalllogMessage> msg = std::make_shared<DBCalllogGetCount>(state);
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBCalllogResponseMessage *calllogResponse = reinterpret_cast<DBCalllogResponseMessage *>(ret.second.get());
- if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode == true)) {
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto calllogResponse = dynamic_cast<DBCalllogResponseMessage *>(ret.second.get());
+ assert(calllogResponse != nullptr);
+ if ((ret.first == sys::ReturnCodes::Success) && (calllogResponse->retCode != 0)) {
return calllogResponse->count;
}
- else {
- return 0;
- }
+ return 0;
}
-bool DBServiceAPI::CalllogGetLimitOffset(sys::Service *serv, uint32_t offset, uint32_t limit)
+auto DBServiceAPI::CalllogGetLimitOffset(sys::Service *serv, uint32_t offset, uint32_t limit) -> bool
{
std::shared_ptr<DBCalllogMessage> msg = std::make_shared<DBCalllogMessage>(MessageType::DBCalllogGetLimitOffset);
msg->offset = offset;
@@ 355,22 303,21 @@ bool DBServiceAPI::CalllogGetLimitOffset(sys::Service *serv, uint32_t offset, ui
return true;
}
-uint32_t DBServiceAPI::GetCountryCodeByMCC(sys::Service *serv, uint32_t mcc)
+auto DBServiceAPI::GetCountryCodeByMCC(sys::Service *serv, uint32_t mcc) -> uint32_t
{
std::shared_ptr<DBCountryCodeMessage> msg =
std::make_shared<DBCountryCodeMessage>(MessageType::DBCountryCode, mcc, 0);
- auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
- DBCountryCodeResponseMessage *response = reinterpret_cast<DBCountryCodeResponseMessage *>(ret.second.get());
+ auto ret = sys::Bus::SendUnicast(msg, service::name::db, serv, DefaultTimeoutInMs);
+ auto response = dynamic_cast<DBCountryCodeResponseMessage *>(ret.second.get());
+ assert(response != nullptr);
if (ret.first == sys::ReturnCodes::Success) {
return (response->row.country_code);
}
- else {
- return (0);
- }
+ return 0;
}
-bool DBServiceAPI::DBBackup(sys::Service *serv, std::string backupPath)
+auto DBServiceAPI::DBBackup(sys::Service *serv, std::string backupPath) -> bool
{
LOG_INFO("DBBackup %s", backupPath.c_str());
@@ 381,8 328,6 @@ bool DBServiceAPI::DBBackup(sys::Service *serv, std::string backupPath)
if (ret.first == sys::ReturnCodes::Success) {
return true;
}
- else {
- LOG_ERROR("DBBackup error, return code: %s", c_str(ret.first));
- return false;
- }
+ LOG_ERROR("DBBackup error, return code: %s", c_str(ret.first));
+ return false;
}
M module-services/service-db/DBServiceAPI_GetByQuery.cpp => module-services/service-db/DBServiceAPI_GetByQuery.cpp +5 -2
@@ 22,10 22,13 @@ namespace sys
class Service;
} // namespace sys
-bool DBServiceAPI::GetQuery(sys::Service *serv, db::Interface::Name database, std::unique_ptr<db::Query> query)
+std::pair<bool, std::uint64_t> DBServiceAPI::GetQuery(sys::Service *serv,
+ db::Interface::Name database,
+ std::unique_ptr<db::Query> query)
{
auto msg = std::make_shared<db::QueryMessage>(database, std::move(query));
- return sys::Bus::SendUnicast(msg, service::name::db, serv);
+ const auto isSuccess = sys::Bus::SendUnicast(msg, service::name::db, serv);
+ return std::make_pair(isSuccess, msg->uniID);
}
sys::SendResult DBServiceAPI::GetQueryWithReply(sys::Service *serv,
M module-services/service-db/ServiceDB.cpp => module-services/service-db/ServiceDB.cpp +0 -9
@@ 209,15 209,6 @@ sys::MessagePointer ServiceDB::DataReceivedHandler(sys::DataMessage *msgl, sys::
sendUpdateNotification(db::Interface::Name::SMSThread, db::Query::Type::Delete);
} break;
- case MessageType::DBThreadGetLimitOffset: {
- auto time = utils::time::Scoped("DBThreadGetLimitOffset");
- DBThreadMessage *msg = reinterpret_cast<DBThreadMessage *>(msgl);
- auto ret = threadRecordInterface->GetLimitOffset(msg->offset, msg->limit);
- LOG_INFO("Thread get limit offset");
- responseMsg =
- std::make_shared<DBThreadResponseMessage>(std::move(ret), true, msg->limit, msg->offset, ret->size());
- } break;
-
case MessageType::DBThreadGetCount: {
auto *msg = static_cast<DBThreadGetCountMessage *>(msgl);
auto time = utils::time::Scoped("DBThreadGetCountMessage");
M module-services/service-db/agents/settings/SystemSettings.hpp => module-services/service-db/agents/settings/SystemSettings.hpp +6 -0
@@ 21,6 21,12 @@ namespace settings
constexpr inline auto deviceVisibility = "bt_device_visibility";
constexpr inline auto deviceName = "bt_device_name";
constexpr inline auto bondedDevices = "bt_bonded_devices";
+ constexpr inline auto btKeys = "bt_keys";
} // namespace Bluetooth
+ namespace Cellular
+ {
+ constexpr inline auto volte_on = "cl_volte_on";
+ } // namespace Cellular
+
}; // namespace settings
M module-services/service-db/service-db/DBServiceAPI.hpp => module-services/service-db/service-db/DBServiceAPI.hpp +49 -43
@@ 6,11 6,9 @@
#include <BaseInterface.hpp>
#include <Common/Common.hpp>
#include <Common/Query.hpp>
-#include <Interface/AlarmsRecord.hpp>
#include <Interface/CalllogRecord.hpp>
#include <Interface/ContactRecord.hpp>
#include <Interface/NotesRecord.hpp>
-#include <Interface/SMSRecord.hpp>
#include <Interface/SMSTemplateRecord.hpp>
#include <Interface/ThreadRecord.hpp>
#include <PhoneNumber.hpp>
@@ 41,26 39,35 @@ class DBServiceAPI
public:
static constexpr unsigned int DefaultTimeoutInMs = 5000U;
- enum ContactVerificationError
+ enum class ContactVerificationResult
{
- emptyContactError,
- speedDialError,
- primaryNumberError,
- secondaryNumberError,
- noError
+ emptyContact,
+ temporaryContactExists,
+ primaryNumberDuplicate,
+ secondaryNumberDuplicate,
+ speedDialDuplicate,
+ success
};
- static std::unique_ptr<ThreadRecord> ThreadGetByNumber(sys::Service *serv,
- const utils::PhoneNumber::View &phoneNumber,
- std::uint32_t timeout = DefaultTimeoutInMs);
- static bool ThreadGetLimitOffset(sys::Service *serv, uint32_t offset, uint32_t limit);
- static uint32_t ThreadGetCount(sys::Service *serv, EntryState state = EntryState::ALL);
+ static auto ThreadGetByNumber(sys::Service *serv,
+ const utils::PhoneNumber::View &phoneNumber,
+ std::uint32_t timeout = DefaultTimeoutInMs) -> std::unique_ptr<ThreadRecord>;
+ static auto ThreadGetCount(sys::Service *serv, EntryState state = EntryState::ALL) -> uint32_t;
- static auto GetQuery(sys::Service *serv, db::Interface::Name database, std::unique_ptr<db::Query> query) -> bool;
- static sys::SendResult GetQueryWithReply(sys::Service *serv,
- db::Interface::Name database,
- std::unique_ptr<db::Query> query,
- std::uint32_t timeout);
+ /**
+ * Queries the database.
+ * @param serv Sender service.
+ * @param database Target database name.
+ * @param query Query.
+ * @return A pair of: a flag that indicates whether query send was successful, and a message identifier that common
+ * for the query and its response.
+ */
+ static auto GetQuery(sys::Service *serv, db::Interface::Name database, std::unique_ptr<db::Query> query)
+ -> std::pair<bool, std::uint64_t>;
+ static auto GetQueryWithReply(sys::Service *serv,
+ db::Interface::Name database,
+ std::unique_ptr<db::Query> query,
+ std::uint32_t timeout) -> sys::SendResult;
/**
* @brief Function is checking if new contact can be added to database. Function is blocking.
@@ 75,19 82,20 @@ class DBServiceAPI
*
* @note This function is blocking. It's checking until first error.
*/
- static ContactVerificationError verifyContact(sys::Service *serv, const ContactRecord &rec);
- static std::string getVerificationErrorString(const ContactVerificationError err);
- static std::unique_ptr<std::vector<ContactRecord>> ContactGetByID(sys::Service *serv, uint32_t contactID);
- static std::unique_ptr<std::vector<ContactRecord>> ContactGetByIDWithTemporary(sys::Service *serv,
- uint32_t contactID);
+ static auto verifyContact(sys::Service *serv, const ContactRecord &rec) -> ContactVerificationResult;
+ static auto ContactGetByID(sys::Service *serv, uint32_t contactID) -> std::unique_ptr<std::vector<ContactRecord>>;
+ static auto ContactGetByIDWithTemporary(sys::Service *serv, uint32_t contactID)
+ -> std::unique_ptr<std::vector<ContactRecord>>;
private:
- static std::unique_ptr<std::vector<ContactRecord>> ContactGetByIDCommon(
- sys::Service *serv, std::shared_ptr<DBContactMessage> contactMsg);
+ static auto ContactGetByIDCommon(sys::Service *serv, std::shared_ptr<DBContactMessage> contactMsg)
+ -> std::unique_ptr<std::vector<ContactRecord>>;
public:
- static std::unique_ptr<std::vector<ContactRecord>> ContactGetBySpeeddial(sys::Service *serv, UTF8 speeddial);
- static std::unique_ptr<std::vector<ContactRecord>> ContactGetByPhoneNumber(sys::Service *serv, UTF8 phoneNumber);
+ static auto ContactGetBySpeeddial(sys::Service *serv, UTF8 speeddial)
+ -> std::unique_ptr<std::vector<ContactRecord>>;
+ static auto ContactGetByPhoneNumber(sys::Service *serv, UTF8 phoneNumber)
+ -> std::unique_ptr<std::vector<ContactRecord>>;
/**
* @brief Matches single Contact by a provided phone number
@@ 96,24 104,22 @@ class DBServiceAPI
* @param numberView - number to match contact with
* @return std::unique_ptr<ContactRecord>
*/
- static std::unique_ptr<ContactRecord> MatchContactByPhoneNumber(sys::Service *serv,
- const utils::PhoneNumber::View &numberView);
- static bool ContactAdd(sys::Service *serv, const ContactRecord &rec);
- static bool ContactRemove(sys::Service *serv, uint32_t id);
- static bool ContactUpdate(sys::Service *serv, const ContactRecord &rec);
- static std::unique_ptr<std::vector<ContactRecord>> ContactSearch(sys::Service *serv,
- UTF8 primaryName,
- UTF8 alternativeName,
- UTF8 number);
+ static auto MatchContactByPhoneNumber(sys::Service *serv, const utils::PhoneNumber::View &numberView)
+ -> std::unique_ptr<ContactRecord>;
+ static auto ContactAdd(sys::Service *serv, const ContactRecord &rec) -> bool;
+ static auto ContactRemove(sys::Service *serv, uint32_t id) -> bool;
+ static auto ContactUpdate(sys::Service *serv, const ContactRecord &rec) -> bool;
+ static auto ContactSearch(sys::Service *serv, UTF8 primaryName, UTF8 alternativeName, UTF8 number)
+ -> std::unique_ptr<std::vector<ContactRecord>>;
- static CalllogRecord CalllogAdd(sys::Service *serv, const CalllogRecord &rec);
- static bool CalllogRemove(sys::Service *serv, uint32_t id);
- static bool CalllogUpdate(sys::Service *serv, const CalllogRecord &rec);
- static uint32_t CalllogGetCount(sys::Service *serv, EntryState state = EntryState::ALL);
- static bool CalllogGetLimitOffset(sys::Service *serv, uint32_t offset, uint32_t limit);
+ static auto CalllogAdd(sys::Service *serv, const CalllogRecord &rec) -> CalllogRecord;
+ static auto CalllogRemove(sys::Service *serv, uint32_t id) -> bool;
+ static auto CalllogUpdate(sys::Service *serv, const CalllogRecord &rec) -> bool;
+ static auto CalllogGetCount(sys::Service *serv, EntryState state = EntryState::ALL) -> uint32_t;
+ static auto CalllogGetLimitOffset(sys::Service *serv, uint32_t offset, uint32_t limit) -> bool;
/* country codes */
- static uint32_t GetCountryCodeByMCC(sys::Service *serv, uint32_t mcc);
+ static auto GetCountryCodeByMCC(sys::Service *serv, uint32_t mcc) -> uint32_t;
- static bool DBBackup(sys::Service *serv, std::string backupPath);
+ static auto DBBackup(sys::Service *serv, std::string backupPath) -> bool;
};
M module-services/service-db/test/test-service-db-api.cpp => module-services/service-db/test/test-service-db-api.cpp +10 -12
@@ 6,55 6,53 @@
#include <service-db/DBServiceAPI.hpp> // for DBServiceAPI, DBServiceAPI::noError, DBServiceAPI::ContactVerificationError, DBServiceAPI::emptyContactError
#include <memory> // for allocator
-#include "utf8/UTF8.hpp" // for UTF8
-
TEST_CASE("DB_API")
{
SECTION("DBServiceAPI::verifyContact emptyContactError")
{
ContactRecord testRecord;
- DBServiceAPI::ContactVerificationError err = DBServiceAPI::verifyContact(nullptr, testRecord);
- REQUIRE(err == DBServiceAPI::emptyContactError);
+ DBServiceAPI::ContactVerificationResult err = DBServiceAPI::verifyContact(nullptr, testRecord);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::emptyContact);
testRecord.primaryName = "testName";
err = DBServiceAPI::verifyContact(nullptr, testRecord);
testRecord.primaryName.clear();
- REQUIRE(err == DBServiceAPI::noError);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::success);
testRecord.alternativeName = "testName";
err = DBServiceAPI::verifyContact(nullptr, testRecord);
testRecord.alternativeName.clear();
- REQUIRE(err == DBServiceAPI::noError);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::success);
// Here should be tested contact with single number, but it would require involving sys::bus.
testRecord.mail = "test@mail.com";
err = DBServiceAPI::verifyContact(nullptr, testRecord);
testRecord.mail.clear();
- REQUIRE(err == DBServiceAPI::noError);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::success);
testRecord.mail = "test@mail.com";
err = DBServiceAPI::verifyContact(nullptr, testRecord);
testRecord.mail.clear();
- REQUIRE(err == DBServiceAPI::noError);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::success);
testRecord.speeddial = "1";
err = DBServiceAPI::verifyContact(nullptr, testRecord);
testRecord.speeddial.clear();
- REQUIRE(err == DBServiceAPI::emptyContactError);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::emptyContact);
testRecord.address = "test address";
err = DBServiceAPI::verifyContact(nullptr, testRecord);
testRecord.address.clear();
- REQUIRE(err == DBServiceAPI::noError);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::success);
testRecord.note = "test note";
err = DBServiceAPI::verifyContact(nullptr, testRecord);
testRecord.note.clear();
- REQUIRE(err == DBServiceAPI::noError);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::success);
err = DBServiceAPI::verifyContact(nullptr, testRecord);
- REQUIRE(err == DBServiceAPI::emptyContactError);
+ REQUIRE(err == DBServiceAPI::ContactVerificationResult::emptyContact);
}
}
M module-services/service-desktop/endpoints/backup/BackupRestore.cpp => module-services/service-desktop/endpoints/backup/BackupRestore.cpp +3 -2
@@ 9,6 9,7 @@
#include <purefs/filesystem_paths.hpp>
#include <service-db/DBServiceAPI.hpp>
#include <service-db/DBServiceName.hpp>
+#include <module-utils/Utils.hpp>
#include <cassert>
#include <filesystem>
@@ 116,7 117,7 @@ bool BackupRestore::PackUserFiles()
std::string backupPathDB = PATH_BACKUP;
backupPathDB += "/";
- const auto backupOSPath = purefs::dir::getBackupOSPath();
+ const auto backupOSPath = purefs::dir::getBackupOSPath();
if (std::filesystem::is_empty(backupOSPath.c_str())) {
LOG_ERROR("PackUserFiles: backup dir %s is empty, nothing to backup, quitting...", backupOSPath.c_str());
@@ 330,7 331,7 @@ bool BackupRestore::UnpackBackupFile()
bool BackupRestore::ReplaceUserFiles()
{
/* replace existing files that have respective backup files existing */
- const auto backupOSPath = purefs::dir::getBackupOSPath();
+ const auto backupOSPath = purefs::dir::getBackupOSPath();
if (std::filesystem::is_directory(backupOSPath) && std::filesystem::is_empty(backupOSPath)) {
LOG_INFO("ReplaceUserFiles: dir emtpy, nothing to restore, quitting...");
return false;
M module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.cpp => module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.cpp +195 -55
@@ 34,29 34,74 @@ namespace parserFSM
const inline auto two_days_before = Duration(0, 2, 0, 0);
const inline auto one_week_before = Duration(1, 0, 0, 0);
const inline auto event_time = Duration(0, 0, 0, 0);
+ const inline auto never = Duration(0, 0, 0, 0xFFFF);
} // namespace duration
} // namespace ical
- namespace json::calendar::events
+ namespace json::calendar
{
- constexpr inline auto UID = "UID";
- constexpr inline auto data = "data";
+ constexpr inline auto events = "calendar_events";
+ namespace event
+ {
+ constexpr inline auto vevent = "VEVENT";
+
+ constexpr inline auto uid = "UID";
+ constexpr inline auto summary = "SUMMARY";
+ constexpr inline auto dtstart = "DTSTART";
+ constexpr inline auto dtend = "DTEND";
+
+ namespace recurrence_rule
+ {
+ constexpr inline auto rrule = "RRULE";
+
+ constexpr inline auto frequency = "FREQ";
+ constexpr inline auto count = "COUNT";
+ constexpr inline auto interval = "INTERVAL";
+ } // namespace recurrence_rule
+
+ namespace alarm
+ {
+ constexpr inline auto valarm = "VALARM";
+
+ constexpr inline auto trigger = "TRIGGER";
+ constexpr inline auto action = "ACTION";
+ } // namespace alarm
+
+ namespace provider
+ {
+ constexpr inline auto provider = "provider";
+
+ constexpr inline auto type = "type";
+ constexpr inline auto id = "id";
+ constexpr inline auto iCalUid = "iCalUid";
+ } // namespace provider
+ } // namespace event
constexpr inline auto offset = "offset";
constexpr inline auto limit = "limit";
constexpr inline auto count = "count";
constexpr inline auto total_count = "total_count";
- constexpr inline auto providers = "providers";
- namespace provider
- {
- constexpr inline auto type = "provider_type";
- constexpr inline auto id = "provider_id";
- constexpr inline auto iCalUid = "provider_iCalUid";
- } // namespace provider
- } // namespace json::calendar::events
+ } // namespace json::calendar
} // namespace parserFSM
using namespace parserFSM;
+auto CalendarEventsHelper::isICalEventValid(ICalEvent icalEvent) const -> bool
+{
+ if (!icalEvent.event.isValid) {
+ LOG_ERROR("Ical event invalid!");
+ return false;
+ }
+ if (!icalEvent.alarm.isValid) {
+ LOG_ERROR("Ical alarm invalid!");
+ return false;
+ }
+ if (!icalEvent.rrule.isValid) {
+ LOG_ERROR("Ical recurrence rule invalid!");
+ return false;
+ }
+ return true;
+}
+
auto CalendarEventsHelper::frequencyFromCustomRepeat(Repeat repeat) const -> Frequency
{
return Frequency(static_cast<uint32_t>(repeat));
@@ 101,43 146,44 @@ auto CalendarEventsHelper::alarmFrom(Reminder reminder) const -> Alarm
{
switch (reminder) {
case Reminder::never: {
- return Alarm();
+ auto beforeEvent = ical::duration::never;
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::five_min_before: {
auto beforeEvent = ical::duration::five_minutes_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::fifteen_min_before: {
auto beforeEvent = ical::duration::fifteen_minutes_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::thirty_min_before: {
auto beforeEvent = ical::duration::thirty_minutes_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::one_hour_before: {
auto beforeEvent = ical::duration::one_hour_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::two_hour_before: {
auto beforeEvent = ical::duration::two_hours_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::one_day_before: {
auto beforeEvent = ical::duration::one_day_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::two_days_before: {
auto beforeEvent = ical::duration::two_days_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::one_week_before: {
auto beforeEvent = ical::duration::one_week_before;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
case Reminder::event_time: {
auto beforeEvent = ical::duration::event_time;
- return Alarm(beforeEvent, Action::display);
+ return Alarm(beforeEvent, Action::none);
}
}
return Alarm();
@@ 152,11 198,43 @@ auto CalendarEventsHelper::icalEventFrom(const EventsRecord &record) const -> IC
return ICalEvent{event, alarm, rrule};
}
+auto CalendarEventsHelper::eventJsonObjectFrom(EventsRecord record) const -> json11::Json
+{
+ auto icalEvent = icalEventFrom(record);
+ if (!isICalEventValid(icalEvent)) {
+ LOG_ERROR("Bad event record formatting (Event UID: %s)", icalEvent.event.getUID().c_str());
+ }
+
+ auto rruleObj = json11::Json::object{
+ {json::calendar::event::recurrence_rule::frequency, icalEvent.rrule.getFrequencyString().c_str()},
+ {json::calendar::event::recurrence_rule::count, icalEvent.rrule.getCountString().c_str()},
+ {json::calendar::event::recurrence_rule::interval, icalEvent.rrule.getIntervalString().c_str()}};
+
+ auto alarmObj =
+ json11::Json::object{{json::calendar::event::alarm::trigger, icalEvent.alarm.getTriggerString().c_str()},
+ {json::calendar::event::alarm::action, icalEvent.alarm.getActionString().c_str()}};
+
+ auto providerObj =
+ json11::Json::object{{json::calendar::event::provider::type, record.provider_type.c_str()},
+ {json::calendar::event::provider::id, record.provider_id.c_str()},
+ {json::calendar::event::provider::iCalUid, record.provider_iCalUid.c_str()}};
+
+ auto eventObj = json11::Json::object{{json::calendar::event::dtend, icalEvent.event.getDTEndString().c_str()},
+ {json::calendar::event::dtstart, icalEvent.event.getDTStartString().c_str()},
+ {json::calendar::event::summary, icalEvent.event.getSummary().c_str()},
+ {json::calendar::event::uid, icalEvent.event.getUID().c_str()},
+ {json::calendar::event::recurrence_rule::rrule, rruleObj},
+ {json::calendar::event::alarm::valarm, alarmObj},
+ {json::calendar::event::provider::provider, providerObj}};
+
+ return eventObj;
+}
+
auto CalendarEventsHelper::requestDataFromDB(Context &context) -> sys::ReturnCodes
{
- auto obj = context.getBody();
- uint32_t offset = obj[json::calendar::events::offset].int_value();
- uint32_t limit = obj[json::calendar::events::limit].int_value();
+ const auto obj = context.getBody();
+ uint32_t offset = obj[json::calendar::offset].int_value();
+ uint32_t limit = obj[json::calendar::limit].int_value();
auto query = std::make_unique<db::query::events::GetAllLimited>(offset, limit);
auto listener = std::make_unique<db::EndpointListener>(
@@ 166,17 244,22 @@ auto CalendarEventsHelper::requestDataFromDB(Context &context) -> sys::ReturnCod
uint32_t totalCount = EventsResult->getCountResult();
auto parser = std::make_unique<ParserICS>();
std::vector<ICalEvent> icalEvents;
+
+ auto eventsArray = json11::Json::array();
+
for (auto rec : records) {
- icalEvents.push_back(icalEventFrom(rec));
+ auto eventObject = eventJsonObjectFrom(rec);
+
+ eventsArray.emplace_back(eventObject);
}
- parser->importEvents(icalEvents);
- auto jsonObj =
- json11::Json::object({{json::calendar::events::data, parser->getIcsData()},
- {json::calendar::events::count, std::to_string(records.size())},
- {json::calendar::events::total_count, std::to_string(totalCount)}});
+
+ auto jsonObj = json11::Json::object({{json::calendar::events, eventsArray},
+ {json::calendar::count, std::to_string(records.size())},
+ {json::calendar::total_count, std::to_string(totalCount)}});
context.setResponseBody(jsonObj);
MessageHandler::putToSendQueue(context.createSimpleResponse());
+
return true;
}
return false;
@@ 184,7 267,7 @@ auto CalendarEventsHelper::requestDataFromDB(Context &context) -> sys::ReturnCod
context);
query->setQueryListener(std::move(listener));
- auto ret = DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
+ auto [ret, _] = DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
if (ret) {
return sys::ReturnCodes::Success;
@@ 224,8 307,7 @@ auto CalendarEventsHelper::repeatFrom(RecurrenceRule &rrule) const -> Repeat
case Frequency::yearly: {
return Repeat::yearly;
}
- case Frequency::invalid: {
- LOG_ERROR("Frequency invalid");
+ case Frequency::never: {
return Repeat::never;
}
}
@@ 249,26 331,67 @@ auto CalendarEventsHelper::eventsRecordFrom(ICalEvent &icalEvent) const -> Event
return record;
}
+auto CalendarEventsHelper::ICalEventFromJson(json11::Json eventObj) const -> ICalEvent
+{
+ ICalEvent icalEvent;
+ icalEvent.event.setUID(eventObj[json::calendar::event::uid].string_value());
+ icalEvent.event.setSummary(eventObj[json::calendar::event::summary].string_value());
+ icalEvent.event.setDTStart(eventObj[json::calendar::event::dtstart].string_value());
+ icalEvent.event.setDTEnd(eventObj[json::calendar::event::dtend].string_value());
+
+ icalEvent.rrule.setFrequency(
+ eventObj[json::calendar::event::recurrence_rule::rrule][json::calendar::event::recurrence_rule::frequency]
+ .string_value());
+ icalEvent.rrule.setCount(
+ eventObj[json::calendar::event::recurrence_rule::rrule][json::calendar::event::recurrence_rule::count]
+ .string_value());
+ icalEvent.rrule.setInterval(
+ eventObj[json::calendar::event::recurrence_rule::rrule][json::calendar::event::recurrence_rule::interval]
+ .string_value());
+
+ icalEvent.alarm.setTrigger(
+ eventObj[json::calendar::event::alarm::valarm][json::calendar::event::alarm::trigger].string_value());
+ icalEvent.alarm.setAction(
+ eventObj[json::calendar::event::alarm::valarm][json::calendar::event::alarm::action].string_value());
+
+ auto record = eventsRecordFrom(icalEvent);
+
+ return icalEvent;
+}
+
auto CalendarEventsHelper::createDBEntry(Context &context) -> sys::ReturnCodes
{
- auto parser = std::make_shared<ParserICS>();
- parser->loadData(context.getBody()[json::calendar::events::data].string_value());
- auto icalEvents = parser->exportEvents();
+ auto eventsJsonObj = context.getBody();
+ auto eventsJsonArray = eventsJsonObj[json::calendar::events].array_items();
+ bool ret = true;
+ for (auto event : eventsJsonArray) {
- bool ret = true;
- for (auto event : icalEvents) {
- auto UID = event.event.getUID();
- auto record = eventsRecordFrom(event);
- auto query = std::make_unique<db::query::events::Add>(record);
+ auto icalEvent = ICalEventFromJson(event);
+ if (!isICalEventValid(icalEvent)) {
+ context.setResponseStatus(http::Code::BadRequest);
+ MessageHandler::putToSendQueue(context.createSimpleResponse());
+ return sys::ReturnCodes::Failure;
+ }
+
+ auto record = eventsRecordFrom(icalEvent);
+ if (record.UID.empty()) {
+ record.UID = createUID();
+ auto jsonObj = json11::Json::object({{json::calendar::event::uid, record.UID}});
+ context.setResponseBody(jsonObj);
+ }
+ else {
+ LOG_ERROR("UID should not be recieved in put event endpoint. Recieved UID: %s", record.UID.c_str());
+ context.setResponseStatus(http::Code::BadRequest);
+ MessageHandler::putToSendQueue(context.createSimpleResponse());
+ return sys::ReturnCodes::Failure;
+ }
+
+ auto query = std::make_unique<db::query::events::Add>(record);
auto listener = std::make_unique<db::EndpointListener>(
- [=](db::QueryResult *result, Context context) {
+ [&](db::QueryResult *result, Context context) {
if (auto EventResult = dynamic_cast<db::query::events::AddResult *>(result)) {
- auto jsonObj = json11::Json::object(
- {{json::calendar::events::data, parser->getIcsData()}, {json::calendar::events::UID, UID}});
-
- context.setResponseBody(jsonObj);
context.setResponseStatus(EventResult->getResult() ? http::Code::OK
: http::Code::InternalServerError);
@@ 280,7 403,9 @@ auto CalendarEventsHelper::createDBEntry(Context &context) -> sys::ReturnCodes
context);
query->setQueryListener(std::move(listener));
- ret = ret && DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
+ const auto [succeed, _] =
+ DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
+ ret = ret && succeed;
}
if (ret) {
@@ 293,14 418,19 @@ auto CalendarEventsHelper::createDBEntry(Context &context) -> sys::ReturnCodes
auto CalendarEventsHelper::updateDBEntry(Context &context) -> sys::ReturnCodes
{
- auto parser = std::make_unique<ParserICS>();
- parser->loadData(context.getBody()[json::calendar::events::data].string_value());
- auto icalEvents = parser->exportEvents();
+ auto eventsJsonObj = context.getBody();
bool ret = true;
- for (auto event : icalEvents) {
+ for (auto event : eventsJsonObj[json::calendar::events].array_items()) {
- auto record = eventsRecordFrom(event);
+ auto icalEvent = ICalEventFromJson(event);
+ if (!isICalEventValid(icalEvent) || icalEvent.event.getUID().empty()) {
+ context.setResponseStatus(http::Code::BadRequest);
+ MessageHandler::putToSendQueue(context.createSimpleResponse());
+ return sys::ReturnCodes::Failure;
+ }
+
+ auto record = eventsRecordFrom(icalEvent);
auto query = std::make_unique<db::query::events::EditICS>(record);
auto listener = std::make_unique<db::EndpointListener>(
[](db::QueryResult *result, Context context) {
@@ 315,7 445,9 @@ auto CalendarEventsHelper::updateDBEntry(Context &context) -> sys::ReturnCodes
context);
query->setQueryListener(std::move(listener));
- ret = ret && DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
+ const auto [succeed, _] =
+ DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
+ ret = ret && succeed;
}
if (ret) {
return sys::ReturnCodes::Success;
@@ 327,7 459,15 @@ auto CalendarEventsHelper::updateDBEntry(Context &context) -> sys::ReturnCodes
auto CalendarEventsHelper::deleteDBEntry(Context &context) -> sys::ReturnCodes
{
- auto UID = context.getBody()[json::calendar::events::UID].string_value();
+ auto UID = context.getBody()[json::calendar::event::uid].string_value();
+ auto checkUID = Event();
+ checkUID.setUID(UID);
+ if (!checkUID.isValid) {
+ LOG_ERROR("Wrong UID format. Provided UID: %s", UID.c_str());
+ context.setResponseStatus(http::Code::BadRequest);
+ MessageHandler::putToSendQueue(context.createSimpleResponse());
+ return sys::ReturnCodes::Failure;
+ }
auto query = std::make_unique<db::query::events::RemoveICS>(UID);
auto listener = std::make_unique<db::EndpointListener>(
[=](db::QueryResult *result, Context context) {
@@ 341,7 481,7 @@ auto CalendarEventsHelper::deleteDBEntry(Context &context) -> sys::ReturnCodes
context);
query->setQueryListener(std::move(listener));
- auto ret = DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
+ auto [ret, _] = DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Events, std::move(query));
if (ret) {
return sys::ReturnCodes::Success;
M module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.hpp => module-services/service-desktop/endpoints/calendarEvents/CalendarEventsHelper.hpp +4 -0
@@ 29,6 29,10 @@ namespace parserFSM
[[nodiscard]] auto repeatFrom(RecurrenceRule &rrule) const -> Repeat;
[[nodiscard]] auto eventsRecordFrom(ICalEvent &icalEvent) const -> EventsRecord;
+ [[nodiscard]] auto eventJsonObjectFrom(EventsRecord record) const -> json11::Json;
+ [[nodiscard]] auto ICalEventFromJson(json11::Json eventObj) const -> ICalEvent;
+ [[nodiscard]] auto isICalEventValid(ICalEvent event) const -> bool;
+
public:
CalendarEventsHelper(sys::Service *_ownerServicePtr) : DBHelper(_ownerServicePtr)
{}
M module-services/service-desktop/endpoints/calllog/CalllogHelper.cpp => module-services/service-desktop/endpoints/calllog/CalllogHelper.cpp +0 -1
@@ 143,7 143,6 @@ auto CalllogHelper::deleteDBEntry(Context &context) -> sys::ReturnCodes
}
},
context);
-
query->setQueryListener(std::move(listener));
DBServiceAPI::GetQuery(ownerServicePtr, db::Interface::Name::Calllog, std::move(query));
return sys::ReturnCodes::Success;
M module-services/service-gui/SynchronizationMechanism.cpp => module-services/service-gui/SynchronizationMechanism.cpp +1 -0
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "SynchronizationMechanism.hpp"
+#include <stdexcept>
namespace service::gui
{
A module-services/service-gui/doc/README.md => module-services/service-gui/doc/README.md +46 -0
@@ 0,0 1,46 @@
+# GUI service
+
+The GUI service is responsible for rendering frames and sharing them with the E Ink service. This includes:
+- Managing the pool of contexts
+- Dropping frames that are not needed anymore
+- Rendering frames
+- Controlling the flow of sharing frames with the E Ink service
+
+## Pool of contexts
+
+The context is a drawing area. It is used by a renderer to render a frame, which is then forwarded by the GUI service to the E Ink service.
+
+The pool of contexts provides exclusive access to a context in a multithreaded environment. The renderer service locks the context during rendering process. The context is unlocked and available for the renderer service again once it's not used by any other object, e.g. it was displayed by the E Ink service, or cached by the GUI service for later use.
+
+## Initialization
+
+The GUI service is initialized on the E Ink service demand. It uses information received from the E Ink service in order to initialize its resources, e.g. ContextPool.
+
+
+
+## Handling draw requests
+
+Draw request is an application's request used to render the screen.
+
+
+
+### Dropping expired draw requests
+
+If a consecutive request comes to the GUI service, all awaiting requests which deal with the same display area are marked as expired and dropped.
+This solution makes the display more responsive. Removing it from the current implementation may introduce a latency visible to a user when e.g. writing a message.
+
+## Sharing the frame with the E Ink service
+
+### Idle state
+
+If the GUI service is not busy waiting for the previous frame to be displayed by the E Ink service, it immediately sends the rendered frame to the E Ink service.
+
+
+
+### Busy state
+
+The GUI service may render a few next frames while the E Ink service is updating the display. If so, the GUI service caches the last rendered frame and immediately sends it to the E Ink service once it finishes its job.
+
+
+
+
A module-services/service-gui/doc/display_environment_init.png => module-services/service-gui/doc/display_environment_init.png +0 -0
A module-services/service-gui/doc/display_environment_init.pu => module-services/service-gui/doc/display_environment_init.pu +12 -0
@@ 0,0 1,12 @@
+@startuml
+participant "Service E Ink" as eink
+participant "Service GUI" as gui
+participant "Context Pool" as pool
+
+eink -> gui: DisplayReadyNotification()
+activate gui
+
+gui -> gui: setState(Idle)
+gui -> pool ** : create
+
+@enduml
A module-services/service-gui/doc/handle_draw_request.png => module-services/service-gui/doc/handle_draw_request.png +0 -0
A module-services/service-gui/doc/handle_draw_request.pu => module-services/service-gui/doc/handle_draw_request.pu +18 -0
@@ 0,0 1,18 @@
+@startuml
+participant "Application" as app
+participant "Service GUI" as gui
+participant "Context Pool" as pool
+participant "Rendering Worker" as worker
+participant "Service E Ink" as eink
+
+app -> gui: Draw request
+gui -> eink: Prepare display
+gui -> worker: Drop awaiting requests
+gui -> worker: Add new request
+activate worker
+
+worker -> pool: Borrow context
+worker -> worker: Render
+return Finished(contextId)
+
+@enduml
A module-services/service-gui/doc/update_eink_if_busy.png => module-services/service-gui/doc/update_eink_if_busy.png +0 -0
A module-services/service-gui/doc/update_eink_if_busy.pu => module-services/service-gui/doc/update_eink_if_busy.pu +8 -0
@@ 0,0 1,8 @@
+@startuml
+participant "Service GUI" as gui
+participant "Context Pool" as pool
+
+gui -> gui: Cache frame
+gui -> pool: Return context
+
+@enduml
A module-services/service-gui/doc/update_eink_if_idle.png => module-services/service-gui/doc/update_eink_if_idle.png +0 -0
A module-services/service-gui/doc/update_eink_if_idle.pu => module-services/service-gui/doc/update_eink_if_idle.pu +14 -0
@@ 0,0 1,14 @@
+@startuml
+participant "Service GUI" as gui
+participant "Context Pool" as pool
+participant "Service E Ink" as eink
+
+gui -> pool: Peek processed context
+gui -> eink: Display context
+return FinishedNotification
+gui -> pool: Return context
+group If next frame ready
+ gui -> eink: Display context
+end
+
+@enduml
M module-services/service-time/timeEvents/CalendarTimeEvents.cpp => module-services/service-time/timeEvents/CalendarTimeEvents.cpp +7 -4
@@ 43,9 43,11 @@ namespace stm
filterTill = filterFrom;
}
- return DBServiceAPI::GetQuery(service(),
- db::Interface::Name::Events,
- std::make_unique<db::query::events::SelectFirstUpcoming>(filterFrom, filterTill));
+ const auto [succeed, _] =
+ DBServiceAPI::GetQuery(service(),
+ db::Interface::Name::Events,
+ std::make_unique<db::query::events::SelectFirstUpcoming>(filterFrom, filterTill));
+ return succeed;
}
uint32_t CalendarTimeEvents::calcToNextEventInterval(std::unique_ptr<db::QueryResult> nextEventQueryResult)
@@ 74,8 76,9 @@ namespace stm
bool CalendarTimeEvents::sendEventFiredQuery()
{
eventRecord.reminder_fired = TimePointNow();
- return DBServiceAPI::GetQuery(
+ const auto [succeed, _] = DBServiceAPI::GetQuery(
service(), db::Interface::Name::Events, std::make_unique<db::query::events::Edit>(eventRecord));
+ return succeed;
}
void CalendarTimeEvents::invokeEvent()
M module-sys/Service/Worker.cpp => module-sys/Service/Worker.cpp +5 -0
@@ 237,6 237,11 @@ namespace sys
return getControlQueue().Enqueue(&messageToSend, portMAX_DELAY);
}
+ bool Worker::sendCommand(WorkerCommand command)
+ {
+ return getServiceQueue().Enqueue(&command);
+ }
+
bool Worker::send(uint32_t cmd, uint32_t *data)
{
assert(xTaskGetCurrentTaskHandle() == runnerTask);
M module-sys/Service/Worker.hpp => module-sys/Service/Worker.hpp +3 -1
@@ 98,7 98,6 @@ namespace sys
std::optional<size_t> controlQueueIndex;
std::optional<size_t> serviceQueueIndex;
WorkerQueue &getControlQueue() const;
- WorkerQueue &getServiceQueue() const;
static constexpr std::size_t controlMessagesCount = static_cast<std::size_t>(ControlMessage::MessageCount);
static constexpr std::size_t defaultStackSize = 2048;
@@ 117,10 116,13 @@ namespace sys
protected:
virtual bool handleMessage(uint32_t queueID) = 0;
+ WorkerQueue &getServiceQueue() const;
+
xQueueHandle getQueueHandleByName(const std::string &qname) const;
std::shared_ptr<WorkerQueue> getQueueByName(const std::string &qname) const;
bool sendControlMessage(ControlMessage message);
+ bool sendCommand(WorkerCommand command);
State getState() const;
const static uint32_t SERVICE_QUEUE_LENGTH = 10;
M module-sys/SystemManager/PowerManager.cpp => module-sys/SystemManager/PowerManager.cpp +25 -20
@@ 55,14 55,20 @@ namespace sys
{
const auto freq = lowPowerControl->GetCurrentFrequency();
const auto oscSource = lowPowerControl->GetCurrentOscillatorSource();
+ const auto pll2State = lowPowerControl->GetCurrentPll2State();
- /// switch osc source first
+ // switch osc source first
if (freq == bsp::LowPowerMode::CpuFrequency::Level_1 &&
oscSource == bsp::LowPowerMode::OscillatorSource::Internal) {
lowPowerControl->SwitchOscillatorSource(bsp::LowPowerMode::OscillatorSource::External);
}
- /// then increase frequency
+ // then turn on pll2
+ if (pll2State == bsp::LowPowerMode::Pll2State::Disable) {
+ lowPowerControl->SwitchPll2State(bsp::LowPowerMode::Pll2State::Enable);
+ }
+
+ // and increase frequency
if (freq < bsp::LowPowerMode::CpuFrequency::Level_6) {
lowPowerControl->SetCpuFrequency(bsp::LowPowerMode::CpuFrequency::Level_6);
}
@@ 71,38 77,37 @@ namespace sys
void PowerManager::DecreaseCpuFrequency() const
{
const auto freq = lowPowerControl->GetCurrentFrequency();
- const auto oscSource = lowPowerControl->GetCurrentOscillatorSource();
auto level = bsp::LowPowerMode::CpuFrequency::Level_1;
+ // We temporarily limit the minimum CPU frequency
+ // due to problems with the UART of the GSM modem
switch (freq) {
case bsp::LowPowerMode::CpuFrequency::Level_6:
level = bsp::LowPowerMode::CpuFrequency::Level_5;
break;
- case bsp::LowPowerMode::CpuFrequency::Level_5:
+ default:
level = bsp::LowPowerMode::CpuFrequency::Level_4;
break;
- case bsp::LowPowerMode::CpuFrequency::Level_4:
- level = bsp::LowPowerMode::CpuFrequency::Level_3;
- break;
- case bsp::LowPowerMode::CpuFrequency::Level_3:
- level = bsp::LowPowerMode::CpuFrequency::Level_2;
- break;
- case bsp::LowPowerMode::CpuFrequency::Level_2:
- level = bsp::LowPowerMode::CpuFrequency::Level_1;
- break;
- case bsp::LowPowerMode::CpuFrequency::Level_1:
- break;
}
- /// decrease frequency first
+ // decrease frequency first
if (level != freq) {
lowPowerControl->SetCpuFrequency(level);
}
- /// then switch osc source
- if (level == bsp::LowPowerMode::CpuFrequency::Level_1 &&
- oscSource == bsp::LowPowerMode::OscillatorSource::External) {
- lowPowerControl->SwitchOscillatorSource(bsp::LowPowerMode::OscillatorSource::Internal);
+ if (level == bsp::LowPowerMode::CpuFrequency::Level_1) {
+ const auto oscSource = lowPowerControl->GetCurrentOscillatorSource();
+ const auto pll2State = lowPowerControl->GetCurrentPll2State();
+
+ // then switch osc source
+ if (oscSource == bsp::LowPowerMode::OscillatorSource::External) {
+ lowPowerControl->SwitchOscillatorSource(bsp::LowPowerMode::OscillatorSource::Internal);
+ }
+
+ // and turn off pll2
+ if (pll2State == bsp::LowPowerMode::Pll2State::Enable) {
+ lowPowerControl->SwitchPll2State(bsp::LowPowerMode::Pll2State::Disable);
+ }
}
}
M module-utils/CMakeLists.txt => module-utils/CMakeLists.txt +2 -0
@@ 36,6 36,8 @@ set (SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/ical/ParserICS.cpp
${CMAKE_CURRENT_SOURCE_DIR}/time/TimeRangeParser.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Utils.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/log/Logger.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/log/log.cpp
)
add_library(${PROJECT_NAME} STATIC ${SOURCES} ${BOARD_SOURCES})
D module-utils/board/cross/log.cpp => module-utils/board/cross/log.cpp +0 -351
@@ 1,351 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#include "../../log/log.hpp"
-#include "../../segger/rtt/SEGGER_RTT.h"
-#include <ticks.hpp>
-extern "C"
-{
-#include "FreeRTOS.h"
-}
-#include "semphr.h"
-#include <assert.h>
-#include <board.h>
-#include <critical.hpp>
-
-/*
- * TODO: M.P
- * Redictering log output to serial console in this form need be considered as quick&dirty solution.
- * It should be refactored i.e serial terminal BSP created and used here instead of using RT1051's low-level routines
- * directly.
- */
-
-#define LOGGER_BUFFER_SIZE 4096
-
-#if LOG_USE_COLOR == 1
-#define CONSOLE_ESCAPE_COLOR_BLACK "\x1b[30m"
-#define CONSOLE_ESCAPE_COLOR_RED "\x1b[31m"
-#define CONSOLE_ESCAPE_COLOR_GREEN "\x1b[32m"
-#define CONSOLE_ESCAPE_COLOR_YELLOW "\x1b[33m"
-#define CONSOLE_ESCAPE_COLOR_BLUE "\x1b[34m"
-#define CONSOLE_ESCAPE_COLOR_MAGENTA "\x1b[35m"
-#define CONSOLE_ESCAPE_COLOR_CYAN "\x1b[36m"
-#define CONSOLE_ESCAPE_COLOR_WHITE "\x1b[37m"
-#define CONSOLE_ESCAPE_COLOR_RESET "\x1b[0m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_BLACK "\x1b[40m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_RED "\x1b[41m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_GREEN "\x1b[42m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_YELLOW "\x1b[43m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_BLUE "\x1b[44m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_MAGENTA "\x1b[45m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_CYAN "\x1b[46m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_WHITE "\x1b[47m"
-#else
-#define CONSOLE_ESCAPE_COLOR_BLACK
-#define CONSOLE_ESCAPE_COLOR_RED
-#define CONSOLE_ESCAPE_COLOR_GREEN
-#define CONSOLE_ESCAPE_COLOR_YELLOW
-#define CONSOLE_ESCAPE_COLOR_BLUE
-#define CONSOLE_ESCAPE_COLOR_MAGENTA
-#define CONSOLE_ESCAPE_COLOR_CYAN
-#define CONSOLE_ESCAPE_COLOR_WHITE
-#define CONSOLE_ESCAPE_COLOR_RESET
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_BLACK
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_RED
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_GREEN
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_YELLOW
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_BLUE
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_MAGENTA
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_CYAN
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_WHITE
-#endif
-
-const char *level_names[] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
-
-#if LOG_USE_COLOR == 1
-static const char *level_colors[] = {"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"};
-#else
-static const char *level_colors[] = {"", "", "", "", "", ""};
-#endif
-
-#include <map>
-#include <string>
-
-namespace
-{
- const char *critStr = "CRIT";
- const char *irqStr = "IRQ";
-} // namespace
-
-struct Logger
-{
- Logger(logger_level level = LOGTRACE) : level{level}
- {
-
- lock = xSemaphoreCreateMutex();
-
- assert(lock != NULL);
-
- /* Acquire lock */
- if (xSemaphoreTake(lock, 100) != pdPASS) {
- return;
- }
-
- level = level;
-
- /* Release lock */
- xSemaphoreGive(lock);
- }
-
- bool logLock()
- {
- // if called from the ISR use DI/EI. In all other cases use semaphore.
- if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) {
- bt = cpp_freertos::CriticalSection::EnterFromISR();
- }
- else if (xSemaphoreTake(lock, 100) != pdPASS) {
- return false;
- }
- return true;
- }
-
- void logUnlock()
- {
- // if called from the ISR use DI/EI. In all other cases use semaphore.
- if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) {
- cpp_freertos::CriticalSection::ExitFromISR(bt);
- }
- else {
- xSemaphoreGive(lock);
- }
- }
-
- std::map<std::string, logger_level> filtered = {
- // {"ServiceDB", logger_level::LOGFATAL},
- {"ApplicationManager", logger_level::LOGINFO},
- {"TS0710Worker", logger_level::LOGINFO},
- {"ServiceCellular", logger_level::LOGINFO},
- {"ServiceAntenna", logger_level::LOGINFO},
- {"ServiceFota", logger_level::LOGINFO},
- {"ServiceEink", logger_level::LOGINFO},
- {"ServiceDB", logger_level::LOGINFO},
- // make sure that we got defined map entries for at least crit and irq
- // make sure that we got defined map entries for at least crit and irq
- {critStr, logger_level::LOGTRACE},
- {irqStr, logger_level::LOGTRACE}};
-
- /// Filter out not interesting logs via thread Name
- /// its' using fact that:
- /// - TRACE is level 0, for unedfined lookups it will be alvways trace
- /// - it will be one time init for apps which doesn't tell what level they should have
- /// - for others it will be o1 lookup so it's fine
- logger_level filterThreadName(const std::string &name)
- {
- return filtered[name];
- }
-
- xSemaphoreHandle lock;
- logger_level level;
- BaseType_t bt;
-};
-
-static Logger logger;
-static char loggerBuffer[LOGGER_BUFFER_SIZE] = {0};
-
-static inline size_t loggerBufferSizeLeft(char *ptr)
-{
- assert(&loggerBuffer[LOGGER_BUFFER_SIZE] - ptr > 0);
- return &loggerBuffer[LOGGER_BUFFER_SIZE] - ptr;
-}
-
-void log_Printf(const char *fmt, ...)
-{
- if (!logger.logLock()) {
- return;
- }
-
- char *ptr = loggerBuffer;
- va_list args;
-
- va_start(args, fmt);
- ptr += vsnprintf(ptr, loggerBufferSizeLeft(ptr), fmt, args);
- va_end(args);
-
- log_WriteToDevice((uint8_t *)loggerBuffer, ptr - loggerBuffer);
-
- logger.logUnlock();
-}
-
-/// get string description:
-/// - in critical seciton - return CRIT
-/// - in interrupt return - IRQ
-/// - else return thread name
-static inline const char *getTaskDesc()
-{
- return xTaskGetCurrentTaskHandle() == NULL
- ? critStr
- : xPortIsInsideInterrupt() ? irqStr : pcTaskGetName(xTaskGetCurrentTaskHandle());
-}
-
-static void _log_Log(
- logger_level level, const char *file, int line, const char *function, const char *fmt, va_list args)
-{
- if (!logger.logLock()) {
- return;
- }
-
- char *ptr = loggerBuffer;
-
- // filter out not interesing logs
- if (logger.filterThreadName(getTaskDesc()) >= level) {
- logger.logUnlock();
- return;
- }
-
- ptr += snprintf(
- ptr, loggerBufferSizeLeft(ptr), "%lu ms ", cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()));
-
-#if LOG_USE_COLOR == 1
-
- ptr += snprintf(ptr,
- loggerBufferSizeLeft(ptr),
- "%s%-5s " CONSOLE_ESCAPE_COLOR_MAGENTA "[%-10s] \x1b[90m%s:%d:" CONSOLE_ESCAPE_COLOR_RESET,
- level_colors[level],
- level_names[level],
- getTaskDesc(),
- file,
- line);
-#else
- ptr += snprintf(ptr,
- loggerBufferSizeLeft(ptr),
- "%-5s [%s] %s:%s:%d: ",
- level_names[level],
- getTaskDesc(),
- file,
- function,
- line);
-#endif
- ptr += vsnprintf(ptr, loggerBufferSizeLeft(ptr), fmt, args);
- ptr += snprintf(ptr, loggerBufferSizeLeft(ptr), "\n");
-
- log_WriteToDevice((uint8_t *)loggerBuffer, ptr - loggerBuffer);
-
- /* Release lock */
- logger.logUnlock();
-}
-
-void log_Log(logger_level level, const char *file, int line, const char *function, const char *fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- _log_Log(level, file, line, function, fmt, args);
- va_end(args);
-}
-
-/**
- * Update log level
- * @param level [in] - new log level
- */
-void log_SetLevel(logger_level level)
-{
- logger.level = level;
-}
-
-extern "C"
-{
-
- void bt_log_custom(const char *file, int line, const char *foo, const char *fmt, ...)
- {
- va_list args;
- va_start(args, fmt);
- _log_Log(LOGTRACE, file, line, foo, fmt, args);
- va_end(args);
- }
-
- int printf(__const char *__restrict __format, ...)
- {
- /* Acquire lock */
- if (!logger.logLock()) {
- return -1;
- }
-
- char *ptr = loggerBuffer;
- va_list args;
-
- va_start(args, __format);
- ptr += vsnprintf(ptr, loggerBufferSizeLeft(ptr), __format, args);
- va_end(args);
-
- unsigned int numBytes = ptr - loggerBuffer;
- SEGGER_RTT_Write(0, (uint8_t *)loggerBuffer, numBytes);
-
- /* Release lock */
- logger.logUnlock();
-
- return numBytes;
- }
-
- int vprintf(const char *__restrict __format, va_list __arg)
- {
- /* Acquire lock */
- if (!logger.logLock()) {
- return -1;
- }
- char *ptr = loggerBuffer;
- ptr += vsnprintf(ptr, loggerBufferSizeLeft(ptr), __format, __arg);
-
- unsigned int numBytes = ptr - loggerBuffer;
- SEGGER_RTT_Write(0, (uint8_t *)loggerBuffer, numBytes);
-
- /* Release lock */
- logger.logUnlock();
-
- return numBytes;
- }
-
- int log_assert(__const char *__restrict __format, ...)
- {
- /* Acquire lock */
- if (!logger.logLock()) {
- return -1;
- }
-
- char *ptr = loggerBuffer;
- ptr += snprintf(
- ptr, loggerBufferSizeLeft(ptr), "%lu ms ", cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()));
- logger_level level = LOGFATAL;
- ptr += snprintf(ptr,
- loggerBufferSizeLeft(ptr),
- "%s%-5s " CONSOLE_ESCAPE_COLOR_MAGENTA "[%-10s] \x1b[31mASSERTION " CONSOLE_ESCAPE_COLOR_RESET,
- level_colors[level],
- level_names[level],
- getTaskDesc());
-
- va_list args;
- va_start(args, __format);
- ptr += vsnprintf(ptr, loggerBufferSizeLeft(ptr), __format, args);
- va_end(args);
-
- unsigned int numBytes = ptr - loggerBuffer;
-
- log_WriteToDevice((uint8_t *)loggerBuffer, ptr - loggerBuffer);
-
- /* Release lock */
- logger.logUnlock();
-
- return numBytes;
- }
-
- void __assert_func(const char *file, int line, const char *func, const char *failedexpr)
- {
- log_assert("\"%s\" failed: file \"%s\", line %d%s%s\n",
- failedexpr,
- file,
- line,
- func ? ", function: " : "",
- func ? func : "");
-
- abort();
- /* NOTREACHED */
- }
-};
A module-utils/board/cross/log_rt1051.cpp => module-utils/board/cross/log_rt1051.cpp +120 -0
@@ 0,0 1,120 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <board.h>
+#include <critical.hpp>
+#include <gsl/gsl_util>
+#include <macros.h>
+#include "log/log.hpp"
+#include "log/Logger.hpp"
+#include "segger/rtt/SEGGER_RTT.h"
+#include <ticks.hpp>
+
+/// get string description:
+/// - in critical seciton - return CRIT
+/// - in interrupt return - IRQ
+/// - else return thread name
+static inline const char *getTaskDesc()
+{
+ return xTaskGetCurrentTaskHandle() == nullptr
+ ? Log::Logger::CRIT_STR
+ : xPortIsInsideInterrupt() ? Log::Logger::IRQ_STR : pcTaskGetName(xTaskGetCurrentTaskHandle());
+}
+
+namespace Log
+{
+ void Logger::addLogHeader(logger_level level, const char *file, int line, const char *function)
+ {
+ loggerBufferCurrentPos += snprintf(&loggerBuffer[loggerBufferCurrentPos],
+ loggerBufferSizeLeft(),
+ "%lu ms ",
+ cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()));
+
+ loggerBufferCurrentPos += snprintf(&loggerBuffer[loggerBufferCurrentPos],
+ loggerBufferSizeLeft(),
+ "%-5s [%s] %s:%s:%d: ",
+ level_names[level],
+ getTaskDesc(),
+ file,
+ function,
+ line);
+ }
+
+ bool Logger::filterLogs(logger_level level)
+ {
+ return GetLogLevel(getTaskDesc()) < level;
+ }
+
+ void Logger::logToDevice(const char *fmt, va_list args)
+ {
+ loggerBufferCurrentPos = 0;
+ loggerBufferCurrentPos += snprintf(&loggerBuffer[loggerBufferCurrentPos],
+ loggerBufferSizeLeft(),
+ "%lu ms ",
+ cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()));
+
+ loggerBufferCurrentPos += snprintf(&loggerBuffer[loggerBufferCurrentPos],
+ loggerBufferSizeLeft(),
+ "%-5s [%-10s] \x1b[31mASSERTION ",
+ level_names[LOGFATAL],
+ getTaskDesc());
+
+ loggerBufferCurrentPos += vsnprintf(&loggerBuffer[loggerBufferCurrentPos], loggerBufferSizeLeft(), fmt, args);
+ logToDevice(Device::DEFAULT, loggerBuffer, loggerBufferCurrentPos);
+ }
+
+ void Logger::logToDevice(Device device, std::string_view log, size_t length)
+ {
+ switch (device) {
+ case Device::DEFAULT:
+ log_WriteToDevice(reinterpret_cast<const uint8_t *>(log.data()), length);
+ break;
+ case Device::SEGGER_RTT:
+ SEGGER_RTT_Write(0, reinterpret_cast<const void *>(log.data()), length);
+ break;
+ default:
+ break;
+ }
+ }
+} // namespace Log
+
+using Log::Logger;
+
+extern "C"
+{
+ int printf(__const char *__restrict __format, ...)
+ {
+ va_list args;
+ va_start(args, __format);
+ const auto result = Logger::get().log(Log::Device::SEGGER_RTT, __format, args);
+ va_end(args);
+ return result;
+ }
+
+ int vprintf(const char *__restrict __format, va_list __arg)
+ {
+ return Logger::get().log(Log::Device::SEGGER_RTT, __format, __arg);
+ }
+
+ int log_assert(__const char *__restrict __format, ...)
+ {
+ va_list args;
+ va_start(args, __format);
+ const auto result = Logger::get().logAssert(__format, args);
+ va_end(args);
+ return result;
+ }
+
+ void __assert_func(const char *file, int line, const char *func, const char *failedexpr)
+ {
+ log_assert("\"%s\" failed: file \"%s\", line %d%s%s\n",
+ failedexpr,
+ file,
+ line,
+ func ? ", function: " : "",
+ func ? func : "");
+
+ abort();
+ /* NOTREACHED */
+ }
+};
D module-utils/board/linux/log.cpp => module-utils/board/linux/log.cpp +0 -137
@@ 1,137 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#include "../../log/log.hpp"
-#include <iostream>
-#include <string>
-#include <mutex>
-#include <ticks.hpp>
-#include <fstream>
-#include <string_view>
-
-#define LOGGER_BUFFER_SIZE 4096
-
-#define CONSOLE_ESCAPE_COLOR_BLACK "\x1b[30m"
-#define CONSOLE_ESCAPE_COLOR_RED "\x1b[31m"
-#define CONSOLE_ESCAPE_COLOR_GREEN "\x1b[32m"
-#define CONSOLE_ESCAPE_COLOR_YELLOW "\x1b[33m"
-#define CONSOLE_ESCAPE_COLOR_BLUE "\x1b[34m"
-#define CONSOLE_ESCAPE_COLOR_MAGENTA "\x1b[35m"
-#define CONSOLE_ESCAPE_COLOR_CYAN "\x1b[36m"
-#define CONSOLE_ESCAPE_COLOR_WHITE "\x1b[37m"
-#define CONSOLE_ESCAPE_COLOR_RESET "\x1b[0m"
-
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_BLACK "\x1b[40m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_RED "\x1b[41m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_GREEN "\x1b[42m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_YELLOW "\x1b[43m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_BLUE "\x1b[44m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_MAGENTA "\x1b[45m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_CYAN "\x1b[46m"
-#define CONSOLE_ESCAPE_COLOR_BACKGROUND_WHITE "\x1b[47m"
-
-const char *level_names[] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
-
-#if LOG_USE_COLOR == 1
-static const char *level_colors[] = {"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"};
-#endif
-
-struct Logger
-{
- Logger(logger_level level = LOGTRACE) : level{level}
- {}
-
- std::mutex lock;
- logger_level level;
-};
-
-static Logger logger;
-static char loggerBuffer[LOGGER_BUFFER_SIZE] = {0};
-
-void dumpToFile(std::string_view log, size_t length)
-{
- static std::fstream logFile(logFileName, std::fstream::out);
- logFile.write(log.data(), length);
- logFile.flush();
-}
-
-void log_Printf(const char *fmt, ...)
-{
- /* Acquire lock */
- std::lock_guard<std::mutex> guard(logger.lock);
- char *ptr = loggerBuffer;
- ptr[0] = 0;
- va_list args;
-
- va_start(args, fmt);
- ptr += vsnprintf(ptr, LOGGER_BUFFER_SIZE - 1, fmt, args);
- va_end(args);
-
- std::cout << loggerBuffer;
- dumpToFile(loggerBuffer, ptr - loggerBuffer);
-}
-
-static void _log_Log(
- logger_level level, const char *file, int line, const char *function, const char *fmt, va_list args)
-{
- if (level < logger.level) {
- return;
- }
-
- std::lock_guard<std::mutex> guard(logger.lock);
-
- char *ptr = loggerBuffer;
-
- ptr += snprintf(ptr,
- &loggerBuffer[LOGGER_BUFFER_SIZE] - ptr,
- "%d ms ",
- cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()));
-
-#if LOG_USE_COLOR == 1
- ptr += snprintf(ptr,
- &loggerBuffer[LOGGER_BUFFER_SIZE] - ptr,
- "%s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
- level_colors[level],
- level_names[level],
- file,
- line);
-#else
- ptr += snprintf(
- ptr, &loggerBuffer[LOGGER_BUFFER_SIZE] - ptr, "%-5s %s:%s:%d: ", level_names[level], file, function, line);
-#endif
- ptr += vsnprintf(ptr, &loggerBuffer[LOGGER_BUFFER_SIZE] - ptr, fmt, args);
- ptr += snprintf(ptr, &loggerBuffer[LOGGER_BUFFER_SIZE] - ptr, "\n");
-
- std::cout << loggerBuffer;
- dumpToFile(loggerBuffer, ptr - loggerBuffer);
-}
-
-__attribute__((weak)) void log_Log(
- logger_level level, const char *file, int line, const char *function, const char *fmt, ...)
-{
- va_list args;
- va_start(args, fmt);
- _log_Log(level, file, line, function, fmt, args);
- va_end(args);
-}
-
-/**
- * Update log level
- * @param level [in] - new log level
- */
-void log_SetLevel(logger_level level)
-{
- logger.level = level;
-}
-
-extern "C"
-{
-
- void bt_log_custom(const char *file, int line, const char *foo, const char *fmt, ...)
- {
- va_list args;
- va_start(args, fmt);
- _log_Log(LOGTRACE, file, line, foo, fmt, args);
- va_end(args);
- }
-};
A module-utils/board/linux/log_linux.cpp => module-utils/board/linux/log_linux.cpp +55 -0
@@ 0,0 1,55 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "log/log.hpp"
+#include "log/Logger.hpp"
+#include <iostream>
+#include <string_view>
+#include <ticks.hpp>
+
+#if LOG_USE_COLOR == 1
+static const char *level_colors[] = {"\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"};
+#endif
+
+namespace Log
+{
+ void Logger::addLogHeader(logger_level level, const char *file, int line, const char *function)
+ {
+ loggerBufferCurrentPos += snprintf(&loggerBuffer[loggerBufferCurrentPos],
+ LOGGER_BUFFER_SIZE - loggerBufferCurrentPos,
+ "%d ms ",
+ cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()));
+#if LOG_USE_COLOR == 1
+ loggerBufferCurrentPos += snprintf(&loggerBuffer[loggerBufferCurrentPos],
+ LOGGER_BUFFER_SIZE - loggerBufferCurrentPos,
+ "%s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
+ level_colors[level],
+ level_names[level],
+ file,
+ line);
+#else
+ loggerBufferCurrentPos += snprintf(&loggerBuffer[loggerBufferCurrentPos],
+ LOGGER_BUFFER_SIZE - loggerBufferCurrentPos,
+ "%-5s %s:%s:%d: ",
+ level_names[level],
+ file,
+ function,
+ line);
+#endif
+ }
+
+ bool Logger::filterLogs(logger_level _level)
+ {
+ return _level >= level;
+ }
+
+ void Logger::logToDevice(const char *, va_list)
+ {
+ assert(false && "Not implemented");
+ }
+
+ void Logger::logToDevice(Device, std::string_view log, size_t)
+ {
+ std::cout << log;
+ }
+} // namespace Log
M module-utils/i18n/i18n.cpp => module-utils/i18n/i18n.cpp +21 -19
@@ 5,16 5,12 @@
#include "i18n.hpp"
#include "Utils.hpp"
#include <cstdio>
-#include <filesystem>
#include <purefs/filesystem_paths.hpp>
namespace utils
{
namespace
{
- const auto LanguageDirPath = std::filesystem::path{"assets"} / "lang";
- constexpr auto extension = ".json";
-
auto returnNonEmptyString(const std::string &str, const std::string &ret) -> const std::string &
{
return str.empty() ? ret : str;
@@ 24,7 20,7 @@ namespace utils
i18n localize;
json11::Json LangLoader::createJson(const std::string &filename)
{
- const auto path = LanguageDirPath / (filename + extension);
+ const auto path = utils::localize.DisplayLanguageDirPath / (filename + utils::files::jsonExtension);
auto fd = std::fopen(path.c_str(), "r");
if (fd == nullptr) {
LOG_FATAL("Error during opening file %s", path.c_str());
@@ 55,7 51,16 @@ namespace utils
std::vector<Language> LangLoader::getAvailableDisplayLanguages() const
{
std::vector<std::string> languageNames;
- for (const auto &entry : std::filesystem::directory_iterator(LanguageDirPath)) {
+ for (const auto &entry : std::filesystem::directory_iterator(utils::localize.DisplayLanguageDirPath)) {
+ languageNames.push_back(std::filesystem::path(entry.path()).stem());
+ }
+ return languageNames;
+ }
+
+ std::vector<Language> LangLoader::getAvailableInputLanguages() const
+ {
+ std::vector<std::string> languageNames;
+ for (const auto &entry : std::filesystem::directory_iterator(utils::localize.InputLanguageDirPath)) {
languageNames.push_back(std::filesystem::path(entry.path()).stem());
}
return languageNames;
@@ 63,25 68,22 @@ namespace utils
void i18n::setInputLanguage(const Language &lang)
{
- if (lang == currentInputLanguage) {
+ if (lang == inputLanguage) {
return;
}
- currentInputLanguage = lang;
- if (lang == fallbackLanguageName) {
- inputLanguage = fallbackLanguage;
- }
- else {
- json11::Json pack = loader.createJson(lang);
- inputLanguage = pack;
- }
+ inputLanguage = lang;
}
- const std::string &i18n::getInputLanguage(const std::string &str)
+
+ const std::string &i18n::getInputLanguage(const std::string &inputMode)
{
// if language pack returned nothing then try default language
- if (inputLanguage[str].string_value().empty()) {
- return returnNonEmptyString(fallbackLanguage[str].string_value(), str);
+ if (inputLanguage.empty()) {
+ inputLanguageFilename = fallbackLanguageName + utils::files::breakSign + inputMode;
+ }
+ else {
+ inputLanguageFilename = inputLanguage + utils::files::breakSign + inputMode;
}
- return returnNonEmptyString(inputLanguage[str].string_value(), str);
+ return inputLanguageFilename;
}
const std::string &i18n::get(const std::string &str)
M module-utils/i18n/i18n.hpp => module-utils/i18n/i18n.hpp +14 -4
@@ 5,16 5,24 @@
#include "json/json11.hpp"
#include <string>
#include <vfs.hpp>
+#include <filesystem>
using Language = std::string;
namespace utils
{
+ namespace files
+ {
+ constexpr auto jsonExtension = ".json";
+ constexpr auto breakSign = "_";
+ } // namespace files
+
class LangLoader
{
public:
virtual ~LangLoader() = default;
std::vector<Language> getAvailableDisplayLanguages() const;
+ std::vector<Language> getAvailableInputLanguages() const;
json11::Json createJson(const std::string &filename);
};
@@ 22,20 30,22 @@ namespace utils
{
private:
json11::Json displayLanguage;
- json11::Json inputLanguage;
json11::Json fallbackLanguage; // backup language if item not found
LangLoader loader;
Language fallbackLanguageName;
+ Language inputLanguage = fallbackLanguageName;
+ Language inputLanguageFilename;
Language currentDisplayLanguage = fallbackLanguageName;
- Language currentInputLanguage = fallbackLanguageName;
bool backupLanguageInitializer = false;
public:
- static constexpr auto DefaultLanguage = "English";
+ static constexpr auto DefaultLanguage = "English";
+ const std::filesystem::path DisplayLanguageDirPath = "assets/lang";
+ const std::filesystem::path InputLanguageDirPath = "assets/profiles";
virtual ~i18n() = default;
void setInputLanguage(const Language &lang);
- const std::string &getInputLanguage(const std::string &str);
+ const std::string &getInputLanguage(const std::string &inputMode);
const std::string &get(const std::string &str);
void setDisplayLanguage(const Language &lang);
void setFallbackLanguage(const Language &lang);
M module-utils/ical/ParserICS.cpp => module-utils/ical/ParserICS.cpp +211 -38
@@ 2,7 2,8 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ParserICS.hpp"
-//#include <exception>
+#include <cmath>
+#include <module-utils/time/time_date_validation.hpp>
namespace ical
{
@@ 72,6 73,8 @@ namespace ical
} // namespace frequency
constexpr inline auto count = "COUNT=";
constexpr inline auto interval = "INTERVAL=";
+
+ constexpr inline auto max_interval = 2;
} // namespace rrule
namespace duration
@@ 79,22 82,44 @@ namespace ical
constexpr inline auto minutesInHour = 60;
constexpr inline auto minutesInDay = 24 * minutesInHour;
constexpr inline auto minutesInWeek = 7 * minutesInDay;
+
+ namespace minutes
+ {
+ constexpr inline auto never_happens_value = 0xFFFF;
+ }
} // namespace duration
+ constexpr inline auto date_char_length = yearDigitsNumb + monthDigitsNumb + dayDigitsNumb;
+ constexpr inline auto time_char_length = HourDigitsNumb + MinDigitsNumb + SecDigitsNumb;
+ constexpr inline auto dt_char_length = date_char_length + 1 + time_char_length;
} // namespace ical
Duration::Duration(uint32_t week, uint32_t day, uint32_t hour, uint32_t minute)
{
+ if (minute == ical::duration::minutes::never_happens_value) {
+ this->minute = ical::duration::minutes::never_happens_value;
+ return;
+ }
this->week = week;
this->day = day;
this->hour = hour;
this->minute = minute;
+
+ if (!utils::time::validateTime(hour, minute, false)) {
+ isValid = false;
+ }
}
Duration::Duration(const std::string &property)
{
+ if (property.empty()) {
+ LOG_DEBUG("Duration is empty. Event with no Alarm!");
+ this->minute = ical::duration::minutes::never_happens_value;
+ return;
+ }
uint32_t i = 0;
- if (property.empty() || (property[0] != 'P' && property[1] != 'P')) {
+ if ((property[0] != 'P' && property[1] != 'P')) {
LOG_ERROR("Duration constructor: Invalid format provided: %s", property.c_str());
+ isValid = false;
return;
}
while (property[i] != '\0' && i < property.length()) {
@@ 118,14 143,24 @@ Duration::Duration(const std::string &property)
case 'M':
this->minute = stoi(value);
break;
+ default:
+ LOG_ERROR("Wrong duration unit value format");
+ isValid = false;
+ break;
}
}
catch (std::exception &e) {
LOG_DEBUG("Duration conversion from string to int failed with exception:%s", e.what());
+ isValid = false;
}
}
++i;
}
+
+ if (!utils::time::validateTime(hour, minute, false)) {
+ LOG_ERROR("Duration time value is invalid");
+ isValid = false;
+ }
}
auto Duration::getDurationInMinutes() const -> uint32_t
@@ 140,20 175,23 @@ auto Duration::getDurationInMinutes() const -> uint32_t
auto Duration::getDurationString() const -> std::string
{
std::string durationString;
+ if (minute == ical::duration::minutes::never_happens_value) {
+ return durationString;
+ }
if (week) {
- durationString += std::to_string(week) + 'W';
+ durationString += utils::to_string(week) + 'W';
}
if (day) {
- durationString += std::to_string(day) + 'D';
+ durationString += utils::to_string(day) + 'D';
}
if (hour || minute) {
durationString += 'T';
}
if (hour) {
- durationString += std::to_string(hour) + 'H';
+ durationString += utils::to_string(hour) + 'H';
}
if (minute) {
- durationString += std::to_string(minute) + 'M';
+ durationString += utils::to_string(minute) + 'M';
}
if (week == 0 && day == 0 && hour == 0 && minute == 0) {
durationString += "T0M";
@@ 161,13 199,14 @@ auto Duration::getDurationString() const -> std::string
return durationString;
}
-Alarm::Alarm(Duration &beforeEvent, Action action)
+Alarm::Alarm(Duration &timeBeforeEvent, Action action)
{
- if (beforeEvent.getDurationString().empty()) {
- LOG_ERROR("Got empty duration value");
+ if (!timeBeforeEvent.isValid) {
+ LOG_ERROR("Duration format is invalid");
+ isValid = false;
return;
}
- this->trigger = beforeEvent;
+ this->trigger = timeBeforeEvent;
this->action = action;
}
@@ 182,14 221,25 @@ void Alarm::setAction(const std::string &action)
else if (action == ical::alarm::value::action::audio) {
this->action = Action::audio;
}
+ else if (action.empty()) {
+ LOG_DEBUG("Alarm with no action");
+ this->action = Action::none;
+ }
else {
- this->action = Action::invalid;
+ LOG_ERROR("Alarm action invalid");
+ isValid = false;
}
}
void Alarm::setTrigger(const std::string &duration)
{
- this->trigger = Duration(duration);
+ auto timeBeforeEvent = Duration(duration);
+ if (!timeBeforeEvent.isValid) {
+ LOG_ERROR("Duration format is invalid");
+ isValid = false;
+ return;
+ }
+ this->trigger = timeBeforeEvent;
}
auto Alarm::getTriggerValue() const -> Duration
@@ 204,7 254,11 @@ auto Alarm::getActionValue() const -> Action
auto Alarm::getTriggerString() const -> std::string
{
- return ical::alarm::value::before + trigger.getDurationString();
+ auto durationString = trigger.getDurationString();
+ if (durationString.empty()) {
+ return durationString;
+ }
+ return ical::alarm::value::before + durationString;
}
auto Alarm::getActionString() const -> std::string
@@ 220,8 274,8 @@ auto Alarm::getActionString() const -> std::string
case Action::procedure: {
return ical::alarm::value::action::procedure;
}
- case Action::invalid: {
- LOG_ERROR("Alarm with no action");
+ case Action::none: {
+ LOG_DEBUG("Alarm with no action");
return "";
}
}
@@ 230,11 284,15 @@ auto Alarm::getActionString() const -> std::string
RecurrenceRule::RecurrenceRule(Frequency freq, uint32_t count, uint32_t interval)
{
- if (count == 0 || interval == 0) {
- LOG_ERROR("Invalid count or interval value!");
+ if ((count == 0 || interval == 0) && freq != Frequency::never) {
+ LOG_ERROR("Invalid rrule format!");
+ isValid = false;
return;
}
-
+ if (interval > ical::rrule::max_interval) {
+ LOG_ERROR("Invalid interval value!");
+ isValid = false;
+ }
this->frequency = freq;
this->count = count;
this->interval = interval;
@@ 254,9 312,12 @@ void RecurrenceRule::setFrequency(const std::string &property)
else if (property == ical::rrule::frequency::yearly) {
this->frequency = Frequency::yearly;
}
+ else if (property.empty()) {
+ this->frequency = Frequency::never;
+ }
else {
LOG_ERROR("Invalid frequency!");
- this->frequency = Frequency::invalid;
+ isValid = false;
}
}
void RecurrenceRule::setCount(const std::string &property)
@@ 265,7 326,8 @@ void RecurrenceRule::setCount(const std::string &property)
this->count = stoi(property);
}
catch (...) {
- LOG_ERROR("Count value not conversionable to int!");
+ LOG_ERROR("Count value is not an integer!");
+ isValid = false;
}
}
@@ 276,6 338,7 @@ void RecurrenceRule::setInterval(const std::string &property)
}
catch (...) {
LOG_ERROR("Interval value not conversionable to int!");
+ isValid = false;
}
}
@@ 314,8 377,8 @@ auto RecurrenceRule::getFrequencyString() const -> std::string
frequencyStr = ical::rrule::frequency::yearly;
break;
}
- case Frequency::invalid: {
- LOG_ERROR("Frequency invalid");
+ case Frequency::never: {
+ LOG_DEBUG("Frequency never");
return "";
}
}
@@ 324,21 387,12 @@ auto RecurrenceRule::getFrequencyString() const -> std::string
auto RecurrenceRule::getCountString() const -> std::string
{
- if (this->frequency == Frequency::invalid) {
- LOG_ERROR("Frequency value is invalid!");
- return "";
- }
- return std::to_string(this->count);
+ return utils::to_string(this->count);
}
auto RecurrenceRule::getIntervalString() const -> std::string
{
- if (this->frequency == Frequency::invalid) {
- LOG_ERROR("Frequency value is invalid!");
- return "";
- }
-
- return std::to_string(this->interval);
+ return utils::to_string(this->interval);
}
auto Event::getDateFromIcalFormat(const std::string &icalDateTime) const -> std::string
@@ 346,21 400,50 @@ auto Event::getDateFromIcalFormat(const std::string &icalDateTime) const -> std:
return icalDateTime.substr(0, icalDateTime.find_first_of('T'));
}
+auto Event::getYearFromIcalDate(const std::string &icalDate) const -> std::string
+{
+ return icalDate.substr(0, yearDigitsNumb);
+}
+
+auto Event::getMonthFromIcalDate(const std::string &icalDate) const -> std::string
+{
+ return icalDate.substr(yearDigitsNumb, monthDigitsNumb);
+}
+
+auto Event::getDayFromIcalDate(const std::string &icalDate) const -> std::string
+{
+ return icalDate.substr(yearDigitsNumb + monthDigitsNumb, dayDigitsNumb);
+}
+
auto Event::getTimeFromIcalFormat(const std::string &icalDateTime) const -> std::string
{
return icalDateTime.substr(icalDateTime.find_first_of('T') + 1);
}
+auto Event::getHourFromIcalTime(const std::string &icalTime) const -> std::string
+{
+ return icalTime.substr(0, HourDigitsNumb);
+}
+
+auto Event::getMinutesFromIcalTime(const std::string &icalTime) const -> std::string
+{
+ return icalTime.substr(HourDigitsNumb, MinDigitsNumb);
+}
+
+auto Event::getSecondsFromIcalTime(const std::string &icalTime) const -> std::string
+{
+ return icalTime.substr(HourDigitsNumb + MinDigitsNumb, SecDigitsNumb);
+}
+
auto Event::dateStringFrom(const std::string &icalDate) const -> std::string
{
- return icalDate.substr(0, yearDigitsNumb) + "-" + icalDate.substr(yearDigitsNumb, monthDigitsNumb) + "-" +
- icalDate.substr(yearDigitsNumb + monthDigitsNumb, dayDigitsNumb);
+ return getYearFromIcalDate(icalDate) + "-" + getMonthFromIcalDate(icalDate) + "-" + getDayFromIcalDate(icalDate);
}
auto Event::timeStringFrom(const std::string &icalTime) const -> std::string
{
- return icalTime.substr(0, HourDigitsNumb) + ":" + icalTime.substr(HourDigitsNumb, MinDigitsNumb) + ":" +
- icalTime.substr(HourDigitsNumb + MinDigitsNumb, SecDigitsNumb);
+ return getHourFromIcalTime(icalTime) + ":" + getMinutesFromIcalTime(icalTime) + ":" +
+ getSecondsFromIcalTime(icalTime);
}
auto Event::TimePointFromIcalDate(const std::string &icalDateTime) const -> TimePoint
@@ 387,28 470,118 @@ auto Event::TimePointToIcalDate(const TimePoint &tp) const -> std::string
return IcalDate;
}
+auto Event::isDate(const std::string &date) -> bool
+{
+ return utils::time::validateDate(getDayFromIcalDate(date), getMonthFromIcalDate(date), getYearFromIcalDate(date));
+}
+
+auto Event::isTime(const std::string &time) -> bool
+{
+ [[maybe_unused]] uint32_t seconds;
+ try {
+ seconds = stoi(getSecondsFromIcalTime(time));
+ }
+ catch (...) {
+ LOG_ERROR("Seconds value is not an integer!");
+ return false;
+ }
+ return utils::time::validateTime(getHourFromIcalTime(time), getMinutesFromIcalTime(time), false);
+}
+
+auto Event::validateDT(const std::string &dt) -> bool
+{
+ uint32_t dateTimeSize;
+ if (dt.find_first_of('Z') == ical::date_char_length + ical::time_char_length) {
+ dateTimeSize = ical::dt_char_length + 1;
+ }
+ else {
+ dateTimeSize = ical::dt_char_length;
+ }
+ if (dt.size() != dateTimeSize) {
+ LOG_ERROR("Date time length is invalid");
+ return false;
+ }
+
+ auto separatorIndex = dt.find_first_of('T');
+ LOG_DEBUG("Separator index = %d", (int)separatorIndex);
+ if (separatorIndex != (yearDigitsNumb + monthDigitsNumb + dayDigitsNumb)) {
+ LOG_ERROR("Date time separator is invalid");
+ return false;
+ }
+
+ auto date = getDateFromIcalFormat(dt);
+ auto time = getTimeFromIcalFormat(dt);
+
+ if (!isDate(date)) {
+ LOG_ERROR("Date is invalid");
+ return false;
+ }
+
+ if (!isTime(time)) {
+ LOG_ERROR("Time is invalid");
+ return false;
+ }
+
+ return true;
+}
+
+auto Event::validateUID(const std::string &UID) -> bool
+{
+ auto DTimestamp = UID.substr(0, UID.find_first_of('-'));
+ auto id = UID.substr(UID.find_first_of('-') + 1);
+
+ try {
+ stoi(id);
+ }
+ catch (...) {
+ LOG_ERROR("UID value is not an integer!");
+ return false;
+ }
+
+ return validateDT(DTimestamp);
+}
+
Event::Event(const std::string &summary, const TimePoint from, TimePoint till, const std::string &uid)
{
+ if (summary.empty()) {
+ isValid = false;
+ }
+ if (!validateUID(uid) && !uid.empty()) {
+ isValid = false;
+ }
+ this->uid = uid;
this->summary = summary;
this->dtstart = from;
this->dtend = till;
- this->uid = uid;
}
void Event::setUID(const std::string &property)
{
+ if (!validateUID(property) && !property.empty()) {
+ LOG_ERROR("UID invalid format");
+ isValid = false;
+ }
this->uid = property;
}
void Event::setSummary(const std::string &property)
{
+ if (property.empty()) {
+ isValid = false;
+ }
this->summary = property;
}
void Event::setDTStart(const std::string &property)
{
+ if (!validateDT(property)) {
+ isValid = false;
+ }
this->dtstart = TimePointFromIcalDate(property);
}
void Event::setDTEnd(const std::string &property)
{
+ if (!validateDT(property)) {
+ isValid = false;
+ }
this->dtend = TimePointFromIcalDate(property);
}
M module-utils/ical/ParserICS.hpp => module-utils/ical/ParserICS.hpp +24 -7
@@ 17,6 17,7 @@ class Duration
uint32_t week = 0, day = 0, hour = 0, minute = 0;
public:
+ bool isValid = true;
Duration(uint32_t week = 0, uint32_t day = 0, uint32_t hour = 0, uint32_t minute = 0);
explicit Duration(const std::string &property);
@@ 26,7 27,7 @@ class Duration
enum class Action
{
- invalid,
+ none,
audio,
display,
procedure
@@ 35,11 36,12 @@ enum class Action
class Alarm
{
Duration trigger;
- Action action;
+ Action action = Action::none;
public:
- Alarm() = default;
- explicit Alarm(Duration &beforeEvent, Action action = Action::invalid);
+ bool isValid = true;
+ Alarm() = default;
+ explicit Alarm(Duration &beforeEvent, Action action = Action::none);
void setAction(const std::string &action);
void setTrigger(const std::string &duration);
@@ 53,7 55,7 @@ class Alarm
enum class Frequency
{
- invalid,
+ never,
daily,
weekly,
monthly,
@@ 63,11 65,12 @@ enum class Frequency
class RecurrenceRule
{
- Frequency frequency = Frequency::invalid;
+ Frequency frequency = Frequency::never;
uint32_t count = 0;
uint32_t interval = 0;
public:
+ bool isValid = true;
RecurrenceRule() = default;
RecurrenceRule(Frequency freq, uint32_t count, uint32_t interval);
@@ 91,8 94,21 @@ class Event
TimePoint dtstart;
TimePoint dtend;
+ auto isDate(const std::string &dt) -> bool;
+ auto isTime(const std::string &dt) -> bool;
+ auto validateDT(const std::string &dt) -> bool;
+ auto validateUID(const std::string &uid) -> bool;
+
[[nodiscard]] auto getDateFromIcalFormat(const std::string &icalDateTime) const -> std::string;
+ [[nodiscard]] auto getYearFromIcalDate(const std::string &icalDate) const -> std::string;
+ [[nodiscard]] auto getMonthFromIcalDate(const std::string &icalDate) const -> std::string;
+ [[nodiscard]] auto getDayFromIcalDate(const std::string &icalDate) const -> std::string;
+
[[nodiscard]] auto getTimeFromIcalFormat(const std::string &icalDateTime) const -> std::string;
+ [[nodiscard]] auto getHourFromIcalTime(const std::string &icalTime) const -> std::string;
+ [[nodiscard]] auto getMinutesFromIcalTime(const std::string &icalTime) const -> std::string;
+ [[nodiscard]] auto getSecondsFromIcalTime(const std::string &icalTime) const -> std::string;
+
[[nodiscard]] auto dateStringFrom(const std::string &icalDate) const -> std::string;
[[nodiscard]] auto timeStringFrom(const std::string &icalTime) const -> std::string;
@@ 100,7 116,8 @@ class Event
[[nodiscard]] auto TimePointToIcalDate(const TimePoint &tp) const -> std::string;
public:
- Event() = default;
+ bool isValid = true;
+ Event() = default;
Event(const std::string &summary, TimePoint from, TimePoint till, const std::string &uid);
void setUID(const std::string &property);
A module-utils/log/Logger.cpp => module-utils/log/Logger.cpp +95 -0
@@ 0,0 1,95 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "critical.hpp"
+#include <fstream>
+#include <gsl/gsl_util>
+#include "Logger.hpp"
+#include "macros.h"
+
+namespace Log
+{
+ std::map<std::string, logger_level> Logger::filtered = {{"ApplicationManager", logger_level::LOGINFO},
+ {"TS0710Worker", logger_level::LOGINFO},
+ {"ServiceCellular", logger_level::LOGINFO},
+ {"ServiceAntenna", logger_level::LOGINFO},
+ {"ServiceFota", logger_level::LOGINFO},
+ {"ServiceEink", logger_level::LOGINFO},
+ {"ServiceDB", logger_level::LOGINFO},
+ {CRIT_STR, logger_level::LOGTRACE},
+ {IRQ_STR, logger_level::LOGTRACE}};
+ const char *Logger::level_names[] = {"TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
+
+ auto Logger::GetLogLevel(const std::string &name) -> logger_level
+ {
+ return filtered[name];
+ }
+
+ bool Logger::lock()
+ {
+ if (isIRQ()) {
+ bt = cpp_freertos::CriticalSection::EnterFromISR();
+ }
+ else {
+ return mutex.Lock();
+ }
+ return true;
+ }
+
+ auto Logger::log(Device device, const char *fmt, va_list args) -> int
+ {
+ if (!lock()) {
+ return -1;
+ }
+ auto _ = gsl::finally([this] { unlock(); });
+
+ loggerBufferCurrentPos = 0;
+ loggerBufferCurrentPos += vsnprintf(&loggerBuffer[loggerBufferCurrentPos], loggerBufferSizeLeft(), fmt, args);
+ logToDevice(device, loggerBuffer, loggerBufferCurrentPos);
+
+ return loggerBufferCurrentPos;
+ }
+
+ void Logger::log(
+ logger_level level, const char *file, int line, const char *function, const char *fmt, va_list args)
+ {
+ if (!filterLogs(level)) {
+ return;
+ }
+
+ if (!lock()) {
+ return;
+ }
+ auto _ = gsl::finally([this] { unlock(); });
+
+ loggerBufferCurrentPos = 0;
+ addLogHeader(level, file, line, function);
+
+ loggerBufferCurrentPos += vsnprintf(&loggerBuffer[loggerBufferCurrentPos], loggerBufferSizeLeft(), fmt, args);
+ loggerBufferCurrentPos += snprintf(&loggerBuffer[loggerBufferCurrentPos], loggerBufferSizeLeft(), "\n");
+
+ logToDevice(Device::DEFAULT, loggerBuffer, loggerBufferCurrentPos);
+ }
+
+ auto Logger::logAssert(const char *fmt, va_list args) -> int
+ {
+ if (!lock()) {
+ return -1;
+ }
+ auto _ = gsl::finally([this] { unlock(); });
+
+ logToDevice(fmt, args);
+
+ return loggerBufferCurrentPos;
+ }
+
+ void Logger::unlock()
+ {
+ if (isIRQ()) {
+ cpp_freertos::CriticalSection::ExitFromISR(bt);
+ }
+ else {
+ mutex.Unlock();
+ }
+ }
+}; // namespace Log
A module-utils/log/Logger.hpp => module-utils/log/Logger.hpp +70 -0
@@ 0,0 1,70 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <assert.h>
+#include <board.h>
+#include "log.hpp"
+#include <map>
+#include <mutex.hpp>
+#include <string>
+
+namespace Log
+{
+ enum class Device
+ {
+ DEFAULT,
+ SEGGER_RTT
+ };
+
+ class Logger
+ {
+ public:
+ [[nodiscard]] static Logger &get()
+ {
+ static Logger logger;
+ return logger;
+ }
+ auto log(Device device, const char *fmt, va_list args) -> int;
+ void log(logger_level level, const char *file, int line, const char *function, const char *fmt, va_list args);
+ auto logAssert(const char *fmt, va_list args) -> int;
+
+ static constexpr auto CRIT_STR = "CRIT";
+ static constexpr auto IRQ_STR = "IRQ";
+
+ private:
+ Logger() = default;
+
+ void addLogHeader(logger_level level,
+ const char *file = nullptr,
+ int line = -1,
+ const char *function = nullptr);
+ [[nodiscard]] bool filterLogs(logger_level level);
+ /// Filter out not interesting logs via thread Name
+ /// its' using fact that:
+ /// - TRACE is level 0, for unedfined lookups it will be alvways trace
+ /// - it will be one time init for apps which doesn't tell what level they should have
+ /// - for others it will be o1 lookup so it's fine
+ [[nodiscard]] auto GetLogLevel(const std::string &name) -> logger_level;
+ bool lock();
+ void logToDevice(const char *fmt, va_list args);
+ void logToDevice(Device device, std::string_view log, size_t length);
+ [[nodiscard]] size_t loggerBufferSizeLeft() const noexcept
+ {
+ const auto sizeLeft = LOGGER_BUFFER_SIZE - loggerBufferCurrentPos;
+ assert(sizeLeft > 0);
+ return sizeLeft;
+ }
+ void unlock();
+
+ BaseType_t bt;
+ cpp_freertos::MutexStandard mutex;
+ logger_level level{LOGTRACE};
+ char loggerBuffer[LOGGER_BUFFER_SIZE] = {0};
+ size_t loggerBufferCurrentPos = 0;
+
+ static const char *level_names[];
+ static std::map<std::string, logger_level> filtered;
+ };
+} // namespace Log
A module-utils/log/log.cpp => module-utils/log/log.cpp +35 -0
@@ 0,0 1,35 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "log.hpp"
+#include "Logger.hpp"
+#include <ticks.hpp>
+
+using Log::Logger;
+
+void log_Printf(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ Logger::get().log(Log::Device::DEFAULT, fmt, args);
+ va_end(args);
+}
+
+void log_Log(logger_level level, const char *file, int line, const char *function, const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ Logger::get().log(level, file, line, function, fmt, args);
+ va_end(args);
+}
+
+extern "C"
+{
+ void bt_log_custom(const char *file, int line, const char *foo, const char *fmt, ...)
+ {
+ va_list args;
+ va_start(args, fmt);
+ Logger::get().log(LOGTRACE, file, line, foo, fmt, args);
+ va_end(args);
+ }
+}
M module-utils/log/log.hpp => module-utils/log/log.hpp +2 -2
@@ 62,7 62,6 @@ extern "C"
*/
void log_Log(logger_level level, const char *file, int line, const char *function, const char *fmt, ...)
__attribute__((format(printf, 5, 6)));
- void log_SetLevel(logger_level level);
void log_Printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
void log_WriteToDevice(const uint8_t *pBuffer, unsigned NumBytes);
@@ 84,7 83,8 @@ extern "C"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
-static const char *logFileName = "MuditaOS.log";
+static const size_t LOGGER_BUFFER_SIZE = 8192;
+static const double MAX_BUFFER_UTIL_MEM = 0.8 * LOGGER_BUFFER_SIZE;
#pragma GCC diagnostic pop
#endif /* LOG_LOG_H_ */
M module-utils/targets/Target_Cross.cmake => module-utils/targets/Target_Cross.cmake +1 -1
@@ 1,6 1,6 @@
set(BOARD_SOURCES ${BOARD_SOURCES}
- ${CMAKE_CURRENT_SOURCE_DIR}/board/cross/log.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/board/cross/log_rt1051.cpp
CACHE INTERNAL ""
)
M module-utils/targets/Target_Linux.cmake => module-utils/targets/Target_Linux.cmake +1 -1
@@ 1,6 1,6 @@
set(BOARD_SOURCES
- ${CMAKE_CURRENT_SOURCE_DIR}/board/linux/log.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/board/linux/log_linux.cpp
CACHE INTERNAL "")
M source/MessageType.hpp => source/MessageType.hpp +1 -0
@@ 79,6 79,7 @@ enum class MessageType
CellularSimProcedure, // Broadcast on sim state changed
CellularSimResponse, // Send to PIN window (show, error state, hide)
CellularSimVerifyPinRequest, // Send from PIN window with PIN, PUK, ... number
+ CellularSetVoLTE,
CellularPacketData, ///< for all PacketData messages
M test/README.md => test/README.md +12 -8
@@ 77,20 77,24 @@ methods:
from harness.harness import Harness
from harness.interface.defs import key_codes
-port_name = "/dev/ttyACM0"
+# try to init Harness object with automatic port detection
+harness = Harness.from_detect()
-# init Harness object and open serial port
-harness = Harness(port_name)
-
-#get current application name
+# get current application name
current_window = harness.get_window_name()
#open messages when phone is unlocked
-@harness.with_phone_unlocked()
+@harness.with_phone_unlocked
def do_after_unlock(connection):
+ # open menu
+ connection.send_key_code(key_codes["enter"])
+
harness.open_application("messages")
- #send joystick down keypress
- connection.send_key(key_codes["down"])
+ # send joystick down keypress
+ connection.send_key_code(key_codes["down"])
+
+ # open a thread
+ connection.send_key_code(key_codes["enter"])
```
### pyTest running
A test/firmware_update_test/.gdbinit-1051-autocontinue => test/firmware_update_test/.gdbinit-1051-autocontinue +10 -0
@@ 0,0 1,10 @@
+set pagination off
+target remote localhost:2331
+monitor reset 0
+monitor halt
+monitor memU32 0x401BC000 = 128;
+load
+eval "monitor exec SetRTTAddr %p", &_SEGGER_RTT
+info threads
+thread 2
+continue
A test/firmware_update_test/README.md => test/firmware_update_test/README.md +25 -0
@@ 0,0 1,25 @@
+# Firmware update test
+
+The aim of this test is to check the correctness of the `service-desktop` API and upgrade the phone software.
+
+This test is based on [Harness](../README.md) class and uses `pyTest` to verify the update process. It makes use of `service-desktop`
+API to upload a tarball update package to the phone's memory and execute the update. After a successful update the phone should reset
+and an update check will be performed. In the end, an update verification status will be shown in the console (along with the exit code).
+
+## Usage
+To be able to conduct the test (or just use the script as an update utility) a few prerequisites have to be met:
+
+* Pure needs connected via USB
+* Your Python version has to be at least 3.8
+* Your Python virtual environment needs to be properly set up (as mentioned in PyTest's [readme](../README.md))
+
+After all of the requirements are fulfilled, the script can be safely executed:
+
+```python
+python3 ./update.py tarball_path
+```
+The serial port used by the phone will be detected automatically.
+
+
+To obtain the tarball package use either proper CMake target (work in progress) or the `genupdatepkg.sh` script located
+in the `config` subfolder in the root repository.<
\ No newline at end of file
A test/firmware_update_test/update.py => test/firmware_update_test/update.py +147 -0
@@ 0,0 1,147 @@
+#!/usr/bin/env python
+# Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+import sys
+import time
+import sys
+import os.path
+import json
+
+sys.path.append(
+ os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
+
+from harness import log
+from harness.harness import Harness
+from harness.interface.defs import key_codes, endpoint, method
+from harness.utils import Timeout
+from harness.interface.error import TestError, Error
+from functools import partial
+
+# uploaded file chunk size - according to
+# https://appnroll.atlassian.net/wiki/spaces/MFP/pages/656637953/Protocol+description
+CHUNK_SIZE = 1024 * 16
+
+# update performing timeout
+UPDATE_TIMEOUT = 30
+
+update_status_code = {
+ 0: "Initial",
+ 1: "UpdateFileSet",
+ 2: "CreatingDirectories",
+ 3: "ExtractingFiles",
+ 4: "UpdatingBootloader",
+ 5: "ChecksumVerification",
+ 6: "VersionVerification",
+ 7: "ReadyForReset"
+}
+
+
+def update(harness, update_filepath: str):
+ connection = harness.get_connection()
+ serial = harness.get_connection().get_serial()
+ file_size = os.path.getsize(update_filepath)
+ filename = update_filepath.split('/')[-1]
+ body = {"command": "download", "fileName": filename, "fileSize": file_size}
+
+ ret = harness.endpoint_request("filesystemUpload", "post", body)["body"]
+ if ret["status"] is not None:
+ log.info(f"Update status: {update_status_code[int(ret['status'])]}")
+
+ log.info("Downloading update file to the target")
+ with open(update_filepath, 'rb') as file:
+ for chunk in iter(partial(file.read, CHUNK_SIZE), b''):
+ print(".", end='', flush=True)
+ serial.write(chunk)
+ print(" ")
+
+ body = {"fileName": filename}
+ ret = harness.endpoint_request("update", "post", body)["body"]
+ if ret["fileName"] != filename and int(ret[fileSize]) != file_size:
+ log.error("Upload error!")
+ exit(1)
+ timer = time.time()
+
+ while True:
+ if time.time() - timer > UPDATE_TIMEOUT:
+ log.error("Update timeout!")
+ return False
+ if serial.in_waiting > 0:
+ result = connection.read(10)
+ ret = json.loads(result)
+ body = ret['body']
+ if "status" in body:
+ status = body["status"]
+ log.info(f"Update status: {status}")
+ timer = time.time()
+ if "reset" in status:
+ log.info("Update finished, wait for device reset...")
+ return True
+
+
+def get_update_list(harness):
+ ret = harness.endpoint_request("deviceInfo", "get", {})
+ device_info = ret["body"]
+ update_history = device_info["updateHistory"]
+ failed_updates = 0
+
+ if update_history is None:
+ log.info("Update history clean!")
+ return [None, 0]
+
+ for update in update_history:
+ if update["finishedError"] != 0 and update["finishedState"] != 6:
+ failed_updates = failed_updates + 1
+
+ log.info(f"Found {len(update_history)} update entries with {failed_updates} failed updates")
+ return [update_history, failed_updates]
+
+
+def main():
+ if len(sys.argv) == 1:
+ print(f'Please pass update file path as the parameter: python {sys.argv[0]} file_path ')
+ raise TestError(Error.PORT_NOT_FOUND)
+
+ harness = None
+
+ with Timeout.limit(seconds=20):
+ while not harness:
+ try:
+ harness = Harness.from_detect()
+ except TestError:
+ pass
+
+ update_filename = str(sys.argv[1])
+ history, fails = get_update_list(harness)
+
+ if update(harness, update_filename):
+ # wait for reboot
+ harness = None
+ log.info("Waiting for device to reset")
+ time.sleep(5)
+ # connect to the phone once again
+ with Timeout.limit(seconds=20):
+ while not harness:
+ try:
+ harness = Harness.from_detect()
+ except TestError:
+ pass
+
+ [new_history, new_fails] = get_update_list(harness)
+ if new_fails != fails or (history is None) or (history is not None and (len(new_history) != len(history) + 1)):
+ log.error("Update failed!")
+ exit(1)
+ else:
+ log.info("Update successful!")
+ exit(0)
+ else:
+ log.error("Update error!")
+ exit(1)
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except TestError as err:
+ log.error(err)
+ exit(err.get_error_code())
M test/harness/harness.py => test/harness/harness.py +22 -7
@@ 7,6 7,7 @@ from harness import utils, log
from harness.interface import CDCSerial as serial
from harness.interface.defs import key_codes, endpoint, method
from harness.utils import send_keystoke, application_keypath, send_char
+from harness.interface.error import TestError, Error
import random
@@ 20,20 21,33 @@ class Harness:
self.port_name = port
self.connection = serial.CDCSerial(port)
+ @classmethod
+ def from_detect(cls):
+ '''
+ Try to instantiate from first detected device.
+ Do not use this method if you need >1 unique devices.
+ '''
+ found = serial.CDCSerial.find_Pures()
+ if found:
+ port = found[0]
+ return cls(port)
+ else:
+ raise TestError(Error.PORT_NOT_FOUND)
+
def get_connection(self):
return self.connection
def get_window_name(self):
- return self.connection.get_window()
+ return self.connection.get_window_name()
def unlock_phone(self):
if self.connection.is_phone_locked():
- self.connection.send_key(key_codes["enter"])
- self.connection.send_key(key_codes["#"])
- self.connection.send_key(3)
- self.connection.send_key(3)
- self.connection.send_key(3)
- self.connection.send_key(3)
+ self.connection.send_key_code(key_codes["enter"])
+ self.connection.send_key_code(key_codes["#"])
+ self.connection.send_key_code(3)
+ self.connection.send_key_code(3)
+ self.connection.send_key_code(3)
+ self.connection.send_key_code(3)
log.info("Phone unlocked")
else:
log.info("Phone already unlocked")
@@ 71,3 85,4 @@ class Harness:
"body": body
})
return ret
+
M test/harness/interface/CDCSerial.py => test/harness/interface/CDCSerial.py +15 -7
@@ 28,19 28,20 @@ class CDCSerial:
try:
self.serial = serial.Serial(port_name, baudrate=115200, timeout=10)
self.serial.flushInput()
- log.info("port opened!")
+ log.info(f"opened port {port_name}!")
break
except (FileNotFoundError, serial.serialutil.SerialException) as err:
- log.error("can't open {}, retrying...".format(port_name))
+ log.error(f"can't open {port_name}, retrying...")
time.sleep(1)
self.timeout = self.timeout - 1
if self.timeout == 0:
- log.error("uart {} not found - probably OS did not boot".format(port_name))
+ log.error(f"uart {port_name} not found - probably OS did not boot")
raise TestError(Error.PORT_NOT_FOUND)
def __del__(self):
try:
self.serial.close()
+ log.info(f"closed port {self.serial.name}")
except (serial.serialutil.SerialException, AttributeError):
pass
@@ 48,7 49,7 @@ class CDCSerial:
msg = {
"endpoint": endpoint["developerMode"],
"method": method["put"],
- "uuid": randrange(1,100),
+ "uuid": randrange(1, 100),
"body": body
}
return msg
@@ 64,7 65,6 @@ class CDCSerial:
header = self.readRaw(length)
payload_length = int(header[1:])
result = self.readRaw(payload_length)
- log.info(f"received length: {len(result)}, payload length:{payload_length}")
return result
def readRaw(self, length):
@@ 77,11 77,10 @@ class CDCSerial:
return json.loads(result)
def writeRaw(self, message, timeout=30):
- log.info(message)
self.serial.write(message.encode())
self.serial.timeout = timeout
- def send_key(self, key_code, key_type=Keytype.short_press, wait=10):
+ def send_key_code(self, key_code, key_type=Keytype.short_press, wait=10):
if key_type is Keytype.long_press:
body = {"keyPressed": key_code, "state": 4}
else:
@@ 130,3 129,12 @@ class CDCSerial:
ret = self.write(self.__wrap_message(body))
return ret["body"]["isLocked"]
+
+ @staticmethod
+ def find_Pures() -> str:
+ '''
+ Return a list of unique paths to all the Mudita Pure phones found connected to the system
+ '''
+ import serial.tools.list_ports as list_ports
+ return [_.device for _ in list_ports.comports() if _.manufacturer == 'Mudita' and _.product == 'Mudita Pure']
+
M test/harness/interface/defs.py => test/harness/interface/defs.py +12 -2
@@ 1,5 1,6 @@
# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+from enum import Enum
endpoint = {
"deviceInfo": 1,
@@ 45,6 46,15 @@ key_codes = {
"sliderMid": 18,
"sliderDown": 17,
"#": ord('#'),
- "*": ord('*')
-
+ "*": ord('*'),
}
+
+
+class SMSType(Enum):
+ DRAFT = 0x01
+ FAILED = 0x02
+ INBOX = 0x04
+ OUTBOX = 0x08
+ QUEUED = 0x10
+ INPUT = 0x12
+ UNKNOWN = 0xFF
M test/harness/utils.py => test/harness/utils.py +64 -34
@@ 55,32 55,32 @@ application_keypath = {
}
keymap = {
- "a": "2",
- "b": "22",
- "c": "222",
- "d": "3",
- "e": "33",
- "f": "333",
- "g": "4",
- "h": "44",
- "i": "444",
- "j": "5",
- "k": "55",
- "l": "555",
- "m": "6",
- "n": "66",
- "o": "666",
- "p": "7",
- "q": "77",
- "r": "777",
- "s": "7777",
- "t": "8",
- "u": "88",
- "v": "888",
- "w": "9",
- "x": "99",
- "y": "999",
- "z": "9999",
+ "A": "2",
+ "B": "22",
+ "C": "222",
+ "D": "3",
+ "E": "33",
+ "F": "333",
+ "G": "4",
+ "H": "44",
+ "I": "444",
+ "J": "5",
+ "K": "55",
+ "L": "555",
+ "M": "6",
+ "N": "66",
+ "O": "666",
+ "P": "7",
+ "Q": "77",
+ "R": "777",
+ "S": "7777",
+ "T": "8",
+ "U": "88",
+ "V": "888",
+ "W": "9",
+ "X": "99",
+ "Y": "999",
+ "Z": "9999",
" ": "0",
".": "1",
",": "11",
@@ 101,8 101,8 @@ keymap = {
def send_keystoke(keypath, connection):
- for i in keypath:
- connection.send_key(key_codes[i])
+ for key in keypath:
+ connection.send_key_code(key_codes[key])
time.sleep(0.3)
@@ 116,23 116,53 @@ def send_char(char: str, connection):
key_type = Keytype.long_press
if last_char is char:
print("repeated key!")
- connection.send_key(key_codes["right"])
- connection.send_key(int(char), key_type)
- connection.send_key(key_codes["right"])
+ connection.send_key_code(key_codes["right"])
+ connection.send_key_code(int(char), key_type)
+ connection.send_key_code(key_codes["right"])
last_char = char
else:
if last_char is keymap[char][0]:
print("repeated key!")
- connection.send_key(key_codes["right"], key_type)
+ connection.send_key_code(key_codes["right"], key_type)
for key in keymap[char]:
- connection.send_key(int(key), key_type)
+ connection.send_key_code(int(key), key_type)
last_char = keymap[char][0]
def send_number(number: str, connection):
if number.isnumeric():
for digit in number:
- connection.send_key(int(digit))
+ connection.send_key_code(int(digit))
time.sleep(0.3)
+
+### timeout from https://stackoverflow.com/a/601168/5752094
+
+from contextlib import contextmanager
+
+import signal
+
+
+class Timeout(Exception):
+ '''
+ usage:
+ try:
+ with Timeout.limit(10):
+ long_function_call()
+ except Timeout as e:
+ print("Timed out!")
+ '''
+
+ @classmethod
+ @contextmanager
+ def limit(cls, seconds):
+ def signal_handler(signum, frame):
+ raise Timeout("Timed out!")
+
+ signal.signal(signal.SIGALRM, signal_handler)
+ signal.alarm(seconds)
+ try:
+ yield
+ finally:
+ signal.alarm(0)
M test/make_a_call.py => test/make_a_call.py +2 -2
@@ 17,10 17,10 @@ def call(harness, phone_number: str, duration: int):
# enter number
harness.send_number(phone_number)
# call
- connection.send_key(key_codes["fnLeft"])
+ connection.send_key_code(key_codes["fnLeft"])
time.sleep(duration)
- connection.send_key(key_codes["fnRight"])
+ connection.send_key_code(key_codes["fnRight"])
def get_calllog_count(harness):
D test/phone_switch_app.py => test/phone_switch_app.py +0 -20
@@ 1,20 0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-# script walking on desktop
-import time
-from harness.uart import connection, log
-
-
-# get in sms and back 100 times
-for el in range(1, 100):
- log.info("TEST ex: {}".format(el))
- # back
- # move left , enter, read for entry
- connection.send_key(ord('d'))
- connection.send_key(ord('d'))
- connection.send_key(ord('\n'))
- time.sleep(1)
- connection.send_key(12)
- time.sleep(1)
M test/phone_unlock.py => test/phone_unlock.py +5 -10
@@ 3,14 3,9 @@
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
# unlock Desktop
-from harness.uart import connection
-from interface.defs import key_codes
-
-# write pin
-connection.send_key(key_codes["enter"])
-connection.send_key(key_codes["#"])
-connection.send_key(3)
-connection.send_key(3)
-connection.send_key(3)
-connection.send_key(3)
+from harness.harness import Harness
+harness = Harness.from_detect()
+@harness.with_phone_unlocked
+def done(connection):
+ pass
D test/phone_walk.py => test/phone_walk.py +0 -13
@@ 1,13 0,0 @@
-#!/usr/bin/env python
-# Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-# script walking on desktop
-
-from harness.uart import connection
-
-for lol in range(1, 60 * 30):
- connection.send_key(ord('d'))
- connection.send_key(ord('s'))
- connection.send_key(ord('s'))
- connection.send_key(ord('d'))
M test/pytest/conftest.py => test/pytest/conftest.py +65 -25
@@ 12,13 12,15 @@ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.
from harness import log
from harness.harness import Harness
+from harness import utils
from harness.interface.error import TestError, Error
+from harness.interface.CDCSerial import CDCSerial as serial
simulator_port = 'simulator'
def pytest_addoption(parser):
- parser.addoption("--port", type=str, action="store", required=True)
+ parser.addoption("--port", type=str, action="store", required=False)
parser.addoption("--timeout", type=int, action="store", default=15)
parser.addoption("--phone_number", type=int, action="store")
parser.addoption("--call_duration", type=int, action="store", default=30)
@@ 48,39 50,77 @@ def sms_text(request):
@pytest.fixture(scope='session')
def harness(request):
+ '''
+ Try to init one Pure phone with serial port path or automatically
+ '''
port_name = request.config.option.port
- timeout = request.config.option.timeout
-
- if port_name is None:
- pytest.exit("no port provided!")
- assert '/dev' in port_name or simulator_port in port_name
-
- if simulator_port in port_name:
- while timeout != 0:
- try:
- file = open("/tmp/purephone_pts_name", "r")
- break
- except FileNotFoundError as err:
- time.sleep(1)
- timeout = timeout - 1
- print("waiting...")
- if timeout == 0:
- raise TestError(Error.PORT_FILE_NOT_FOUND)
-
- port_name = file.readline()
- if port_name.isascii():
- log.debug("found {} entry!".format(port_name))
+ TIMEOUT = int(request.config.option.timeout)
+ timeout_started = time.time()
+
+ RETRY_EVERY_SECONDS = 1.0
+ try:
+ if port_name is None:
+ log.warning("no port provided! trying automatic detection")
+ harness = None
+
+ with utils.Timeout.limit(seconds=TIMEOUT):
+ while not harness:
+ try:
+ harness = Harness.from_detect()
+ except TestError as e:
+ if e.get_error_code() == Error.PORT_NOT_FOUND:
+ log.info(f"waiting for a serial port… ({TIMEOUT- int(time.time() - timeout_started)})")
+ time.sleep(RETRY_EVERY_SECONDS)
else:
- pytest.exit("not a valid sim pts entry!")
+ assert '/dev' in port_name or simulator_port in port_name
+
+ if simulator_port in port_name:
+ file = None
+ with utils.Timeout.limit(seconds=TIMEOUT):
+ while not file:
+ try:
+ file = open("/tmp/purephone_pts_name", "r")
+ except FileNotFoundError as err:
+ log.info(
+ f"waiting for a simulator port… ({TIMEOUT- int(time.time() - timeout_started)})")
+ time.sleep(RETRY_EVERY_SECONDS)
+ port_name = file.readline()
+ if port_name.isascii():
+ log.debug("found {} entry!".format(port_name))
+ else:
+ pytest.exit("not a valid sim pts entry!")
+
+ harness = Harness(port_name)
+ except utils.Timeout:
+ pytest.exit("couldn't find any viable port. exiting")
+ else:
+ return harness
- harness = Harness(port_name)
- return harness
+
+@pytest.fixture(scope='session')
+def harnesses():
+ '''
+ Automatically init at least two Pure phones
+ '''
+ found_pures = serial.find_Pures()
+ harnesses = [Harness(pure) for pure in found_pures]
+ if not len(harnesses) >= 2:
+ pytest.skip("At least two phones are needed for this test")
+ assert len(harnesses) >= 2
+ return harnesses
@pytest.fixture(scope='session')
def phone_unlocked(harness):
harness.unlock_phone()
assert harness.is_phone_unlocked
+
+@pytest.fixture(scope='session')
+def phones_unlocked(harnesses):
+ for harness in harnesses:
+ harness.unlock_phone()
+ assert harness.is_phone_unlocked
+
def pytest_configure(config):
config.addinivalue_line("markers",
"service_desktop_test: mark test if it's related to service-desktop API")
M test/pytest/service-desktop/test_calendar.py => test/pytest/service-desktop/test_calendar.py +243 -31
@@ 2,41 2,103 @@
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
import pytest
from harness.interface.defs import status
+import copy
-
-@pytest.mark.skip("ICS format bug")
@pytest.mark.service_desktop_test
def test_calendar(harness):
- # add event
- event = "BEGIN:VCALENDAR\nBEGIN:VEVENT\nSUMMARY:Testowy\nDTSTART:20200929T123611\nDTEND:20200929T124611\nBEGIN" \
- ":VALARM\nTRIGGER:-P5M\nEND:VALARM\nEND:VEVENT\nEND:VCALENDAR\n "
+ # add events
+ add_body = {
+ "calendar_events":[
+ {
+ "UID":"",
+ "SUMMARY":"Testowy",
+ "DTSTART":"20200129T123600",
+ "DTEND":"20200129T124600",
+ "RRULE":{
+ "COUNT":"0",
+ "FREQ":"",
+ "INTERVAL":"0"
+ },
+ "VALARM":{
+ "ACTION":"",
+ "TRIGGER":""
+ },
+ "provider":{
+ "iCalUid":"",
+ "id":"",
+ "type":""
+ }
+ }
+ ]
+ }
+
+ ret = harness.endpoint_request("events", "put", add_body)
+ assert ret["status"] == status["OK"]
- body = {"data": event}
- ret = harness.endpoint_request("events", "put", body)
+ #add second event
+ second_event = copy.deepcopy(add_body)
+ second_event["calendar_events"][0]["SUMMARY"] = "Testowy2"
+ ret = harness.endpoint_request("events", "put", second_event)
assert ret["status"] == status["OK"]
# get all limited events
- body = {"offset": 0, "limit": 1}
- ret = harness.endpoint_request("events", "get", body)
+ get_body = {"offset": 0, "limit": 1}
+ ret = harness.endpoint_request("events", "get", get_body)
+
assert ret["status"] == status["OK"]
- assert ret["body"]["data"] == event
- assert ret["body"]["count"] == 1
+ assert ret["body"]["count"] == '1'
+ assert ret["body"]["total_count"] == '2'
+
- uid_index = ret["body"]["data"].index('UID:')
- summary_index = ret["body"]["data"].index('SUMMARY:')
- UID = ret["body"]["data"][uid_index + 4:summary_index]
+ for event in ret["body"]["calendar_events"]:
+ assert event["SUMMARY"] == add_body["calendar_events"][0]["SUMMARY"]
+ assert event["RRULE"]["COUNT"] == add_body["calendar_events"][0]["RRULE"]["COUNT"]
+ assert event["RRULE"]["FREQ"] == add_body["calendar_events"][0]["RRULE"]["FREQ"]
+ assert event["RRULE"]["INTERVAL"] == add_body["calendar_events"][0]["RRULE"]["INTERVAL"]
+ assert event["VALARM"]["ACTION"] == add_body["calendar_events"][0]["VALARM"]["ACTION"]
+ assert event["VALARM"]["TRIGGER"] == add_body["calendar_events"][0]["VALARM"]["TRIGGER"]
+ assert event["provider"]["iCalUid"] == add_body["calendar_events"][0]["provider"]["iCalUid"]
+ assert event["provider"]["id"] == add_body["calendar_events"][0]["provider"]["id"]
+ assert event["provider"]["type"] == add_body["calendar_events"][0]["provider"]["type"]
- assert "SUMMARY:Testowy" in ret["body"]["data"]
- assert "DTSTART:20200929T123611" in ret["body"]["data"]
- assert "DTEND:20200929T124611" in ret["body"]["data"]
+ # remove event
+ del_body = {"UID": ret["body"]["calendar_events"][0]["UID"]}
+ ret = harness.endpoint_request("events", "del", del_body)
+ assert ret["status"] == status["OK"]
+
+ # check events after remove
+ body = {"offset": 0, "limit": 1}
+ ret = harness.endpoint_request("events", "get", body)
+ assert ret["status"] == status["OK"]
assert ret["body"]["count"] == "1"
# update event
- event_update = "BEGIN:VCALENDAR\nBEGIN:VEVENT\nUID:" + UID + "\n" + "SUMMARY:Update\nDTSTART:20200928T123611" \
- "\nDTEND:20200928T124611\nEND:VEVENT\nEND" \
- ":VCALENDAR\n "
- body = {"data": event_update}
- ret = harness.endpoint_request("events", "post", body)
+ update_body = {
+ "calendar_events":[
+ {
+ "UID":ret["body"]["calendar_events"][0]["UID"],
+ "SUMMARY":"TestowyUpdate",
+ "DTSTART":"20200929T123600",
+ "DTEND":"20200929T124600",
+ "RRULE":{
+ "COUNT":"0",
+ "FREQ":"",
+ "INTERVAL":"0"
+ },
+ "VALARM":{
+ "ACTION":"",
+ "TRIGGER":"-PT5M"
+ },
+ "provider":{
+ "iCalUid":"",
+ "id":"",
+ "type":""
+ }
+ }
+ ]
+ }
+
+ ret = harness.endpoint_request("events", "post", update_body)
assert ret["status"] == status["OK"]
# get updated event
@@ 44,20 106,170 @@ def test_calendar(harness):
ret = harness.endpoint_request("events", "get", body)
assert ret["status"] == status["OK"]
- assert ret["body"]["data"] == event_update
- assert ret["body"]["count"] == 1
+ for event in ret["body"]["calendar_events"]:
+ assert event["SUMMARY"] == update_body["calendar_events"][0]["SUMMARY"]
+ assert event["RRULE"]["COUNT"] == update_body["calendar_events"][0]["RRULE"]["COUNT"]
+ assert event["RRULE"]["FREQ"] == update_body["calendar_events"][0]["RRULE"]["FREQ"]
+ assert event["RRULE"]["INTERVAL"] == update_body["calendar_events"][0]["RRULE"]["INTERVAL"]
+ assert event["VALARM"]["ACTION"] == update_body["calendar_events"][0]["VALARM"]["ACTION"]
+ assert event["VALARM"]["TRIGGER"] == update_body["calendar_events"][0]["VALARM"]["TRIGGER"]
+ assert event["provider"]["iCalUid"] == update_body["calendar_events"][0]["provider"]["iCalUid"]
+ assert event["provider"]["id"] == update_body["calendar_events"][0]["provider"]["id"]
+ assert event["provider"]["type"] == update_body["calendar_events"][0]["provider"]["type"]
- assert "SUMMARY:Update" in ret["body"]["data"]
- assert "DTSTART:20200928T123611" in ret["body"]["data"]
- assert "DTEND:20200928T124611" in ret["body"]["data"]
assert ret["body"]["count"] == "1"
- # remove event
- body = {"UID": UID}
- ret = harness.endpoint_request("events", "del", body)
+
+
+ # remove second event
+ body = {"offset": 0, "limit": 1}
+ ret = harness.endpoint_request("events", "get", body)
+ assert ret["status"] == status["OK"]
+
+ del_body = {"UID": ret["body"]["calendar_events"][0]["UID"]}
+ ret = harness.endpoint_request("events", "del", del_body)
assert ret["status"] == status["OK"]
# check events after remove
- body = {"data": "", "count": "1"}
+ body = {"offset": 0, "limit": 1}
ret = harness.endpoint_request("events", "get", body)
assert ret["status"] == status["OK"]
+ assert ret["body"]["count"] == "0"
+
+
+
+ # INVALID FORMAT
+ add_body_invalid = copy.deepcopy(add_body)
+ update_body_invalid = copy.deepcopy(update_body)
+
+ # add events invalid date and time formats
+ # wrong month
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201329T123600"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong day
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201132T123600"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong year
+ add_body_invalid["calendar_events"][0]["DTEND"] = "50201120T123600"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong hour
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201120T253600"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong minute
+ add_body_invalid["calendar_events"][0]["DTEND"] = "20201122T126300"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong second
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201122T1200OO"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ #wrong separator
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201122R120059"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # wrong length
+ add_body_invalid["calendar_events"][0]["DTSTART"] = "20201122T12550000"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ add_body_invalid["calendar_events"][0]["DTEND"] = "201122T125500"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # Summary invalid
+ add_body_invalid["calendar_events"][0]["SUMMARY"] = ""
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+
+ # Alarm invalid
+ #trigger
+ add_body_invalid["calendar_events"][0]["VALARM"]["TRIGGER"] = "-PTM"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ add_body_invalid["calendar_events"][0]["VALARM"]["TRIGGER"] = "-PT4T"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ add_body_invalid["calendar_events"][0]["VALARM"]["TRIGGER"] = "-PT63M"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # Recurrency Rule invalid
+ # Freq
+ add_body_invalid["calendar_events"][0]["RRULE"]["FREQ"] = "DAIL"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # Count
+ add_body_invalid["calendar_events"][0]["RRULE"]["COUNT"] = ""
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # interval
+ add_body_invalid["calendar_events"][0]["RRULE"]["COUNT"] = ""
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+ # UID invalid (UID should be empty in add event request)
+ add_body_invalid["calendar_events"][0]["UID"] = "20201122T125500-63"
+ ret = harness.endpoint_request("events", "put", add_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ add_body_invalid = copy.deepcopy(add_body)
+
+
+ # UPDATE INVALID
+ # UID invalid
+ update_body_invalid["calendar_events"][0]["UID"] = ""
+ ret = harness.endpoint_request("events", "post", update_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ update_body_invalid = copy.deepcopy(update_body_invalid)
+
+ update_body_invalid["calendar_events"][0]["UID"] = "43526gsdgsa322"
+ ret = harness.endpoint_request("events", "post", update_body_invalid)
+ assert ret["status"] == status["BadRequest"]
+ update_body_invalid = copy.deepcopy(update_body_invalid)
+
+ # REMOVE INVALID
+ # It is a valid UID, but not exist in any events in DB
+ # (handle it in db)
+ # del_body = {"UID": "20151122T125500-63"}
+ #
+ # ret = harness.endpoint_request("events", "post", del_body)
+ # assert ret["status"] == status["InternalServerError"]
+
+ # UID invalid
+ del_body = {"UID": "20201122T125500-dadsadsada"}
+ ret = harness.endpoint_request("events", "del", del_body)
+ assert ret["status"] == status["BadRequest"]
+
+ del_body = {"UID": "201122T125500-63"}
+ ret = harness.endpoint_request("events", "del", del_body)
+ assert ret["status"] == status["BadRequest"]
+
M test/pytest/service-desktop/test_templates.py => test/pytest/service-desktop/test_templates.py +1 -1
@@ 59,4 59,4 @@ def test_messages(harness):
body = {"template": True, "count": True}
ret = harness.endpoint_request("messages", "get", body)
assert ret["status"] == status["OK"]
- assert ret["body"]["count"] == count>
\ No newline at end of file
+ assert ret["body"]["count"] == count
M test/pytest/test_call.py => test/pytest/test_call.py +2 -2
@@ 18,10 18,10 @@ def test_call(harness, phone_number, call_duration):
# enter number
harness.send_number(str(phone_number))
# call
- harness.connection.send_key(key_codes["fnLeft"])
+ harness.connection.send_key_code(key_codes["fnLeft"])
time.sleep(call_duration)
# hang up
- harness.connection.send_key(key_codes["fnRight"])
+ harness.connection.send_key_code(key_codes["fnRight"])
count_after = get_calllog_count(harness)
assert count_before + 1 == count_after
M test/pytest/test_call_back.py => test/pytest/test_call_back.py +4 -4
@@ 17,21 17,21 @@ def test_call(harness, call_duration):
count_before = get_calllog_count(harness)
# enter menu
- harness.connection.send_key(key_codes["enter"])
+ harness.connection.send_key_code(key_codes["enter"])
harness.open_application("calllog")
if harness.connection.get_window_name() != "ApplicationCallLog":
time.sleep(2)
assert harness.connection.get_window_name() == "ApplicationCallLog"
# call
- harness.connection.send_key(key_codes["fnLeft"])
+ harness.connection.send_key_code(key_codes["fnLeft"])
time.sleep(call_duration)
# hang up
- harness.connection.send_key(key_codes["fnRight"])
+ harness.connection.send_key_code(key_codes["fnRight"])
count_after = get_calllog_count(harness)
for _ in range(3):
- harness.connection.send_key(key_codes["fnRight"])
+ harness.connection.send_key_code(key_codes["fnRight"])
time.sleep(1)
assert count_before + 1 == count_after
M test/pytest/test_send_message.py => test/pytest/test_send_message.py +16 -12
@@ 3,7 3,7 @@
import time
import pytest
-from harness.interface.defs import key_codes
+from harness.interface.defs import key_codes, SMSType
def get_message_by_text(harness, message: str, phone_number: str):
@@ 14,33 14,37 @@ def get_message_by_text(harness, message: str, phone_number: str):
@pytest.mark.rt1051
@pytest.mark.usefixtures("phone_unlocked")
def test_send_message(harness, phone_number, sms_text):
- messages = get_message_by_text(harness, sms_text.upper(), str(phone_number))
+ old_messages = get_message_by_text(harness, sms_text.upper(), str(phone_number))
# enter menu
- harness.connection.send_key(key_codes["enter"])
+ harness.connection.send_key_code(key_codes["enter"])
harness.open_application("messages")
if harness.connection.get_window_name() != "ApplicationMessages":
time.sleep(2)
assert harness.connection.get_window_name() == "ApplicationMessages"
# create new message
- harness.connection.send_key(key_codes["left"])
+ harness.connection.send_key_code(key_codes["left"])
# enter phone number
harness.send_number(str(phone_number))
# move down to message body
- harness.connection.send_key(key_codes["down"])
+ harness.connection.send_key_code(key_codes["down"])
# write a message
harness.send_text(sms_text)
# send
- harness.connection.send_key(key_codes["enter"])
-
+ harness.connection.send_key_code(key_codes["enter"])
# go back to main screen
for _ in range(3):
- harness.connection.send_key(key_codes["fnRight"])
+ time.sleep(1.2) # it take horrendous amount of time to go back to thread view
+ harness.connection.send_key_code(key_codes["fnRight"])
- time.sleep(2)
new_messages = get_message_by_text(harness, sms_text.upper(), str(phone_number))
+ diff_messages = []
+
+ for message in new_messages:
+ if message not in old_messages:
+ diff_messages.append(message)
+
+ assert len(diff_messages) == 1
+ assert SMSType(diff_messages[0]["type"]) == SMSType.OUTBOX
- diff = [i for i in messages + new_messages if i not in messages or i not in new_messages]
- assert len(diff) == 1
- assert diff[0]["type"] == 0x08
A test/pytest/test_two_phones.py => test/pytest/test_two_phones.py +42 -0
@@ 0,0 1,42 @@
+# Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+import time
+import pytest
+
+from test_call import get_calllog_count, test_call as call
+from test_call_back import test_call as call_back
+
+
+@pytest.fixture(scope='function')
+def two_phones_available(harnesses):
+ assert len(harnesses) == 2
+
+@pytest.mark.rt1051
+@pytest.mark.usefixtures("phones_unlocked")
+@pytest.mark.usefixtures("two_phones_available")
+def test_call_number(harnesses, phone_number, call_duration):
+ role_calling = harnesses[1]
+ role_receiving = harnesses[0]
+
+ reciving_calllog_count = get_calllog_count(role_receiving)
+
+ call(role_calling, phone_number, call_duration) # asserts calling calllog count
+
+ time.sleep(1) # let the db transactions settle…
+ assert (get_calllog_count(role_receiving) == reciving_calllog_count + 1)
+
+
+@pytest.mark.rt1051
+@pytest.mark.usefixtures("phones_unlocked")
+@pytest.mark.usefixtures("two_phones_available")
+def test_call_back(harnesses, phone_number, call_duration):
+ role_calling = harnesses[0]
+ role_receiving = harnesses[1]
+
+ reciving_calllog_count = get_calllog_count(role_receiving)
+
+ call_back(role_calling, call_duration)
+
+ time.sleep(1) # let the db transactions settle…
+ assert (get_calllog_count(role_receiving) == reciving_calllog_count + 1)
M tools/check_commit_messages.py => tools/check_commit_messages.py +1 -1
@@ 33,7 33,7 @@ def validate_commit(commit):
empty_line = lines[1]
body = ''.join(lines[2:]).strip()
- subject_format = r'^\[\w+-\d+\] (?:Add|Change|Fix) .+[^.]$'
+ subject_format = r'^\[\w+-\d+\] .+[^.]$'
if not re.match(subject_format, subject):
errors.append(f'[{commit.hexsha}] invalid subject "{subject}", should match format "{subject_format}"')