diff --git a/.gitmodules b/.gitmodules
index 0406ca94d5d4f98c432216f3072c15fea38be8b9..e3877e67162c697829984109305a8957250cb4aa 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -7,3 +7,6 @@ url = https://git.qt.io/design-studio/kit-dependencies/qt-quickdesigner-componen
 [submodule "3rdparty/qtquickdesigner-components"]
 	path = 3rdparty/qtquickdesigner-components
 	url = https://git.qt.io/design-studio/kit-dependencies/qt-quickdesigner-components
+[submodule "3rdparty/googletest"]
+	path = 3rdparty/googletest
+	url = https://github.com/google/googletest.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index de88c8171fefbfadf3a477b1910bc71058c7e4c8..dd4c369cfc67c1492a28bb2d44abcaa47a983f62 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.14)
 
-project(qtdesignviewer-android LANGUAGES CXX)
+project(qtuiviewer LANGUAGES CXX)
 
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
 
@@ -11,83 +11,40 @@ set(CMAKE_AUTORCC ON)
 set(CMAKE_CXX_STANDARD 17)
 set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
-if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
-    add_compile_options(-Wall -Wextra)
+if(NOT EXISTS ${ANDROID_OPENSSL_PATH})
+    message(WARNING "Cannot find OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
+    message(FATAL_ERROR "Please set ANDROID_OPENSSL_PATH to the path of OpenSSL for Android.")
 endif()
 
+message(STATUS "Found OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
+
 find_package(
     QT NAMES Qt6
-    COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets Concurrent
-    REQUIRED
 )
 
-find_package(
-    Qt6
-    COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets Concurrent
-    REQUIRED
-)
+find_package(Qt6 REQUIRED COMPONENTS Core)
+qt_policy(SET QTP0002 NEW)
 
-set(QT_MINIMUM_VERSION 6.3.0)
+set(QT_MINIMUM_VERSION 6.6.1)
 if(QT_VERSION VERSION_LESS QT_MINIMUM_VERSION)
     message(FATAL_ERROR "Minimum supported Qt version: ${QT_MINIMUM_VERSION}")
 endif()
 
-
-add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zxing-cpp)
-
-qt_add_executable(${PROJECT_NAME}
-    src/importdummy.qml
-    src/main.cpp
-    src/backend.cpp src/backend.h
-    src/serviceconnector.cpp src/serviceconnector.h
-    src/projectmanager.cpp src/projectmanager.h
-    src/dsconnector.cpp src/dsconnector.h
-    src/qrscanner.cpp src/qrscanner.h
-    3rdparty/zxing-cpp/example/ZXingQtReader.h
-    ui/main.qml
-    ui/resources.qrc
-)
-
-target_link_libraries(${PROJECT_NAME} PRIVATE
-    Qt6::Core Qt6::Widgets
-    Qt6::Quick Qt6::Gui
-    Qt6::Qml Qt6::GuiPrivate
-    Qt6::Multimedia Qt6::MultimediaWidgets
-    Qt6::Concurrent
-    ZXing::ZXing
-)
-
-set_property(TARGET ${PROJECT_NAME}
-    APPEND PROPERTY QT_WASM_INITIAL_MEMORY "50MB"
-)
-
-set_property(TARGET ${PROJECT_NAME}
-APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
-)
-
-if(NOT EXISTS ${ANDROID_OPENSSL_PATH})
-    message(WARNING "Cannot find OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
-    message(FATAL_ERROR "Please set ANDROID_OPENSSL_PATH to the path of OpenSSL for Android.")
-endif()
-
-message(STATUS "Found OpenSSL for Android. Path: ${ANDROID_OPENSSL_PATH}")
-set_property(TARGET ${PROJECT_NAME} PROPERTY QT_ANDROID_EXTRA_LIBS
-    ${ANDROID_OPENSSL_PATH}/libcrypto_3.so
-    ${ANDROID_OPENSSL_PATH}/libssl_3.so)
-
 set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_INSTALL_PREFIX})
 
-set_property(TARGET ${PROJECT_NAME} PROPERTY QT_ANDROID_TARGET_SDK_VERSION 34)
-qt6_import_qml_plugins(${PROJECT_NAME})
+execute_process(COMMAND git describe --always --tags OUTPUT_VARIABLE CMAKE_VAR_GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+execute_process(COMMAND git -C ${CMAKE_SOURCE_DIR}/3rdparty/qtquickdesigner-components describe --always --tags OUTPUT_VARIABLE CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+execute_process(COMMAND git -C ${CMAKE_SOURCE_DIR}/3rdparty/zxing-cpp describe --always --tags OUTPUT_VARIABLE CMAKE_VAR_ZXING_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
 
-execute_process(COMMAND git describe --always --tags OUTPUT_VARIABLE GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
-execute_process(COMMAND git -C ${CMAKE_SOURCE_DIR}/3rdparty/qtquickdesigner-components describe --always --tags OUTPUT_VARIABLE QT_QUICK_COMPONENTS_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
-execute_process(COMMAND git -C ${CMAKE_SOURCE_DIR}/3rdparty/zxing-cpp describe --always --tags OUTPUT_VARIABLE ZXING-CPP OUTPUT_STRIP_TRAILING_WHITESPACE)
+add_definitions( -DCMAKE_VAR_GIT_VERSION="${CMAKE_VAR_GIT_VERSION}" )
+add_definitions( -DCMAKE_VAR_QT_QUICK_COMPONENTS_VERSION="${CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION}" )
+add_definitions( -DCMAKE_VAR_ZXING_VERSION="${CMAKE_VAR_ZXING_VERSION}" )
 
-add_definitions( -DGIT_VERSION="${GIT_VERSION}" )
-add_definitions( -DQT_QUICK_COMPONENTS_VERSION="${QT_QUICK_COMPONENTS_VERSION}" )
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src)
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/zxing-cpp)
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tests)
 
-message(STATUS "PROJECT VERSION: ${GIT_VERSION}")
+message(STATUS "PROJECT VERSION: ${CMAKE_VAR_GIT_VERSION}")
 message(STATUS "QT_VERSION: ${QT_VERSION}")
-message(STATUS "QT_QUICK_COMPONENTS_VERSION: ${QT_QUICK_COMPONENTS_VERSION}")
-message(STATUS "ZXING-CPP: ${ZXING-CPP}")
+message(STATUS "QT_QUICK_COMPONENTS_VERSION: ${CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION}")
+message(STATUS "ZXING-CPP: ${CMAKE_VAR_ZXING_VERSION}")
diff --git a/cicd/android/android_release.keystore b/cicd/android/android_release.keystore
new file mode 100644
index 0000000000000000000000000000000000000000..b8d55ca7380da897a4df89fd9b6c524e2c4200f2
Binary files /dev/null and b/cicd/android/android_release.keystore differ
diff --git a/cicd/gitlab-ci.yml b/cicd/gitlab-ci.yml
index 1941f33e32e64cd626c05abeba48e9cff574333d..33bccd4606ffd5314aa0f5311d698cd7c3bc848c 100644
--- a/cicd/gitlab-ci.yml
+++ b/cicd/gitlab-ci.yml
@@ -9,8 +9,14 @@ workflow:
 
 stages:
   - build
+  - test
   - deploy
   - release
 
 include:
   - local: "cicd/stages/*"
+
+.pipeline_common:
+  image: "git.qt.io:4567/design-studio/maintenance/docker-images/qt-full:${QDS_CI_QT_VERSION}"
+  tags:
+    - linux-blade
diff --git a/cicd/stages/build.yml b/cicd/stages/build.yml
index 0bea3919f9df45901f4031d0560262e6caab1ca8..79c476e6d76a01f0fc6c4181580d9c95f18c09de 100644
--- a/cicd/stages/build.yml
+++ b/cicd/stages/build.yml
@@ -1,9 +1,8 @@
 # QDS_CI_BUILD_QT_VERSION_ANDROID and QDS_CI_BUILD_QT_VERSION_WASM are the tags for the docker images.
 # https://git.qt.io/design-studio/maintenance/docker-images/container_registry
 build-android:
+  extends: .pipeline_common
   stage: build
-  tags:
-    - linux-blade
   rules:
     - if: $CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "web"
   parallel:
@@ -17,18 +16,19 @@ build-android:
       - QDS_CI_JOB_TARGET_ARCH: "x86_64"
         QDS_CI_JOB_OPENSSL_PATH: "/opt/openssl/ssl_3/x86_64"
   variables:
-    QDS_CI_JOB_BUILD_PATH: "${CI_PROJECT_DIR}/outdir/build"
+    QDS_CI_JOB_BUILD_PATH: "${QDS_CI_CACHE_PATH}/${QDS_CI_JOB_TARGET_ARCH}/build"
     QDS_CI_JOB_TARGET_PLATFORM: "android"
     QDS_CI_JOB_ARTIFACTS_PATH: ${QDS_CI_ARTIFACTS_PATH}/${QDS_CI_JOB_TARGET_PLATFORM}/${QDS_CI_JOB_TARGET_ARCH}
+    QDS_CI_JOB_ARTIFACTS_PATH_APP: ${QDS_CI_JOB_ARTIFACTS_PATH}/app
+    QDS_CI_JOB_ARTIFACTS_PATH_TEST: ${QDS_CI_JOB_ARTIFACTS_PATH}/test
     QDS_CI_JOB_QT_ANDROID_PATH: "${QDS_CI_QT_PATH}/${QDS_CI_QT_VERSION}/${QDS_CI_JOB_TARGET_PLATFORM}_${QDS_CI_JOB_TARGET_ARCH}"
-  image: "git.qt.io:4567/design-studio/maintenance/docker-images/qt-full:${QDS_CI_QT_VERSION}"
   artifacts:
     name: design-viewer-${CI_JOB_ID}-qt${QDS_CI_QT_VERSION}-${QDS_CI_JOB_TARGET_PLATFORM}-${QDS_CI_JOB_TARGET_ARCH}
     expose_as: "build-artifacts"
     paths:
       - ${QDS_CI_ARTIFACTS_PATH}
   script:
-    - mkdir -p ${QDS_CI_JOB_ARTIFACTS_PATH}
+    - mkdir -p ${QDS_CI_JOB_ARTIFACTS_PATH_APP} ${QDS_CI_JOB_ARTIFACTS_PATH_TEST}
     - ls -l ${QDS_CI_JOB_OPENSSL_PATH}
     - pushd 3rdparty/qtquickdesigner-components
     - export
@@ -58,4 +58,8 @@ build-android:
       -DCMAKE_INSTALL_PREFIX=${QDS_CI_JOB_QT_ANDROID_PATH} \
       -DANDROID_OPENSSL_PATH=${QDS_CI_JOB_OPENSSL_PATH}
     - cmake --build ${QDS_CI_JOB_BUILD_PATH} --target all
-    - cp -r ${QDS_CI_JOB_BUILD_PATH}/android-build/build/outputs/apk/release/* ${QDS_CI_JOB_ARTIFACTS_PATH}
+    - cp -r ${QDS_CI_JOB_BUILD_PATH}/src/android-build/build/outputs/apk/release/* ${QDS_CI_JOB_ARTIFACTS_PATH_APP}
+    - cp -r ${QDS_CI_JOB_BUILD_PATH}/tests/android-build/build/outputs/apk/release/* ${QDS_CI_JOB_ARTIFACTS_PATH_TEST}
+    - echo designviewer | ${DOCKER_ENV_ANDROID_SDK_ROOT}/build-tools/30.0.3/apksigner sign -verbose -ks ${CI_PROJECT_DIR}/cicd/android/android_release.keystore -out ${QDS_CI_JOB_ARTIFACTS_PATH_APP}/android-build-release.apk ${QDS_CI_JOB_ARTIFACTS_PATH_APP}/android-build-release-unsigned.apk
+    - echo designviewer | ${DOCKER_ENV_ANDROID_SDK_ROOT}/build-tools/30.0.3/apksigner sign -verbose -ks ${CI_PROJECT_DIR}/cicd/android/android_release.keystore -out ${QDS_CI_JOB_ARTIFACTS_PATH_TEST}/android-build-release.apk ${QDS_CI_JOB_ARTIFACTS_PATH_TEST}/android-build-release-unsigned.apk
+    - rm -f ${QDS_CI_JOB_ARTIFACTS_PATH_APP}/android-build-release-unsigned.apk ${QDS_CI_JOB_ARTIFACTS_PATH_TEST}/android-build-release-unsigned.apk
diff --git a/cicd/stages/release.yml b/cicd/stages/release.yml
index d49a61142632ea8cac065310169a8b3d46574f5f..9397cbb0dbf345653513f9d3472e43b0b31fb794 100644
--- a/cicd/stages/release.yml
+++ b/cicd/stages/release.yml
@@ -1,26 +1,28 @@
 create-packages:
   stage: release
   image: alpine:latest
-  tags:
-    - linux-blade
+  extends: .pipeline_common
   rules:
     - if: $CI_COMMIT_TAG
   needs:
-    - job: build-android
+    - job: test-x86_64
       optional: false
       artifacts: true
   variables:
     QDS_PACKAGE_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/qt-ui-viewer/${CI_COMMIT_TAG}"
   script:
     - apk add tar curl
-    - cd ${QDS_CI_ARTIFACTS_PATH}
+    - cd ${QDS_CI_ARTIFACTS_PATH_APP}
     - |
       for platform in $(ls); do
         echo "Creating packages for platform $platform"
         cd "$platform"
         for arch in $(ls); do
             echo "Running for $platform-$arch. Compressing..."
-            cd "$arch"
+            # app folder contains the apk for the app
+            # test folder contains the apk for the tests
+            # we only need to compress the app folder
+            cd "$arch/app"
             tar -czf "$platform-$arch.tar.gz" *
             echo "Uploading $platform-$arch.tar.gz to GitLab Package Registry"
             curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file "${platform}-${arch}.tar.gz" ${QDS_PACKAGE_URL}/qt-ui-viewer-${CI_COMMIT_TAG}-qt${QDS_CI_QT_VERSION}-${platform}-${arch}.tar.gz
diff --git a/cicd/stages/test.yml b/cicd/stages/test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2d1ee04c672461779d77c2e004b44d43f392b3ed
--- /dev/null
+++ b/cicd/stages/test.yml
@@ -0,0 +1,31 @@
+# todo: run the tests for all architectures
+test-x86_64:
+  stage: test
+  extends: .pipeline_common
+  tags:
+    - qds-blade-server-shell
+  needs:
+    - job: build-android
+      optional: false
+      artifacts: true
+  variables:
+    GIT_SUBMODULE_STRATEGY: none
+    QDS_CI_JOB_TEST_RESULTS_PATH: ${CI_PROJECT_DIR}/test
+  script:
+    - |
+      export ANDROID_SDK_ROOT=/opt/android
+      export PATH=$PATH:${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/
+      export PATH=$PATH:${ANDROID_SDK_ROOT}/emulator
+      export PATH=$PATH:${ANDROID_SDK_ROOT}/platform-tools
+    - cd ${QDS_CI_ARTIFACTS_PATH}/android/x86_64/test || exit 1
+    - mkdir -p ${QDS_CI_JOB_TEST_RESULTS_PATH}
+    - adb install -r -g android-build-release.apk
+    - adb shell am start -e applicationArguments "'-o output.txt,txt -o output.xml,xml -o output.junitxml,junitxml'" -n io.qt.qtuiviewer.test/org.qtproject.qt.android.bindings.QtActivity
+    - adb shell "run-as io.qt.qtuiviewer.test cat /data/data/io.qt.qtuiviewer.test/files/output.junitxml" > output.junit.xml
+    - mv output.junit.xml ${QDS_CI_JOB_TEST_RESULTS_PATH}/test.junit.xml
+  artifacts:
+    paths:
+      - ${QDS_CI_JOB_TEST_RESULTS_PATH}/
+    reports:
+      junit: ${QDS_CI_JOB_TEST_RESULTS_PATH}/test.junit.xml
+    expire_in: 1 week
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cef94e9fafd108e973ea3b8d482261dccb6c9981
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,39 @@
+find_package(
+    Qt6
+    COMPONENTS Core Widgets Quick Gui Qml Multimedia MultimediaWidgets Concurrent
+    REQUIRED
+)
+
+qt_add_executable(${PROJECT_NAME}
+    backend/importdummy.qml
+    backend/main.cpp
+    backend/backend.cpp backend/backend.h
+    backend/serviceconnector.cpp backend/serviceconnector.h
+    backend/projectmanager.cpp backend/projectmanager.h
+    backend/dsconnector.cpp backend/dsconnector.h
+    backend/qrscanner.cpp backend/qrscanner.h
+    ui/main.qml
+    ui/resources.qrc
+    ../3rdparty/zxing-cpp/example/ZXingQtReader.h
+)
+
+target_link_libraries(${PROJECT_NAME} PRIVATE
+    Qt6::Core Qt6::Widgets
+    Qt6::Quick Qt6::Gui
+    Qt6::Qml Qt6::GuiPrivate
+    Qt6::Multimedia Qt6::MultimediaWidgets
+    Qt6::Concurrent
+    ZXing::ZXing
+)
+
+set_property(TARGET ${PROJECT_NAME}
+    APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
+)
+
+set_property(TARGET ${PROJECT_NAME} PROPERTY QT_ANDROID_EXTRA_LIBS
+    ${ANDROID_OPENSSL_PATH}/libcrypto_3.so
+    ${ANDROID_OPENSSL_PATH}/libssl_3.so
+)
+
+set_property(TARGET ${PROJECT_NAME} PROPERTY QT_ANDROID_TARGET_SDK_VERSION 34)
+qt6_import_qml_plugins(${PROJECT_NAME})
diff --git a/android/AndroidManifest.xml b/src/android/AndroidManifest.xml
similarity index 80%
rename from android/AndroidManifest.xml
rename to src/android/AndroidManifest.xml
index f8ea2fae91df6a8b138ff4da7921b107ff1d304f..1b15bb4174af2fbc5bd062ce88ded49e6ed2a487 100644
--- a/android/AndroidManifest.xml
+++ b/src/android/AndroidManifest.xml
@@ -1,14 +1,15 @@
 <?xml version="1.0"?>
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtdesignviewer"
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtuiviewer"
     android:installLocation="auto" android:versionCode="27" android:versionName="1.2">
     <!-- %%INSERT_PERMISSIONS -->
     <!-- %%INSERT_FEATURES -->
     <supports-screens android:anyDensity="true" android:largeScreens="true"
         android:normalScreens="true" android:smallScreens="true" />
-    <application android:name="org.qtproject.qt.android.bindings.QtApplication"
+    <application android:icon="@mipmap/app_icon"
+        android:name="org.qtproject.qt.android.bindings.QtApplication"
         android:extractNativeLibs="true" android:hardwareAccelerated="true"
         android:label="Qt UI Viewer" android:requestLegacyExternalStorage="true"
-        android:allowNativeHeapPointerTagging="false" android:icon="@mipmap/app_icon">
+        android:allowNativeHeapPointerTagging="false">
         <profileable android:shell="true" android:enabled="true" />
         <activity android:name="org.qtproject.qt.android.bindings.QtActivity"
             android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
@@ -30,9 +31,11 @@
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:host="designviewer.qt.io" android:scheme="https" />
             </intent-filter>
-            <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
-            <meta-data android:name="android.app.arguments" android:value="-- %%INSERT_APP_ARGUMENTS%% --"/>
-            <meta-data android:name="android.app.extract_android_style" android:value="minimal"/>
+            <meta-data android:name="android.app.lib_name"
+                android:value="-- %%INSERT_APP_LIB_NAME%% --" />
+            <meta-data android:name="android.app.arguments"
+                android:value="-- %%INSERT_APP_ARGUMENTS%% --" />
+            <meta-data android:name="android.app.extract_android_style" android:value="minimal" />
         </activity>
     </application>
 </manifest>
diff --git a/android/res/mipmap-hdpi/app_icon.png b/src/android/res/mipmap-hdpi/app_icon.png
similarity index 100%
rename from android/res/mipmap-hdpi/app_icon.png
rename to src/android/res/mipmap-hdpi/app_icon.png
diff --git a/android/res/mipmap-ldpi/app_icon.png b/src/android/res/mipmap-ldpi/app_icon.png
similarity index 100%
rename from android/res/mipmap-ldpi/app_icon.png
rename to src/android/res/mipmap-ldpi/app_icon.png
diff --git a/android/res/mipmap-mdpi/app_icon.png b/src/android/res/mipmap-mdpi/app_icon.png
similarity index 100%
rename from android/res/mipmap-mdpi/app_icon.png
rename to src/android/res/mipmap-mdpi/app_icon.png
diff --git a/android/res/mipmap-xhdpi/app_icon.png b/src/android/res/mipmap-xhdpi/app_icon.png
similarity index 100%
rename from android/res/mipmap-xhdpi/app_icon.png
rename to src/android/res/mipmap-xhdpi/app_icon.png
diff --git a/android/res/mipmap-xxhdpi/app_icon.png b/src/android/res/mipmap-xxhdpi/app_icon.png
similarity index 100%
rename from android/res/mipmap-xxhdpi/app_icon.png
rename to src/android/res/mipmap-xxhdpi/app_icon.png
diff --git a/android/res/mipmap-xxxhdpi/app_icon.png b/src/android/res/mipmap-xxxhdpi/app_icon.png
similarity index 100%
rename from android/res/mipmap-xxxhdpi/app_icon.png
rename to src/android/res/mipmap-xxxhdpi/app_icon.png
diff --git a/src/backend.cpp b/src/backend/backend.cpp
similarity index 98%
rename from src/backend.cpp
rename to src/backend/backend.cpp
index e88b71effaa82efea497687c08b59b92f0970a61..35afbe21ab5161e273a71385b722c3ac3a160c76 100644
--- a/src/backend.cpp
+++ b/src/backend/backend.cpp
@@ -63,8 +63,10 @@ Backend::Backend(QObject *parent)
     const QString buildType = "Release";
 #endif
     m_buildInfo = QCoreApplication::applicationVersion() + "\nTechnology Preview - "
-                  + QString(GIT_VERSION) + "\nQt " + QString(QT_VERSION_STR) + " - " + buildType
-                  + " Build" + "\nQt Quick Components " + QString(QT_QUICK_COMPONENTS_VERSION)
+                  + QString(CMAKE_VAR_GIT_VERSION) + "\nQt " + QString(QT_VERSION_STR) + " - "
+                  + buildType + " Build" + "\nQt Quick Components "
+                  + QString(CMAKE_VAR_QT_QUICK_COMPONENTS_VERSION)
+                  + "\nZXing-Cpp: " + QString(CMAKE_VAR_ZXING_VERSION)
                   + "\nOpenSSL support: " + QVariant(QSslSocket::supportsSsl()).toString();
 
     const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
diff --git a/src/backend.h b/src/backend/backend.h
similarity index 100%
rename from src/backend.h
rename to src/backend/backend.h
diff --git a/src/dsconnector.cpp b/src/backend/dsconnector.cpp
similarity index 100%
rename from src/dsconnector.cpp
rename to src/backend/dsconnector.cpp
diff --git a/src/dsconnector.h b/src/backend/dsconnector.h
similarity index 100%
rename from src/dsconnector.h
rename to src/backend/dsconnector.h
diff --git a/src/importdummy.qml b/src/backend/importdummy.qml
similarity index 100%
rename from src/importdummy.qml
rename to src/backend/importdummy.qml
diff --git a/src/main.cpp b/src/backend/main.cpp
similarity index 100%
rename from src/main.cpp
rename to src/backend/main.cpp
diff --git a/src/projectmanager.cpp b/src/backend/projectmanager.cpp
similarity index 100%
rename from src/projectmanager.cpp
rename to src/backend/projectmanager.cpp
diff --git a/src/projectmanager.h b/src/backend/projectmanager.h
similarity index 100%
rename from src/projectmanager.h
rename to src/backend/projectmanager.h
diff --git a/src/qrscanner.cpp b/src/backend/qrscanner.cpp
similarity index 100%
rename from src/qrscanner.cpp
rename to src/backend/qrscanner.cpp
diff --git a/src/qrscanner.h b/src/backend/qrscanner.h
similarity index 100%
rename from src/qrscanner.h
rename to src/backend/qrscanner.h
diff --git a/src/serviceconnector.cpp b/src/backend/serviceconnector.cpp
similarity index 100%
rename from src/serviceconnector.cpp
rename to src/backend/serviceconnector.cpp
diff --git a/src/serviceconnector.h b/src/backend/serviceconnector.h
similarity index 100%
rename from src/serviceconnector.h
rename to src/backend/serviceconnector.h
diff --git a/ui/AboutHeader.qml b/src/ui/AboutHeader.qml
similarity index 100%
rename from ui/AboutHeader.qml
rename to src/ui/AboutHeader.qml
diff --git a/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
similarity index 100%
rename from ui/CMakeLists.txt
rename to src/ui/CMakeLists.txt
diff --git a/ui/DesignViewer.qmlproject b/src/ui/DesignViewer.qmlproject
similarity index 100%
rename from ui/DesignViewer.qmlproject
rename to src/ui/DesignViewer.qmlproject
diff --git a/src/ui/DesignViewer.qmlproject.qtds b/src/ui/DesignViewer.qmlproject.qtds
new file mode 100644
index 0000000000000000000000000000000000000000..55733bad9e6f2a2851edf2c2ecaf7cc623d01631
--- /dev/null
+++ b/src/ui/DesignViewer.qmlproject.qtds
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE QtCreatorProject>
+<!-- Written by QtDesignStudio 4.3.2, 2024-02-26T09:55:47. -->
+<qtcreator>
+ <data>
+  <variable>EnvironmentId</variable>
+  <value type="QByteArray">{ac7fba27-a706-4163-bcb3-981f191b8f80}</value>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.ActiveTarget</variable>
+  <value type="qlonglong">1</value>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.EditorSettings</variable>
+  <valuemap type="QVariantMap">
+   <value type="bool" key="EditorConfiguration.AutoIndent">true</value>
+   <value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
+   <value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
+   <valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
+    <value type="QString" key="language">Cpp</value>
+    <valuemap type="QVariantMap" key="value">
+     <value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
+    </valuemap>
+   </valuemap>
+   <valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
+    <value type="QString" key="language">QmlJS</value>
+    <valuemap type="QVariantMap" key="value">
+     <value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
+    </valuemap>
+   </valuemap>
+   <value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
+   <value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
+   <value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
+   <value type="int" key="EditorConfiguration.IndentSize">4</value>
+   <value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
+   <value type="int" key="EditorConfiguration.MarginColumn">80</value>
+   <value type="bool" key="EditorConfiguration.MouseHiding">true</value>
+   <value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
+   <value type="int" key="EditorConfiguration.PaddingMode">1</value>
+   <value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
+   <value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
+   <value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
+   <value type="bool" key="EditorConfiguration.ShowMargin">false</value>
+   <value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
+   <value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
+   <value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
+   <value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
+   <value type="int" key="EditorConfiguration.TabSize">8</value>
+   <value type="bool" key="EditorConfiguration.UseGlobal">true</value>
+   <value type="bool" key="EditorConfiguration.UseIndenter">false</value>
+   <value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
+   <value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
+   <value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
+   <value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
+   <value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
+   <value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
+   <value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
+   <value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
+  </valuemap>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.Target.0</variable>
+  <valuemap type="QVariantMap">
+   <value type="QString" key="DeviceType">Desktop</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Replacement for &quot;Desktop Qt 5.15.5&quot;</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Replacement for &quot;Desktop Qt 5.15.5&quot;</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{8994bd34-5ed9-4c45-8c0a-94c8f33eca4a}</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">-1</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">0</value>
+   <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
+    <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
+     <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
+    </valuemap>
+    <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
+    <valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
+    <value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
+   </valuemap>
+   <value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
+   <valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
+    <value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
+    <valuelist type="QVariantList" key="CustomOutputParsers"/>
+    <value type="int" key="PE.EnvironmentAspect.Base">0</value>
+    <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
+    <value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">QML Runtime</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QmlProjectManager.QmlRunConfiguration.Qml</value>
+    <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
+    <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
+    <value type="QString" key="QmlProjectManager.QmlRunConfiguration.LastUsedLanguage">en</value>
+    <value type="QString" key="QmlProjectManager.QmlRunConfiguration.MainScript">CurrentFile</value>
+    <value type="bool" key="QmlProjectManager.QmlRunConfiguration.UseMultiLanguage">true</value>
+    <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
+    <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
+    <value type="QString" key="RunConfiguration.X11Forwarding">/private/tmp/com.apple.launchd.ddnxVkgmc6/org.xquartz:0</value>
+   </valuemap>
+   <value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
+  </valuemap>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.Target.1</variable>
+  <valuemap type="QVariantMap">
+   <value type="QString" key="DeviceType">Desktop</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 6.6.0</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 6.6.0</value>
+   <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{63f87550-2541-4163-9631-08b7fea781da}</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">-1</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
+   <value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">0</value>
+   <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
+    <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
+     <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
+     <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
+    </valuemap>
+    <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
+    <valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
+    <value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
+   </valuemap>
+   <value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
+   <valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
+    <value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
+    <valuelist type="QVariantList" key="CustomOutputParsers"/>
+    <value type="int" key="PE.EnvironmentAspect.Base">0</value>
+    <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
+    <value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">QML Runtime</value>
+    <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QmlProjectManager.QmlRunConfiguration.Qml</value>
+    <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
+    <value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
+    <value type="QString" key="QmlProjectManager.QmlRunConfiguration.LastUsedLanguage">en</value>
+    <value type="QString" key="QmlProjectManager.QmlRunConfiguration.MainScript">CurrentFile</value>
+    <value type="bool" key="QmlProjectManager.QmlRunConfiguration.UseMultiLanguage">true</value>
+    <value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
+    <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
+    <value type="QString" key="RunConfiguration.X11Forwarding">/private/tmp/com.apple.launchd.ddnxVkgmc6/org.xquartz:0</value>
+   </valuemap>
+   <value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
+  </valuemap>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.TargetCount</variable>
+  <value type="qlonglong">2</value>
+ </data>
+ <data>
+  <variable>ProjectExplorer.Project.Updater.FileVersion</variable>
+  <value type="int">22</value>
+ </data>
+ <data>
+  <variable>Version</variable>
+  <value type="int">22</value>
+ </data>
+</qtcreator>
diff --git a/ui/ExamplesPage.qml b/src/ui/ExamplesPage.qml
similarity index 100%
rename from ui/ExamplesPage.qml
rename to src/ui/ExamplesPage.qml
diff --git a/ui/HomePage.qml b/src/ui/HomePage.qml
similarity index 100%
rename from ui/HomePage.qml
rename to src/ui/HomePage.qml
diff --git a/ui/Logs.qml b/src/ui/Logs.qml
similarity index 100%
rename from ui/Logs.qml
rename to src/ui/Logs.qml
diff --git a/ui/Network.qml b/src/ui/Network.qml
similarity index 100%
rename from ui/Network.qml
rename to src/ui/Network.qml
diff --git a/ui/SettingsPage.qml b/src/ui/SettingsPage.qml
similarity index 100%
rename from ui/SettingsPage.qml
rename to src/ui/SettingsPage.qml
diff --git a/ui/asset_imports/asset_imports.txt b/src/ui/asset_imports/asset_imports.txt
similarity index 100%
rename from ui/asset_imports/asset_imports.txt
rename to src/ui/asset_imports/asset_imports.txt
diff --git a/ui/content/CMakeLists.txt b/src/ui/content/CMakeLists.txt
similarity index 100%
rename from ui/content/CMakeLists.txt
rename to src/ui/content/CMakeLists.txt
diff --git a/ui/content/fonts/fonts.txt b/src/ui/content/fonts/fonts.txt
similarity index 100%
rename from ui/content/fonts/fonts.txt
rename to src/ui/content/fonts/fonts.txt
diff --git a/ui/content/images/appicon.png b/src/ui/content/images/appicon.png
similarity index 100%
rename from ui/content/images/appicon.png
rename to src/ui/content/images/appicon.png
diff --git a/ui/content/images/closed_eye.png b/src/ui/content/images/closed_eye.png
similarity index 100%
rename from ui/content/images/closed_eye.png
rename to src/ui/content/images/closed_eye.png
diff --git a/ui/content/images/open_eye.png b/src/ui/content/images/open_eye.png
similarity index 100%
rename from ui/content/images/open_eye.png
rename to src/ui/content/images/open_eye.png
diff --git a/ui/i18n/qml_en.qm b/src/ui/i18n/qml_en.qm
similarity index 100%
rename from ui/i18n/qml_en.qm
rename to src/ui/i18n/qml_en.qm
diff --git a/ui/imports/CMakeLists.txt b/src/ui/imports/CMakeLists.txt
similarity index 100%
rename from ui/imports/CMakeLists.txt
rename to src/ui/imports/CMakeLists.txt
diff --git a/ui/imports/DesignViewer/CMakeLists.txt b/src/ui/imports/DesignViewer/CMakeLists.txt
similarity index 100%
rename from ui/imports/DesignViewer/CMakeLists.txt
rename to src/ui/imports/DesignViewer/CMakeLists.txt
diff --git a/ui/imports/DesignViewer/Constants.qml b/src/ui/imports/DesignViewer/Constants.qml
similarity index 100%
rename from ui/imports/DesignViewer/Constants.qml
rename to src/ui/imports/DesignViewer/Constants.qml
diff --git a/ui/imports/DesignViewer/DirectoryFontLoader.qml b/src/ui/imports/DesignViewer/DirectoryFontLoader.qml
similarity index 100%
rename from ui/imports/DesignViewer/DirectoryFontLoader.qml
rename to src/ui/imports/DesignViewer/DirectoryFontLoader.qml
diff --git a/ui/imports/DesignViewer/EventListModel.qml b/src/ui/imports/DesignViewer/EventListModel.qml
similarity index 100%
rename from ui/imports/DesignViewer/EventListModel.qml
rename to src/ui/imports/DesignViewer/EventListModel.qml
diff --git a/ui/imports/DesignViewer/EventListSimulator.qml b/src/ui/imports/DesignViewer/EventListSimulator.qml
similarity index 100%
rename from ui/imports/DesignViewer/EventListSimulator.qml
rename to src/ui/imports/DesignViewer/EventListSimulator.qml
diff --git a/ui/imports/DesignViewer/designer/plugin.metainfo b/src/ui/imports/DesignViewer/designer/plugin.metainfo
similarity index 100%
rename from ui/imports/DesignViewer/designer/plugin.metainfo
rename to src/ui/imports/DesignViewer/designer/plugin.metainfo
diff --git a/ui/imports/DesignViewer/qmldir b/src/ui/imports/DesignViewer/qmldir
similarity index 100%
rename from ui/imports/DesignViewer/qmldir
rename to src/ui/imports/DesignViewer/qmldir
diff --git a/ui/insight b/src/ui/insight
similarity index 100%
rename from ui/insight
rename to src/ui/insight
diff --git a/ui/main.qml b/src/ui/main.qml
similarity index 100%
rename from ui/main.qml
rename to src/ui/main.qml
diff --git a/ui/qmlcomponents b/src/ui/qmlcomponents
similarity index 100%
rename from ui/qmlcomponents
rename to src/ui/qmlcomponents
diff --git a/ui/qmlmodules b/src/ui/qmlmodules
similarity index 100%
rename from ui/qmlmodules
rename to src/ui/qmlmodules
diff --git a/ui/qtquickcontrols2.conf b/src/ui/qtquickcontrols2.conf
similarity index 100%
rename from ui/qtquickcontrols2.conf
rename to src/ui/qtquickcontrols2.conf
diff --git a/ui/resources.qrc b/src/ui/resources.qrc
similarity index 100%
rename from ui/resources.qrc
rename to src/ui/resources.qrc
diff --git a/ui/src/app_environment.h b/src/ui/src/app_environment.h
similarity index 100%
rename from ui/src/app_environment.h
rename to src/ui/src/app_environment.h
diff --git a/ui/src/import_qml_components_plugins.h b/src/ui/src/import_qml_components_plugins.h
similarity index 100%
rename from ui/src/import_qml_components_plugins.h
rename to src/ui/src/import_qml_components_plugins.h
diff --git a/ui/src/import_qml_plugins.h b/src/ui/src/import_qml_plugins.h
similarity index 100%
rename from ui/src/import_qml_plugins.h
rename to src/ui/src/import_qml_plugins.h
diff --git a/ui/src/main.cpp b/src/ui/src/main.cpp
similarity index 100%
rename from ui/src/main.cpp
rename to src/ui/src/main.cpp
diff --git a/src/ui/translations.db b/src/ui/translations.db
new file mode 100644
index 0000000000000000000000000000000000000000..c8306d07379f4b62c1a2ff52a3c57553e68bd10e
Binary files /dev/null and b/src/ui/translations.db differ
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0733caf57be2f2a29a9df7c7fa26f24b1e7f34bc
--- /dev/null
+++ b/tests/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(TARGET_NAME ${PROJECT_NAME}_test)
+
+find_package(Qt6 REQUIRED COMPONENTS Test Core Quick Gui)
+
+enable_testing(true)
+qt_add_executable(${TARGET_NAME}
+    unit/tst_qtuiviewer.cpp
+    unit/tst_qtuiviewer.h
+)
+
+add_test(NAME ${TARGET_NAME} COMMAND qtuiviewer_test)
+target_link_libraries(${TARGET_NAME} PRIVATE Qt::Test Qt::Core Qt::Quick Qt::Gui)
+
+set_property(TARGET ${TARGET_NAME}
+    APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
+)
diff --git a/tests/android/AndroidManifest.xml b/tests/android/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e3ad0a2c7b8c5670e4502a84c2438cd9cc7508a2
--- /dev/null
+++ b/tests/android/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.qt.qtuiviewer.test"
+    android:installLocation="auto" android:versionCode="1" android:versionName="1">
+    <!-- %%INSERT_PERMISSIONS -->
+    <!-- %%INSERT_FEATURES -->
+    <supports-screens android:anyDensity="true" android:largeScreens="true"
+        android:normalScreens="true" android:smallScreens="true" />
+    <application android:icon="@mipmap/app_icon" android:debuggable="true"
+        android:name="org.qtproject.qt.android.bindings.QtApplication"
+        android:extractNativeLibs="true"
+        android:hardwareAccelerated="true" android:label="Qt UI Viewer Test"
+        android:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false">
+        <profileable android:shell="true" android:enabled="true" />
+        <activity android:name="org.qtproject.qt.android.bindings.QtActivity"
+            android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
+            android:label="Qt UI Viewer Test" android:launchMode="singleTask"
+            android:screenOrientation="unspecified" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.app.lib_name"
+                android:value="-- %%INSERT_APP_LIB_NAME%% --" />
+            <meta-data android:name="android.app.arguments"
+                android:value="-- %%INSERT_APP_ARGUMENTS%% --" />
+            <meta-data android:name="android.app.extract_android_style" android:value="minimal" />
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/android/res/mipmap-hdpi/app_icon.png b/tests/android/res/mipmap-hdpi/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..74a188d073456264ab840da9314a48447fc95fcf
Binary files /dev/null and b/tests/android/res/mipmap-hdpi/app_icon.png differ
diff --git a/tests/android/res/mipmap-ldpi/app_icon.png b/tests/android/res/mipmap-ldpi/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..a52520e2630446955333139ca069431c38b75376
Binary files /dev/null and b/tests/android/res/mipmap-ldpi/app_icon.png differ
diff --git a/tests/android/res/mipmap-mdpi/app_icon.png b/tests/android/res/mipmap-mdpi/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..36a356c0cec907702d1ecd5562586ca8ee72325c
Binary files /dev/null and b/tests/android/res/mipmap-mdpi/app_icon.png differ
diff --git a/tests/android/res/mipmap-xhdpi/app_icon.png b/tests/android/res/mipmap-xhdpi/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..e1f2e9dc1f2653cdb78f3392df37664615f811aa
Binary files /dev/null and b/tests/android/res/mipmap-xhdpi/app_icon.png differ
diff --git a/tests/android/res/mipmap-xxhdpi/app_icon.png b/tests/android/res/mipmap-xxhdpi/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..652a8e9b7e4e7ebb31424805025b1a2bce8e708d
Binary files /dev/null and b/tests/android/res/mipmap-xxhdpi/app_icon.png differ
diff --git a/tests/android/res/mipmap-xxxhdpi/app_icon.png b/tests/android/res/mipmap-xxxhdpi/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..7a404f6cbb8e0acc99545092319bea31c384ecca
Binary files /dev/null and b/tests/android/res/mipmap-xxxhdpi/app_icon.png differ
diff --git a/tests/unit/tst_qtuiviewer.cpp b/tests/unit/tst_qtuiviewer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bf2aff2dae2ec9418e0ccbcb651177d4f0438cc2
--- /dev/null
+++ b/tests/unit/tst_qtuiviewer.cpp
@@ -0,0 +1,17 @@
+#include "tst_qtuiviewer.h"
+
+void TestQString::toUpper()
+{
+    qDebug("TestQString::toUpper");
+    QString str = "Hello";
+    QVERIFY(str.toUpper() == "HELLO");
+}
+
+void TestQString::toLower()
+{
+    qDebug("TestQString::toLower");
+    QString str = "Hello";
+    QCOMPARE(str.toLower(), "hell");
+}
+
+QTEST_MAIN(TestQString)
diff --git a/tests/unit/tst_qtuiviewer.h b/tests/unit/tst_qtuiviewer.h
new file mode 100644
index 0000000000000000000000000000000000000000..cffffcf03f4442c003bf1c86e5ca38b9176fcce7
--- /dev/null
+++ b/tests/unit/tst_qtuiviewer.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <QtTest/QTest>
+
+class TestQString : public QObject
+{
+    Q_OBJECT
+private slots:
+    void toUpper();
+    void toLower();
+};