From f50cf443f210339bc5004ce6d89c29195aae2d34 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 12:09:58 +0100 Subject: [PATCH 001/174] Create generateGoogleServicesJson.py load google-services.json file from environment variables --- scripts/generateGoogleServicesJson.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 scripts/generateGoogleServicesJson.py diff --git a/scripts/generateGoogleServicesJson.py b/scripts/generateGoogleServicesJson.py new file mode 100644 index 00000000..3cd613ce --- /dev/null +++ b/scripts/generateGoogleServicesJson.py @@ -0,0 +1,9 @@ +import os + +outputPath = "app/google-services.json" + +fileContent = os.getenv('GOOGLE_SERVICES_JSON') + +fhand = open(outputPath, 'w') +fhand.write(str(fileContent)) +fhand.close() \ No newline at end of file From e4dfe2054cf996d9aaeeb695ec4cfe37ae28392c Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 12:10:24 +0100 Subject: [PATCH 002/174] Update build.gradle update AGP version; add jacoco_version to ext --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 462fec47..2e395f32 100644 --- a/build.gradle +++ b/build.gradle @@ -5,13 +5,14 @@ buildscript { kotlin_version = '1.7.10' hilt_version = '2.44' firebase_bom_version = '30.5.0' + jacoco_version = '0.8.8' } repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.0-rc01' + classpath 'com.android.tools.build:gradle:7.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath 'com.google.gms:google-services:4.3.14' From 798cf4c9ac9b1d2d3f19b758bdffcfdd62b2cedf Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 12:12:58 +0100 Subject: [PATCH 003/174] Update build.gradle - activate test coverage for debug builds - add gradle managed device - automated test device (api-30; google_atd) - apply jacoco and require xml and html reports - merge unit and instrumented test reports and export result under `/mergedReportDir` in build directory --- app/build.gradle | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 01e6fc54..d8e1c896 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,6 +5,7 @@ plugins { id 'org.jetbrains.kotlin.android' id 'kotlin-kapt' id 'dagger.hilt.android.plugin' + id "jacoco" } repositories { @@ -46,12 +47,54 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } + debug { + testCoverageEnabled = true + } + } + + testOptions { + + animationsDisabled = true + + managedDevices { + devices { + pixel2api30 (com.android.build.api.dsl.ManagedVirtualDevice) { + device = "Pixel 2" + apiLevel = 30 + systemImageSource = "google_atd" + } + } + } } namespace 'org.kabiri.android.usbterminal' } +jacoco { + toolVersion = jacoco_version + reportsDirectory.set(layout.buildDirectory.dir("mergedReportDir")) +} + +task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'pixel2api30DebugAndroidTest']) { + reports { + xml.required.set(true) + html.required.set(true) + csv.required.set(false) + } + + def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] + def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter) + def mainSrc = "${project.projectDir}/src/main/kotlin" + + sourceDirectories.from(files([mainSrc])) + classDirectories.from(files([debugTree])) + executionData.from(fileTree(dir: "$buildDir", includes: [ + "outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec", + "outputs/managed_device_code_coverage/pixel2api30/coverage.ec" + ])) +} + private void loadKeyStore(Properties ksProp) { def ksPropFile = file("keystore.properties") if (ksPropFile.exists()) { @@ -111,4 +154,4 @@ dependencies { * For more information, visit: https://github.com/felHR85/UsbSerial */ implementation 'com.github.felHR85:UsbSerial:6.1.0' -} +} \ No newline at end of file From 8cb90517ad084f6cae95a40e98f1f47b35e5824b Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 12:17:16 +0100 Subject: [PATCH 004/174] Update config.yml - use circleci android machine executor to run the tests - generate ksPropFile and google-services.json - download emulator image for android emulator (google automated test device) - accept sdkmanager licenses after download - run the instrumented tests using gradle managed device `system-images;android-30;google_atd;x86` with coverage - run unit tests with coverage - store test results in the artifacts directory of circleci --- .circleci/config.yml | 81 ++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 02d75bf9..b9249f1c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,57 +1,48 @@ version: 2.1 -jobs: - android-test: - macos: - xcode: "11.2.0" - working_directory: ~/repo/App +commands: + restore_gradle_cache: steps: - - checkout: - path: ~/repo - - - run: - name: set ANDROID_SDK_ROOT - command: | - echo 'export ANDROID_SDK_ROOT=$HOME/android-tools' >> $BASH_ENV - - restore_cache: - key: android=tools-v1-{{ checksum "scripts/install-android-tools.sh" }}-{{ arch }} - - - run: - name: install android tools - command: | - sh scripts/install-android-tools.sh - echo 'export PATH=$ANDROID_SDK_ROOT/tools/bin:$PATH' >> $BASH_ENV - echo 'export PATH=$ANDROID_SDK_ROOT/tools:$PATH' >> $BASH_ENV - echo 'export PATH=$ANDROID_SDK_ROOT/platform-tools:$PATH' >> $BASH_ENV - echo 'export PATH=$ANDROID_SDK_ROOT/emulator:$PATH' >> $BASH_ENV - source $BASH_ENV - sdkmanager --list + key: v1-gradle-wrapper-{{ arch }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} + - restore_cache: + key: v1-gradle-cache-{{ arch }}-{{ checksum "build.gradle" }} + save_gradle_cache: + steps: - save_cache: - key: android=tools-v1-{{ checksum "scripts/install-android-tools.sh" }}-{{ arch }} paths: - - /Users/distiller/android-tools - - - run: - name: create AVD - command: make create-avd - - - run: - name: start AVD - command: emulator-headless -avd android-tablet - background: true - - - run: - name: wait for emulator - command: adb wait-for-device shell 'while [[ -z $(getprop dev.bootcomplete) ]]; do sleep 1; done;' - - - run: adb shell screencap -p > screenshots/before.png - - # (insert testing here) + - ~/.gradle/wrapper + key: v1-gradle-wrapper-{{ arch }}-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} + - save_cache: + paths: + - ~/.gradle/caches + key: v1-gradle-cache-{{ arch }}-{{ checksum "build.gradle" }} +jobs: + android-test: + machine: + image: android:2022.12.1 + resource_class: large + steps: + - run: lsb_release -a + - checkout + - run: python3 scripts/generateKsPropFile.py + - run: python3 scripts/generateGoogleServicesJson.py + - restore_gradle_cache + - run: ./gradlew tasks + - save_gradle_cache + - run: sdkmanager --list + - run: (yes || true) | sdkmanager "tools" "platform-tools" "build-tools;33.0.2" "platforms;android-33" "system-images;android-30;google_atd;x86" + - run: (yes || true) | sdkmanager --licenses + - run: ./gradlew assembleDebug --stacktrace + - run: ./gradlew pixel2api30DebugAndroidTest --stacktrace + - run: ./gradlew testDebugUnitTest --stacktrace + - run: ./gradlew jacocoTestReport --stacktrace - store_artifacts: - path: screenshots + path: app/build/mergedReportDir + - store_test_results: + path: app/build/test-results/testDebugUnitTest workflows: workflow: From 9503e9ea6d8a84be08e3236e89ade592e21fde1e Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 12:33:35 +0100 Subject: [PATCH 005/174] use codecov orb to upload jacoco coverage file --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index b9249f1c..64864519 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,4 +1,6 @@ version: 2.1 +orbs: + codecov: codecov/codecov@3.2.4 commands: restore_gradle_cache: @@ -39,6 +41,8 @@ jobs: - run: ./gradlew pixel2api30DebugAndroidTest --stacktrace - run: ./gradlew testDebugUnitTest --stacktrace - run: ./gradlew jacocoTestReport --stacktrace + - codecov/upload: + file: app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml - store_artifacts: path: app/build/mergedReportDir - store_test_results: From b01c46a159ab27716bc2a0f2bf07f68ed9d63ddd Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 12:39:00 +0100 Subject: [PATCH 006/174] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 64864519..0e3fe664 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,4 +51,4 @@ jobs: workflows: workflow: jobs: - - android-test \ No newline at end of file + - android-test From 3d6e438102e4722d0165efcc2b0d2a5cec3573c4 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 12:46:13 +0100 Subject: [PATCH 007/174] Update config.yml --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0e3fe664..aca01a1e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,10 +43,10 @@ jobs: - run: ./gradlew jacocoTestReport --stacktrace - codecov/upload: file: app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml - - store_artifacts: - path: app/build/mergedReportDir - store_test_results: path: app/build/test-results/testDebugUnitTest + - store_artifacts: + path: app/build/mergedReportDir workflows: workflow: From 3debdf1263ac17b89fc42afe6e14b195036e6c10 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 13:06:21 +0100 Subject: [PATCH 008/174] Update README.md - add circleci and codecov badges --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c75c331a..e0102fa7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Terminal-like app to send commands to Arduino through USB ![Android CI](https://github.com/k4biri/arduino-usb-terminal/workflows/Android%20CI/badge.svg) - +[![superus8r](https://circleci.com/gh/superus8r/arduino-usb-terminal.svg?style=shield)](https://circleci.com/gh/superus8r/arduino-usb-terminal) +[![codecov](https://codecov.io/gh/superus8r/arduino-usb-terminal/branch/develop/graph/badge.svg?token=RYIUU345QG)](https://codecov.io/gh/superus8r/arduino-usb-terminal) This app simplifies testing your Arduino components that work with direct usb commands by giving you the ability to send custom commands and view the returned message from your Arduino device. From 890667facff5b3a83ae8febb577dd2f24dca746f Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sun, 19 Feb 2023 13:40:35 +0100 Subject: [PATCH 009/174] migrate build.gradle to build.gradle.kts --- app/build.gradle | 157 -------------------------------------- app/build.gradle.kts | 167 +++++++++++++++++++++++++++++++++++++++++ app/proguard-rules.pro | 2 +- 3 files changed, 168 insertions(+), 158 deletions(-) delete mode 100644 app/build.gradle create mode 100644 app/build.gradle.kts diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index d8e1c896..00000000 --- a/app/build.gradle +++ /dev/null @@ -1,157 +0,0 @@ -plugins { - id 'com.android.application' - id 'com.google.gms.google-services' - id 'com.google.firebase.crashlytics' - id 'org.jetbrains.kotlin.android' - id 'kotlin-kapt' - id 'dagger.hilt.android.plugin' - id "jacoco" -} - -repositories { -} - -android { - compileSdkVersion 33 - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - defaultConfig { - applicationId "org.kabiri.android.usbterminal" - minSdkVersion 23 - targetSdkVersion 33 - versionCode 13 - versionName "0.9.12" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - signingConfigs { - // read release credentials from keystore.properties file - def ksProp = new Properties() - // load keys inside the ksProp file - loadKeyStore(ksProp) - release { - keyAlias ksProp.getProperty("release.keyAlias") - keyPassword ksProp.getProperty("release.keyPassword") - storeFile file(ksProp.getProperty("release.file")) - storePassword ksProp.getProperty("release.storePassword") - } - } - - buildTypes { - release { - signingConfig signingConfigs.release - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - debug { - testCoverageEnabled = true - } - } - - testOptions { - - animationsDisabled = true - - managedDevices { - devices { - pixel2api30 (com.android.build.api.dsl.ManagedVirtualDevice) { - device = "Pixel 2" - apiLevel = 30 - systemImageSource = "google_atd" - } - } - } - } - - namespace 'org.kabiri.android.usbterminal' - -} - -jacoco { - toolVersion = jacoco_version - reportsDirectory.set(layout.buildDirectory.dir("mergedReportDir")) -} - -task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'pixel2api30DebugAndroidTest']) { - reports { - xml.required.set(true) - html.required.set(true) - csv.required.set(false) - } - - def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] - def debugTree = fileTree(dir: "${buildDir}/tmp/kotlin-classes/debug", excludes: fileFilter) - def mainSrc = "${project.projectDir}/src/main/kotlin" - - sourceDirectories.from(files([mainSrc])) - classDirectories.from(files([debugTree])) - executionData.from(fileTree(dir: "$buildDir", includes: [ - "outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec", - "outputs/managed_device_code_coverage/pixel2api30/coverage.ec" - ])) -} - -private void loadKeyStore(Properties ksProp) { - def ksPropFile = file("keystore.properties") - if (ksPropFile.exists()) { - ksProp.load(new FileInputStream(ksPropFile)) - } else { - println 'ERROR: local keystore file not found' - } -} - -dependencies { - - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.0' - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - - // Firebase - implementation platform("com.google.firebase:firebase-bom:$firebase_bom_version") - implementation 'com.google.firebase:firebase-analytics-ktx' - implementation 'com.google.firebase:firebase-crashlytics-ktx' - - // Dependency Injection - implementation "com.google.dagger:hilt-android:$hilt_version" - kapt "com.google.dagger:hilt-compiler:$hilt_version" - - // Coroutines - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' - implementation 'androidx.activity:activity-compose:1.6.1' - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" - implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4' - - // hilt testing - // more info: - // https://developer.android.com/training/dependency-injection/hilt-testing - androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" - kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_version" - - // unit test libs - testImplementation 'junit:junit:4.13.2' - testImplementation "com.google.truth:truth:1.1.3" - - // instrumented test libs - androidTestImplementation 'androidx.test:core:1.5.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5' - // Espresso - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' - // Hamcrest for view matching - androidTestImplementation 'org.hamcrest:hamcrest-library:2.2' - androidTestImplementation 'androidx.test:runner:1.5.2' - androidTestImplementation 'androidx.test:rules:1.5.0' - - /** - * This library helps to automate some parts of the USB serial connection. - * For more information, visit: https://github.com/felHR85/UsbSerial - */ - implementation 'com.github.felHR85:UsbSerial:6.1.0' -} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..23b09bd3 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,167 @@ +import java.io.FileInputStream +import java.util.* + +plugins { + id("com.android.application") + id("com.google.gms.google-services") + id("com.google.firebase.crashlytics") + id("org.jetbrains.kotlin.android") + id("kotlin-kapt") + id("dagger.hilt.android.plugin") + id("jacoco") +} + +repositories { +} + +android { + compileSdkVersion(33) + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + applicationId = "org.kabiri.android.usbterminal" + minSdkVersion(23) + targetSdkVersion(33) + versionCode = 13 + versionName = "0.9.12" + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + signingConfigs { + // read release credentials from keystore.properties file + val ksProp = Properties() + // load keys inside the ksProp file + loadKeyStore(ksProp) + create("release") { + keyAlias = ksProp.getProperty("release.keyAlias") + keyPassword = ksProp.getProperty("release.keyPassword") + storeFile = file(ksProp.getProperty("release.file")) + storePassword = ksProp.getProperty("release.storePassword") + } + } + + buildTypes { + named("release") { + signingConfig = signingConfigs.getByName("release") + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + named("debug") { + isTestCoverageEnabled = true + } + } + + testOptions { + + animationsDisabled = true + + managedDevices { + devices { + maybeCreate("pixel2api30").apply { + device = "Pixel 2" + apiLevel = 30 + systemImageSource = "google_atd" + } + } + } + } + + namespace = "org.kabiri.android.usbterminal" + +} + +jacoco { + val jacoco_version: String by project + toolVersion = jacoco_version + reportsDirectory.set(layout.buildDirectory.dir("mergedReportDir")) +} + +tasks.register("jacocoTestReport") { + + dependsOn("testDebugUnitTest") + dependsOn("pixel2api30DebugAndroidTest") + + reports { + xml.required.set(true) + html.required.set(true) + csv.required.set(false) + } + + val fileFilter = listOf("**/R.class", "**/R$*.class", "**/BuildConfig.*", "**/Manifest*.*", "**/*Test*.*", "android/**/*.*") + val debugTree = fileTree("${buildDir}/tmp/kotlin-classes/debug") { exclude(fileFilter) } + val mainSrc = "${project.projectDir}/src/main/kotlin" + + sourceDirectories.from(files(setOf(mainSrc))) + classDirectories.from(files(setOf(debugTree))) + executionData.from(fileTree(buildDir) { include(setOf( + "outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec", + "outputs/managed_device_code_coverage/pixel2api30/coverage.ec" + ))}) +} + +fun loadKeyStore(ksProp: Properties) { + val ksPropFile = file("keystore.properties") + if (ksPropFile.exists()) { + ksProp.load(FileInputStream(ksPropFile)) + } else { + println("ERROR: local keystore file not found") + } +} + +val firebase_bom_version: String by project +val hilt_version: String by project +dependencies { + +// implementation fileTree("libs") { include(setOf("*.jar")) } + implementation("androidx.appcompat:appcompat:1.6.0") + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + + // Firebase + implementation(platform("com.google.firebase:firebase-bom:$firebase_bom_version")) + implementation("com.google.firebase:firebase-analytics-ktx") + implementation("com.google.firebase:firebase-crashlytics-ktx") + + // Dependency Injection + implementation("com.google.dagger:hilt-android:$hilt_version") + kapt("com.google.dagger:hilt-compiler:$hilt_version") + + // Coroutines + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1") + implementation("androidx.activity:activity-compose:1.6.1") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") + implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") + + // hilt testing + // more info: + // https://developer.android.com/training/dependency-injection/hilt-testing + androidTestImplementation("com.google.dagger:hilt-android-testing:$hilt_version") + kaptAndroidTest("com.google.dagger:hilt-android-compiler:$hilt_version") + + // unit test libs + testImplementation("junit:junit:4.13.2") + testImplementation("com.google.truth:truth:1.1.3") + + // instrumented test libs + androidTestImplementation("androidx.test:core:1.5.0") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.ext:junit-ktx:1.1.5") + // Espresso + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + // Hamcrest for view matching + androidTestImplementation("org.hamcrest:hamcrest-library:2.2") + androidTestImplementation("androidx.test:runner:1.5.2") + androidTestImplementation("androidx.test:rules:1.5.0") + + /** + * This library helps to automate some parts of the USB serial connection. + * For more information, visit: https://github.com/felHR85/UsbSerial + */ + implementation("com.github.felHR85:UsbSerial:6.1.0") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f1b42451..2f9dc5a4 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html From 208a7d96421c5fa3c84f4b4be669fdbb4d240b3f Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 2 Mar 2023 08:48:36 +0100 Subject: [PATCH 010/174] apply sonar gradle plugin Signed-off-by: Ali Kabiri --- app/build.gradle.kts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 23b09bd3..dbc6dd0e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,6 +9,7 @@ plugins { id("kotlin-kapt") id("dagger.hilt.android.plugin") id("jacoco") + id ("org.sonarqube") version "3.5.0.2730" } repositories { @@ -103,6 +104,14 @@ tasks.register("jacocoTestReport") { ))}) } +sonarqube { + properties { + property("sonar.projectKey", System.getenv("SONAR_PROJECT_KEY")) + property("sonar.organization", System.getenv("SONAR_ORGANIZATION")) + property("sonar.host.url", System.getenv("SONAR_HOST_URL")) + } +} + fun loadKeyStore(ksProp: Properties) { val ksPropFile = file("keystore.properties") if (ksPropFile.exists()) { From 2af25c11a72b1395dfbf597a33dfac72b2822e9a Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 2 Mar 2023 08:55:35 +0100 Subject: [PATCH 011/174] set usesCleartextTraffic to false Signed-off-by: Ali Kabiri --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c87c749d..521743c2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,7 @@ Date: Thu, 2 Mar 2023 09:02:25 +0100 Subject: [PATCH 012/174] add a CircleCI step to analyze on SonarCloud Signed-off-by: Ali Kabiri --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index aca01a1e..8503a425 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,6 +41,9 @@ jobs: - run: ./gradlew pixel2api30DebugAndroidTest --stacktrace - run: ./gradlew testDebugUnitTest --stacktrace - run: ./gradlew jacocoTestReport --stacktrace + - run: + name: Analyze on SonarCloud + command: ./gradlew assembleDebug lint sonar - codecov/upload: file: app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml - store_test_results: From 7b0d13c19a00c6f96adc8f00b31fcda1ca6c309a Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 2 Mar 2023 09:16:19 +0100 Subject: [PATCH 013/174] apply SonarCloud context to build Signed-off-by: Ali Kabiri --- .circleci/config.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8503a425..cf0cac16 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -54,4 +54,5 @@ jobs: workflows: workflow: jobs: - - android-test + - android-test: + context: SonarCloud From 9484f1a006e9b281892da1b7191f2a42a91a769f Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 2 Mar 2023 09:27:22 +0100 Subject: [PATCH 014/174] use lintDebug instead of lint Signed-off-by: Ali Kabiri --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cf0cac16..966735b4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,7 +43,7 @@ jobs: - run: ./gradlew jacocoTestReport --stacktrace - run: name: Analyze on SonarCloud - command: ./gradlew assembleDebug lint sonar + command: ./gradlew assembleDebug lintDebug sonar - codecov/upload: file: app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml - store_test_results: From 9da237367542da94ab3c82e464d0dae9c8e5db0a Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 9 Mar 2023 16:58:53 +0100 Subject: [PATCH 015/174] Update config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 966735b4..904da6a1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -43,7 +43,7 @@ jobs: - run: ./gradlew jacocoTestReport --stacktrace - run: name: Analyze on SonarCloud - command: ./gradlew assembleDebug lintDebug sonar + command: ./gradlew lintDebug sonar - codecov/upload: file: app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml - store_test_results: From 8bc7e1c2432b5d8938a4b6c4982d85d7f2987f24 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 9 Mar 2023 16:59:11 +0100 Subject: [PATCH 016/174] cleanup; no changes --- app/build.gradle.kts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dbc6dd0e..075e4633 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,14 +9,13 @@ plugins { id("kotlin-kapt") id("dagger.hilt.android.plugin") id("jacoco") - id ("org.sonarqube") version "3.5.0.2730" } repositories { } android { - compileSdkVersion(33) + compileSdk = 33 compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 @@ -25,8 +24,8 @@ android { defaultConfig { applicationId = "org.kabiri.android.usbterminal" - minSdkVersion(23) - targetSdkVersion(33) + minSdk = 23 + targetSdk = 33 versionCode = 13 versionName = "0.9.12" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -125,7 +124,6 @@ val firebase_bom_version: String by project val hilt_version: String by project dependencies { -// implementation fileTree("libs") { include(setOf("*.jar")) } implementation("androidx.appcompat:appcompat:1.6.0") implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") From e22c184726728abf2a9744678697db34ad3e5c4c Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 9 Mar 2023 17:00:07 +0100 Subject: [PATCH 017/174] update structure; add sonarqube config --- build.gradle | 34 ++++++++++++++++------------------ settings.gradle | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/build.gradle b/build.gradle index 2e395f32..4b609460 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { ext { kotlin_version = '1.7.10' @@ -7,29 +5,29 @@ buildscript { firebase_bom_version = '30.5.0' jacoco_version = '0.8.8' } - repositories { - google() - mavenCentral() - } dependencies { - classpath 'com.android.tools.build:gradle:7.4.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath 'com.google.gms:google-services:4.3.14' - classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.2' - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath "com.google.gms:google-services:4.3.15" + classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.4" } } -allprojects { - repositories { - google() - mavenCentral() - maven { url "https://jitpack.io" } - } +plugins { + id("com.android.application") version "7.4.2" apply false + id("org.jetbrains.kotlin.android") version "1.7.0" apply false + id("org.sonarqube") version "3.5.0.2730" } task clean(type: Delete) { delete rootProject.buildDir } + +sonarqube { + properties { + property "sonar.organization", "superus8r" + property "sonar.projectKey", "superus8r_arduino-usb-terminal" + property "sonar.projectName", "arduino-usb-terminal" + property "sonar.sourceEncoding", "UTF-8" + property "sonar.host.url", "https://sonarcloud.io" + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 8a5a18d2..19a3c784 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,17 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url "https://jitpack.io" } + } +} rootProject.name='USBTerminal' -include ':app' +include ':app' \ No newline at end of file From ef84962254c94167aff53ac67d6373795f849aaa Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 9 Mar 2023 17:16:09 +0100 Subject: [PATCH 018/174] remove extra steps, save gradle cache after generating jacoco reports Signed-off-by: Ali Kabiri --- .circleci/config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 904da6a1..3999805a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -27,13 +27,10 @@ jobs: image: android:2022.12.1 resource_class: large steps: - - run: lsb_release -a - checkout - run: python3 scripts/generateKsPropFile.py - run: python3 scripts/generateGoogleServicesJson.py - restore_gradle_cache - - run: ./gradlew tasks - - save_gradle_cache - run: sdkmanager --list - run: (yes || true) | sdkmanager "tools" "platform-tools" "build-tools;33.0.2" "platforms;android-33" "system-images;android-30;google_atd;x86" - run: (yes || true) | sdkmanager --licenses @@ -41,6 +38,7 @@ jobs: - run: ./gradlew pixel2api30DebugAndroidTest --stacktrace - run: ./gradlew testDebugUnitTest --stacktrace - run: ./gradlew jacocoTestReport --stacktrace + - save_gradle_cache - run: name: Analyze on SonarCloud command: ./gradlew lintDebug sonar From bef16324c4c2f197f5a7f95300b917196b26b43c Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 9 Mar 2023 18:00:20 +0100 Subject: [PATCH 019/174] add info about testing using gradle managed devices, firebase crashlytics, and overview text Signed-off-by: Ali Kabiri --- README.md | 64 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index e0102fa7..1fabb76f 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,22 @@ # arduino-usb-terminal - Terminal-like app to send commands to Arduino through USB +Simplify testing your IoT projects by using your Android device to send commands to Arduino through USB. ![Android CI](https://github.com/k4biri/arduino-usb-terminal/workflows/Android%20CI/badge.svg) [![superus8r](https://circleci.com/gh/superus8r/arduino-usb-terminal.svg?style=shield)](https://circleci.com/gh/superus8r/arduino-usb-terminal) [![codecov](https://codecov.io/gh/superus8r/arduino-usb-terminal/branch/develop/graph/badge.svg?token=RYIUU345QG)](https://codecov.io/gh/superus8r/arduino-usb-terminal) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=superus8r_arduino-usb-terminal&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=superus8r_arduino-usb-terminal) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=superus8r_arduino-usb-terminal&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=superus8r_arduino-usb-terminal) +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=superus8r_arduino-usb-terminal&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=superus8r_arduino-usb-terminal) + + Ever thought of testing your Arduino project on the go without using a LapTop? + + Sometimes, you just want to send simple commands to an Arduino through USB without getting that 1.4 kilogram laptop out of the bag! 😉 - This app simplifies testing your Arduino components that work with direct usb commands by giving you the ability to send custom commands and view the returned message from your Arduino device. + Especially if the Android phone in your pocket has enough resources to do that! - This is being done as a hobby, and for experimenting, so probably there might be some flaws; As an example, the vendor ID of Arduino is hardcoded to only work with Arduino devices, but this is my use case and please feel free to change it to match your needs. + This is being done as a hobby, and for experimenting, so probably there might be some flaws; As an example, the vendor ID of Arduino is set to only work with Arduino devices, but this is my use case and please feel free to change it to match your needs. Or if you have a great idea to make this dynamic wile keeping the app simple, please feel free to open a pull request! # Build and Run The app is available for free on Google Play Store (Arduino USB Terminal). @@ -17,32 +24,45 @@ Please read the **Sentry Reports** part on this page before running the project to avoid build failures. Get it on Google Play + + + # Tests + You can run all the instrumented tests using a Gradle Managed Device in one line: +``` +./gradlew pixel2api30DebugAndroidTest +``` +This command will run an Android emulator on the background and run all the tests. +After a successful run, the test coverage file will be available in your build folder: +``` +app/build/outputs/managed_device_code_coverage/pixel2api30/coverage.ec +``` +To create a unified coverage report you can use the Jacoco task: +``` +./gradlew jacocoTestReport +// Jacoco HTML and XML output will be under the following path: +app/build/mergedReportDir/jacocoTestReport +``` +More info about Gradle managed devices in official Android testing docs: [Scale your tests with Gradle Managed Devices](https://developer.android.com/studio/test/gradle-managed-devices) + + - ## Terminal - A Simple terminal page which does what it is supposed to do interacting with an Arduino manually through the USB cable. + ## Firebase Crashlytics Reports + The project uses Firebase Crashlytics for the crash reports, therefore you will need to create a free Firebase project to use it. + - Once you create a Firebase project, Firebase will provide you with a config file (`google-services.json`). + - Place your `google-services.json` file under the `app/` directory and build the project to activate it. - ## Joystick - The Joystick is removed for the first release. + More info on [Firebase official docs for getting started with Firebase Crashlytics](https://firebase.google.com/docs/crashlytics/get-started?platform=android) - ## Tests - Currently, there are some basic tests to run on the CI, but needs improvements. - UI tests will be completed once the project is migrated to Jetpack Compose. + If this is not needed, you can remove the crashlytics dependency in project leve and app level build.gradle files. + - remove `classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.4"` from `build.gradle` file + - remove `implementation("com.google.firebase:firebase-crashlytics-ktx")` from `app/build.gradle.kts` file - ## Sentry Reports - The project uses Sentry for the crash reports, if this is not needed, you can remove the following line in `AndroidManifest.xml`: - `` - But if it is needed, you need to [create a Sentry dsn value](https://docs.sentry.io/platforms/android/) to put under the following path: - `app/src/main/res/values/api_keys.xml` - The file contents might look like similar to this: - ` - - YOUR_SENTRY_SPECIFIC_VALUE - ` + ## Knows Issues +- Please feel free inform me about new issues - ### Knows Issues - _Still unknown! :) Suggestions and PRs are welcome! :) +--- ### More comes as the project evolves... \ No newline at end of file From 9b902c11f6bdd3e7ad9e2a147076cd72acfc290a Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 9 Mar 2023 18:05:45 +0100 Subject: [PATCH 020/174] add info about testing using gradle managed devices Signed-off-by: Ali Kabiri --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1fabb76f..ed445f55 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Simplify testing your IoT projects by using your Android device to send commands [![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=superus8r_arduino-usb-terminal&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=superus8r_arduino-usb-terminal) [![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=superus8r_arduino-usb-terminal&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=superus8r_arduino-usb-terminal) - Ever thought of testing your Arduino project on the go without using a LapTop? + Ever thought of testing your Arduino project on the go without using a lap top? Sometimes, you just want to send simple commands to an Arduino through USB without getting that 1.4 kilogram laptop out of the bag! 😉 @@ -42,6 +42,9 @@ To create a unified coverage report you can use the Jacoco task: // Jacoco HTML and XML output will be under the following path: app/build/mergedReportDir/jacocoTestReport ``` +Gradle automatically recognizes your environment and configures, runs, and closes the emulator in the background. +This has been tested on environments with Arm (Apple M1) and Intel CPUs. + More info about Gradle managed devices in official Android testing docs: [Scale your tests with Gradle Managed Devices](https://developer.android.com/studio/test/gradle-managed-devices) From 03e09104f90fde41d7223d15707f9e26bfa91c8a Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Thu, 9 Mar 2023 18:19:17 +0100 Subject: [PATCH 021/174] specify jacoco custom output file path for sonar coverage Signed-off-by: Ali Kabiri --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 4b609460..e1c4170a 100644 --- a/build.gradle +++ b/build.gradle @@ -29,5 +29,6 @@ sonarqube { property "sonar.projectName", "arduino-usb-terminal" property "sonar.sourceEncoding", "UTF-8" property "sonar.host.url", "https://sonarcloud.io" + property "sonar.coverage.jacoco.xmlReportPaths", "${project.rootDir}/app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml" } } \ No newline at end of file From 45977ac4a1881ebed34f3dce3cfc6fa7bca67def Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Fri, 10 Mar 2023 07:49:35 +0100 Subject: [PATCH 022/174] add sonar.java.binaries path Signed-off-by: Ali Kabiri --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index e1c4170a..ec2edbad 100644 --- a/build.gradle +++ b/build.gradle @@ -30,5 +30,6 @@ sonarqube { property "sonar.sourceEncoding", "UTF-8" property "sonar.host.url", "https://sonarcloud.io" property "sonar.coverage.jacoco.xmlReportPaths", "${project.rootDir}/app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml" + property "sonar.java.binaries", "app/build/tmp/kotlin-classes/debug" } } \ No newline at end of file From 175528d8bfe1c8975c7991d9978d49702e48482c Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 12:35:37 +0100 Subject: [PATCH 023/174] update SonarScanner gradle properties; update README Signed-off-by: Ali Kabiri --- README.md | 12 ++++++++++++ build.gradle | 9 +++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed445f55..01708181 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,18 @@ More info about Gradle managed devices in official Android testing docs: [Scale If this is not needed, you can remove the crashlytics dependency in project leve and app level build.gradle files. - remove `classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.4"` from `build.gradle` file - remove `implementation("com.google.firebase:firebase-crashlytics-ktx")` from `app/build.gradle.kts` file + + +## Sonar Cloud Analysis +The project uses Sonar Cloud manual analysis to detect code smells and potential bugs +To run the manual analysis locally, use the gradle `sonar` task: +``` +./gradlew sonar +``` +Since this uses the gradle scanner, the sonar properties are defined in root project's `build.gradle` file. +On one hand, the Sonar scanner requires local paths for `sonar.sources` and `sonar.binaries` properties, on the other hand it requires absolute path for `sonar.androidLint.reportPaths` and `sonar.coverage.jacoco.xmlReportPaths`. +More info on official Sonar docs: [SonarScanner for Gradle](https://docs.sonarcloud.io/advanced-setup/ci-based-analysis/sonarscanner-for-gradle/) + ## Knows Issues diff --git a/build.gradle b/build.gradle index ec2edbad..09ce2b2e 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,12 @@ sonarqube { property "sonar.projectName", "arduino-usb-terminal" property "sonar.sourceEncoding", "UTF-8" property "sonar.host.url", "https://sonarcloud.io" - property "sonar.coverage.jacoco.xmlReportPaths", "${project.rootDir}/app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml" - property "sonar.java.binaries", "app/build/tmp/kotlin-classes/debug" + + // sonar requires relative path for sources and binaries + property "sonar.sources", "/app/src/main/java" + property "sonar.binaries", "/app/build/tmp/kotlin-classes/debug" + // sonar requires absolute path for lint and jacoco reports! + property "sonar.androidLint.reportPaths", "$rootDir/app/build/reports/lint-results-debug.xml" + property "sonar.coverage.jacoco.xmlReportPaths", "$rootDir/app/build/mergedReportDir/jacocoTestReport/jacocoTestReport.xml" } } \ No newline at end of file From 5f8e09f5fff6276056a26a1226895a5a1fdd4ce3 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 12:42:59 +0100 Subject: [PATCH 024/174] create a gradle task to generate google-services.json file from env vars Signed-off-by: Ali Kabiri --- .circleci/config.yml | 2 +- app/build.gradle.kts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3999805a..d49905d2 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,11 +29,11 @@ jobs: steps: - checkout - run: python3 scripts/generateKsPropFile.py - - run: python3 scripts/generateGoogleServicesJson.py - restore_gradle_cache - run: sdkmanager --list - run: (yes || true) | sdkmanager "tools" "platform-tools" "build-tools;33.0.2" "platforms;android-33" "system-images;android-30;google_atd;x86" - run: (yes || true) | sdkmanager --licenses + - run: ./gradlew generateGoogleServicesJson - run: ./gradlew assembleDebug --stacktrace - run: ./gradlew pixel2api30DebugAndroidTest --stacktrace - run: ./gradlew testDebugUnitTest --stacktrace diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 075e4633..a3d78fb6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -111,6 +111,17 @@ sonarqube { } } +tasks.register("generateGoogleServicesJson") { + doLast { + val jsonFileName = "google-services.json" + val fileContent = System.getenv("GOOGLE_SERVICES_JSON") + File(projectDir, jsonFileName).apply { + createNewFile(); writeText(fileContent) + println("generated google-services.json") + } + } +} + fun loadKeyStore(ksProp: Properties) { val ksPropFile = file("keystore.properties") if (ksPropFile.exists()) { From 3c9875a03ba4d9346e43dc104a000b153e4d7577 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 12:56:50 +0100 Subject: [PATCH 025/174] create a gradle task to generate the ksProp file for signing Signed-off-by: Ali Kabiri --- .circleci/config.yml | 2 +- app/build.gradle.kts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d49905d2..d144b6bb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,11 +28,11 @@ jobs: resource_class: large steps: - checkout - - run: python3 scripts/generateKsPropFile.py - restore_gradle_cache - run: sdkmanager --list - run: (yes || true) | sdkmanager "tools" "platform-tools" "build-tools;33.0.2" "platforms;android-33" "system-images;android-30;google_atd;x86" - run: (yes || true) | sdkmanager --licenses + - run: ./gradlew generateKsPropFile - run: ./gradlew generateGoogleServicesJson - run: ./gradlew assembleDebug --stacktrace - run: ./gradlew pixel2api30DebugAndroidTest --stacktrace diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a3d78fb6..2283d247 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -117,7 +117,24 @@ tasks.register("generateGoogleServicesJson") { val fileContent = System.getenv("GOOGLE_SERVICES_JSON") File(projectDir, jsonFileName).apply { createNewFile(); writeText(fileContent) - println("generated google-services.json") + println("generated $jsonFileName") + } + } +} + +tasks.register("generateKsPropFile") { + doLast { + val configFileName = "keystore.properties" + File("$projectDir, $configFileName").apply { + createNewFile() + writeText(""" + # Gradle signing properties for app module + release.file=${System.getenv("KS_PATH")} + release.storePassword=${System.getenv("KS_PASSWORD")} + release.keyAlias=${System.getenv("KS_KEY_ALIAS")} + release.keyPassword=${System.getenv("KS_KEY_PASSWORD")} + """.trimIndent()) + println("generated $configFileName") } } } From 190fa9d2b0e887c0c90c8d92029fdc163decad9c Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 13:05:54 +0100 Subject: [PATCH 026/174] update config.yml Signed-off-by: Ali Kabiri --- app/build.gradle.kts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2283d247..7356bb65 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -129,10 +129,10 @@ tasks.register("generateKsPropFile") { createNewFile() writeText(""" # Gradle signing properties for app module - release.file=${System.getenv("KS_PATH")} - release.storePassword=${System.getenv("KS_PASSWORD")} - release.keyAlias=${System.getenv("KS_KEY_ALIAS")} - release.keyPassword=${System.getenv("KS_KEY_PASSWORD")} + release.file=${System.getenv("KS_PATH") ?: "empty"} + release.storePassword=${System.getenv("KS_PASSWORD") ?: "empty"} + release.keyAlias=${System.getenv("KS_KEY_ALIAS") ?: "empty"} + release.keyPassword=${System.getenv("KS_KEY_PASSWORD") ?: "empty"} """.trimIndent()) println("generated $configFileName") } From 79b933abaa961b9e1581538c122f3d41f346354a Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 13:12:34 +0100 Subject: [PATCH 027/174] fix an issue with keystore.properties path while using the gradle task to generate it Signed-off-by: Ali Kabiri --- app/build.gradle.kts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7356bb65..9b9c7aac 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -125,16 +125,16 @@ tasks.register("generateGoogleServicesJson") { tasks.register("generateKsPropFile") { doLast { val configFileName = "keystore.properties" - File("$projectDir, $configFileName").apply { + File(projectDir, configFileName).apply { createNewFile() writeText(""" # Gradle signing properties for app module - release.file=${System.getenv("KS_PATH") ?: "empty"} - release.storePassword=${System.getenv("KS_PASSWORD") ?: "empty"} - release.keyAlias=${System.getenv("KS_KEY_ALIAS") ?: "empty"} - release.keyPassword=${System.getenv("KS_KEY_PASSWORD") ?: "empty"} + release.file=${System.getenv("KS_PATH")} + release.storePassword=${System.getenv("KS_PASSWORD")} + release.keyAlias=${System.getenv("KS_KEY_ALIAS")} + release.keyPassword=${System.getenv("KS_KEY_PASSWORD")} """.trimIndent()) - println("generated $configFileName") + println("generated ${this.path}") } } } From 66265fb21f0028e946e10faf36db8cf48629de2d Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 13:17:12 +0100 Subject: [PATCH 028/174] update sonar --- app/build.gradle.kts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9b9c7aac..db4b667d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -129,10 +129,10 @@ tasks.register("generateKsPropFile") { createNewFile() writeText(""" # Gradle signing properties for app module - release.file=${System.getenv("KS_PATH")} - release.storePassword=${System.getenv("KS_PASSWORD")} - release.keyAlias=${System.getenv("KS_KEY_ALIAS")} - release.keyPassword=${System.getenv("KS_KEY_PASSWORD")} + release.file=${System.getenv("KS_PATH") ?: "empty"} + release.storePassword=${System.getenv("KS_PASSWORD") ?: "empty"} + release.keyAlias=${System.getenv("KS_KEY_ALIAS") ?: "empty"} + release.keyPassword=${System.getenv("KS_KEY_PASSWORD") ?: "empty"} """.trimIndent()) println("generated ${this.path}") } From 71425f895f41259dd0aff308106f25e442021558 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 13:45:46 +0100 Subject: [PATCH 029/174] distribute the debug version to firebase appTester --- .circleci/config.yml | 31 +++++++++++++++++++++++++++++-- app/build.gradle.kts | 16 ++++++++++++++-- fastlane/Fastfile | 12 ++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d144b6bb..e794c12f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,10 +21,17 @@ commands: - ~/.gradle/caches key: v1-gradle-cache-{{ arch }}-{{ checksum "build.gradle" }} -jobs: - android-test: +executors: + android-machine: machine: image: android:2022.12.1 + android-docker: + docker: + - image: cimg/android:2023.02 + +jobs: + android-test: + executor: android-machine resource_class: large steps: - checkout @@ -49,8 +56,28 @@ jobs: - store_artifacts: path: app/build/mergedReportDir + distribute-dev: + executor: android-docker + steps: + - checkout + - restore_gradle_cache + - run: + name: Prepare Fastlane + command: | + sudo bundle update + ./gradlew generateKsPropFile + ./gradlew generateGoogleServicesJson + ./gradlew assembleDebug + - run: + name: Distribute to Firebase AppTester + command: bundle exec fastlane distDev + - store_artifacts: + path: /home/circleci/project/app/build/outputs/apk/debug/app-debug.apk + destination: fastlane-output-debug + workflows: workflow: jobs: - android-test: context: SonarCloud + - distribute-dev diff --git a/app/build.gradle.kts b/app/build.gradle.kts index db4b667d..67e4eb06 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,8 +26,8 @@ android { applicationId = "org.kabiri.android.usbterminal" minSdk = 23 targetSdk = 33 - versionCode = 13 - versionName = "0.9.12" + versionCode = System.getenv("CIRCLE_BUILD_NUM")?.toIntOrNull() ?: 13 + versionName = "0.9.12${System.getenv("CIRCLE_BUILD_NUM") ?: ""}" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -139,6 +139,18 @@ tasks.register("generateKsPropFile") { } } +tasks.register("generateAppDistKey") { + doLast { + val jsonFileName = "app-dist-key.json" + val fileContent = System.getenv("GOOGLE_APP_DIST_FASTLANE_SERVICE_ACCOUNT") + File(rootDir, jsonFileName).apply { + createNewFile() + writeText(fileContent) + println("generated ${this.path}") + } + } +} + fun loadKeyStore(ksProp: Properties) { val ksPropFile = file("keystore.properties") if (ksPropFile.exists()) { diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 1e03a189..a28e78d3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -16,11 +16,23 @@ default_platform(:android) platform :android do + desc "Runs all the tests" lane :run_unit_tests do gradle(task: "testDebugUnitTest --stacktrace") end + desc "Deploy to Firebase AppTester channel" + lane :distDev do + gradle(task: "generateKsPropFile generateGoogleServicesJson generateAppDistKey clean assembleDebug") + firebase_app_distribution( + service_credentials_file: ENV['APP_DIST_SERVICE_ACCOUNT_PATH'], + app: "1:1032339097117:android:1e333da555ec74a71668f4", + groups: "testers", + release_notes: "", + ) + end + desc "Submit a new Beta Build to Crashlytics Beta" lane :beta do gradle(task: "clean assembleRelease") From c2b5ba1215493a649fe5770d4aebcf2bd29ead9a Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 13:58:29 +0100 Subject: [PATCH 030/174] check if keyStore properties are loaded before creating the default config --- app/build.gradle.kts | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 67e4eb06..55244f3d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -32,15 +32,16 @@ android { } signingConfigs { - // read release credentials from keystore.properties file - val ksProp = Properties() - // load keys inside the ksProp file - loadKeyStore(ksProp) - create("release") { - keyAlias = ksProp.getProperty("release.keyAlias") - keyPassword = ksProp.getProperty("release.keyPassword") - storeFile = file(ksProp.getProperty("release.file")) - storePassword = ksProp.getProperty("release.storePassword") + + val ksName = "keystore.properties" + val ksProp = loadKeyStore(ksName) + ksProp?.let { + create("release") { + keyAlias = ksProp.getProperty("release.keyAlias") + keyPassword = ksProp.getProperty("release.keyPassword") + storeFile = file(ksProp.getProperty("release.file")) + storePassword = ksProp.getProperty("release.storePassword") + } } } @@ -129,10 +130,10 @@ tasks.register("generateKsPropFile") { createNewFile() writeText(""" # Gradle signing properties for app module - release.file=${System.getenv("KS_PATH") ?: "empty"} - release.storePassword=${System.getenv("KS_PASSWORD") ?: "empty"} - release.keyAlias=${System.getenv("KS_KEY_ALIAS") ?: "empty"} - release.keyPassword=${System.getenv("KS_KEY_PASSWORD") ?: "empty"} + release.file=${System.getenv("KS_PATH")} + release.storePassword=${System.getenv("KS_PASSWORD")} + release.keyAlias=${System.getenv("KS_KEY_ALIAS")} + release.keyPassword=${System.getenv("KS_KEY_PASSWORD")} """.trimIndent()) println("generated ${this.path}") } @@ -151,12 +152,15 @@ tasks.register("generateAppDistKey") { } } -fun loadKeyStore(ksProp: Properties) { - val ksPropFile = file("keystore.properties") - if (ksPropFile.exists()) { +fun loadKeyStore(name: String): Properties? { + val ksProp = Properties() + val ksPropFile = file(name) + return if (ksPropFile.exists()) { ksProp.load(FileInputStream(ksPropFile)) + ksProp } else { println("ERROR: local keystore file not found") + null } } From 33046e8ed0bc1b0e31b139892ddfb0a945005e6c Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 14:13:11 +0100 Subject: [PATCH 031/174] use findByName instead of getByName for release signing config Signed-off-by: Ali Kabiri --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 55244f3d..253fb968 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -47,7 +47,7 @@ android { buildTypes { named("release") { - signingConfig = signingConfigs.getByName("release") + signingConfig = signingConfigs.findByName("release") isMinifyEnabled = false proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } From f306e4c4a0c17a92e6bb7e367a411519a88390af Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 14:21:15 +0100 Subject: [PATCH 032/174] add firebase_app_distribution plugin for fastlate --- .circleci/config.yml | 1 + Gemfile | 4 ++- Gemfile.lock | 64 +++++++++++++++++++++++--------------------- fastlane/Pluginfile | 5 ++++ 4 files changed, 42 insertions(+), 32 deletions(-) create mode 100644 fastlane/Pluginfile diff --git a/.circleci/config.yml b/.circleci/config.yml index e794c12f..46e202fb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -58,6 +58,7 @@ jobs: distribute-dev: executor: android-docker + resource_class: large steps: - checkout - restore_gradle_cache diff --git a/Gemfile b/Gemfile index adc90d98..2ccf2ecb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,5 @@ source "https://rubygems.org" -gem "fastlane" \ No newline at end of file +gem "fastlane" +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock index 944b5730..7a0078bc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,24 +1,24 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.6) rexml addressable (2.8.1) public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.642.0) - aws-sdk-core (3.158.0) + aws-partitions (1.725.0) + aws-sdk-core (3.170.0) aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.58.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (1.63.0) + aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-s3 (1.119.1) + aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) aws-sigv4 (1.5.2) @@ -36,8 +36,8 @@ GEM unf (>= 0.0.5, < 1.0.0) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.93.0) - faraday (1.10.2) + excon (0.99.0) + faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.210.1) + fastlane (2.212.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -105,10 +105,11 @@ GEM xcodeproj (>= 1.13.0, < 2.0.0) xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-firebase_app_distribution (0.5.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.29.0) - google-apis-core (>= 0.9.0, < 2.a) - google-apis-core (0.9.0) + google-apis-androidpublisher_v3 (0.35.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.0) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -117,10 +118,10 @@ GEM retriable (>= 2.0, < 4.a) rexml webrick - google-apis-iamcredentials_v1 (0.15.0) - google-apis-core (>= 0.9.0, < 2.a) - google-apis-playcustomapp_v1 (0.11.0) - google-apis-core (>= 0.9.0, < 2.a) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-playcustomapp_v1 (0.13.0) + google-apis-core (>= 0.11.0, < 2.a) google-apis-storage_v1 (0.19.0) google-apis-core (>= 0.9.0, < 2.a) google-cloud-core (1.6.0) @@ -128,8 +129,8 @@ GEM google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.3.0) - google-cloud-storage (1.43.0) + google-cloud-errors (1.3.1) + google-cloud-storage (1.44.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) @@ -137,7 +138,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.2.0) + googleauth (1.3.0) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -148,11 +149,11 @@ GEM http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.6.1) - json (2.6.2) - jwt (2.5.0) + jmespath (1.6.2) + json (2.6.3) + jwt (2.7.0) memoist (0.16.2) - mini_magick (4.11.0) + mini_magick (4.12.0) mini_mime (1.1.2) multi_json (1.15.0) multipart-post (2.0.0) @@ -160,8 +161,8 @@ GEM naturally (2.2.1) optparse (0.1.1) os (1.1.4) - plist (3.6.0) - public_suffix (5.0.0) + plist (3.7.0) + public_suffix (5.0.1) rake (13.0.6) representable (3.2.0) declarative (< 0.1.0) @@ -178,7 +179,7 @@ GEM faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.8) + simctl (1.6.10) CFPropertyList naturally terminal-notifier (2.0.0) @@ -195,7 +196,7 @@ GEM unf_ext (0.0.8.2) unf_ext (0.0.8.2-x64-mingw32) unicode-display_width (1.8.0) - webrick (1.7.0) + webrick (1.8.1) word_wrap (1.0.0) xcodeproj (1.22.0) CFPropertyList (>= 2.3.3, < 4.0) @@ -217,6 +218,7 @@ PLATFORMS DEPENDENCIES fastlane + fastlane-plugin-firebase_app_distribution BUNDLED WITH - 2.3.7 + 2.4.7 diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 00000000..b18539bc --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,5 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-firebase_app_distribution' From a77ab32721bbbd034131f550e8eff6d7b0e65a20 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 14:35:11 +0100 Subject: [PATCH 033/174] use gradle tasks to prepare the build on Github actions --- .github/workflows/android.yml | 45 ++++++++--------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 86605917..e9188b06 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -12,24 +12,15 @@ jobs: with: ruby-version: '3.0' bundler-cache: true - - name: set up JDK 11 uses: actions/setup-java@v1 with: distribution: 'zulu' java-version: '11' - - name: set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - name: generate ksProp file - run: python scripts/generateKsPropFile.py - - name: create google-services.json file - run: cat app/google-services.json | base64 - - name: update google-services.json file - env: - DATA: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo $DATA > app/google-services.json + run: ./gradlew generateKsPropFile + - name: generate google-services.json file + run: ./gradlew generateGoogleServicesJson - name: setup fastlane run: bundle install - name: run unit tests @@ -49,18 +40,10 @@ jobs: with: distribution: 'zulu' java-version: '11' - - name: set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - name: generate ksProp file - run: python scripts/generateKsPropFile.py - - name: create google-services.json file - run: cat app/google-services.json | base64 - - name: update google-services.json file - env: - DATA: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo $DATA > app/google-services.json + run: ./gradlew generateKsPropFile + - name: generate google-services.json file + run: ./gradlew generateGoogleServicesJson - name: Lint run: bash ./gradlew lintDebug - name: Lint results @@ -70,7 +53,7 @@ jobs: path: app/build/reports/lint-results-debug.html ui-test: - runs-on: macOS-latest + runs-on: macos-latest steps: - name: checkout uses: actions/checkout@v2 @@ -79,18 +62,10 @@ jobs: with: distribution: 'zulu' java-version: '11' - - name: set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' - name: generate ksProp file - run: python scripts/generateKsPropFile.py - - name: create google-services.json file - run: cat app/google-services.json | base64 - - name: update google-services.json file - env: - DATA: ${{ secrets.GOOGLE_SERVICES_JSON }} - run: echo $DATA > app/google-services.json + run: ./gradlew generateKsPropFile + - name: generate google-services.json file + run: ./gradlew generateGoogleServicesJson - name: run tests uses: reactivecircus/android-emulator-runner@v2 with: From 98140a1dbe52a3221738683099f33222be7b5ace Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 14:35:35 +0100 Subject: [PATCH 034/174] use fastlane to run all tests and generate coverage --- .circleci/config.yml | 4 +--- fastlane/Fastfile | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 46e202fb..981c275e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,9 +42,7 @@ jobs: - run: ./gradlew generateKsPropFile - run: ./gradlew generateGoogleServicesJson - run: ./gradlew assembleDebug --stacktrace - - run: ./gradlew pixel2api30DebugAndroidTest --stacktrace - - run: ./gradlew testDebugUnitTest --stacktrace - - run: ./gradlew jacocoTestReport --stacktrace + - run: bundle exec fastlane testDev - save_gradle_cache - run: name: Analyze on SonarCloud diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a28e78d3..debdf5e5 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -22,6 +22,11 @@ platform :android do gradle(task: "testDebugUnitTest --stacktrace") end + desc "Runs all the tests with coverage" + lane :testDev do + gradle(task: "pixel2api30DebugAndroidTest testDebugUnitTest jacocoTestReport --stacktrace") + end + desc "Deploy to Firebase AppTester channel" lane :distDev do gradle(task: "generateKsPropFile generateGoogleServicesJson generateAppDistKey clean assembleDebug") From 352e2524be11c878052641c33e2c34120d3cb4d2 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 14:40:51 +0100 Subject: [PATCH 035/174] define env for google-servies.json file --- .github/workflows/android.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index e9188b06..795e62ca 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -21,6 +21,8 @@ jobs: run: ./gradlew generateKsPropFile - name: generate google-services.json file run: ./gradlew generateGoogleServicesJson + env: + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - name: setup fastlane run: bundle install - name: run unit tests @@ -44,6 +46,8 @@ jobs: run: ./gradlew generateKsPropFile - name: generate google-services.json file run: ./gradlew generateGoogleServicesJson + env: + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - name: Lint run: bash ./gradlew lintDebug - name: Lint results @@ -66,6 +70,8 @@ jobs: run: ./gradlew generateKsPropFile - name: generate google-services.json file run: ./gradlew generateGoogleServicesJson + env: + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} - name: run tests uses: reactivecircus/android-emulator-runner@v2 with: From 63a6fef1e4ddbd9d8df110c1511f59040ea28837 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 14:41:08 +0100 Subject: [PATCH 036/174] remove unnecessary commands which is already handled by fastlane --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 981c275e..28870a96 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -64,8 +64,6 @@ jobs: name: Prepare Fastlane command: | sudo bundle update - ./gradlew generateKsPropFile - ./gradlew generateGoogleServicesJson ./gradlew assembleDebug - run: name: Distribute to Firebase AppTester From 5d9ea8438459fa2e8d9dc64dd005d68acc7e5601 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 14:44:40 +0100 Subject: [PATCH 037/174] run bundle update before executing fastlane command Signed-off-by: Ali Kabiri --- .circleci/config.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 28870a96..156cb678 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,11 @@ jobs: - run: ./gradlew generateKsPropFile - run: ./gradlew generateGoogleServicesJson - run: ./gradlew assembleDebug --stacktrace - - run: bundle exec fastlane testDev + - run: + name: Fastlane - run all tests with coverage report + command: | + sudo bundle update + bundle exec fastlane testDev - save_gradle_cache - run: name: Analyze on SonarCloud From 1184ffccabb654c148131f9ed24b5e519dc4ffab Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 14:55:48 +0100 Subject: [PATCH 038/174] install ruby to run fastlane Signed-off-by: Ali Kabiri --- .circleci/config.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 156cb678..59590bdd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,6 +1,7 @@ version: 2.1 orbs: codecov: codecov/codecov@3.2.4 + ruby: circleci/ruby@2.0.0 commands: restore_gradle_cache: @@ -36,12 +37,11 @@ jobs: steps: - checkout - restore_gradle_cache - - run: sdkmanager --list - - run: (yes || true) | sdkmanager "tools" "platform-tools" "build-tools;33.0.2" "platforms;android-33" "system-images;android-30;google_atd;x86" - - run: (yes || true) | sdkmanager --licenses - run: ./gradlew generateKsPropFile - run: ./gradlew generateGoogleServicesJson - run: ./gradlew assembleDebug --stacktrace + - ruby/install: + version: '3.0' - run: name: Fastlane - run all tests with coverage report command: | @@ -66,9 +66,7 @@ jobs: - restore_gradle_cache - run: name: Prepare Fastlane - command: | - sudo bundle update - ./gradlew assembleDebug + command: sudo bundle update - run: name: Distribute to Firebase AppTester command: bundle exec fastlane distDev From 7a91b06609ee6f6d216ef6ed0f69888a113cdbed Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 15:07:07 +0100 Subject: [PATCH 039/174] install bundler using ruby gems --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 59590bdd..b808917a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,6 +45,7 @@ jobs: - run: name: Fastlane - run all tests with coverage report command: | + sudo gem install bundler sudo bundle update bundle exec fastlane testDev - save_gradle_cache From 036aa00075e95b9ee5ab9879c5e5d6b2fb5fa78a Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 15:07:19 +0100 Subject: [PATCH 040/174] hopefully setup github actions cache for gradle --- .github/workflows/android.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 795e62ca..6a96ad37 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -17,6 +17,20 @@ jobs: with: distribution: 'zulu' java-version: '11' + - name: Cache Gradle wrapper + uses: actions/cache@v3 + with: + path: ~/.gradle/wrapper + key: ${{ runner.OS }}-gradle-wrapper-cache-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.OS }}-gradle-wrapper-cache- + - name: Cache Gradle caches + uses: actions/cache@v3 + with: + path: ~/.gradle/caches + key: ${{ runner.OS }}-gradle-caches-cache-${{ hashFiles('build.gradle') }} + restore-keys: | + ${{ runner.OS }}-gradle-caches-cache- - name: generate ksProp file run: ./gradlew generateKsPropFile - name: generate google-services.json file From 326ae8dacfb424ea2066415931b648ea08fb1ca4 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 15:16:51 +0100 Subject: [PATCH 041/174] hopefully activate bundler cache on circleci Signed-off-by: Ali Kabiri --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b808917a..5e9552e7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,11 +42,13 @@ jobs: - run: ./gradlew assembleDebug --stacktrace - ruby/install: version: '3.0' + - ruby/install-deps: + with-cache: true - run: name: Fastlane - run all tests with coverage report command: | - sudo gem install bundler - sudo bundle update +# sudo gem install bundler +# sudo bundle update bundle exec fastlane testDev - save_gradle_cache - run: From 54127a410aed953897dedb1daaea6e924aedfb15 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 15:17:20 +0100 Subject: [PATCH 042/174] hopefully activate bundler cache on circleci Signed-off-by: Ali Kabiri --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e9552e7..4630293a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,8 +47,6 @@ jobs: - run: name: Fastlane - run all tests with coverage report command: | -# sudo gem install bundler -# sudo bundle update bundle exec fastlane testDev - save_gradle_cache - run: From 19ec385df46852a789ed62eb250d348d0806e469 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 17:04:23 +0100 Subject: [PATCH 043/174] use fastlane to prepare the tests build Signed-off-by: Ali Kabiri --- .circleci/config.yml | 3 --- fastlane/Fastfile | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4630293a..a2a4a407 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,9 +37,6 @@ jobs: steps: - checkout - restore_gradle_cache - - run: ./gradlew generateKsPropFile - - run: ./gradlew generateGoogleServicesJson - - run: ./gradlew assembleDebug --stacktrace - ruby/install: version: '3.0' - ruby/install-deps: diff --git a/fastlane/Fastfile b/fastlane/Fastfile index debdf5e5..e8381099 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -24,7 +24,7 @@ platform :android do desc "Runs all the tests with coverage" lane :testDev do - gradle(task: "pixel2api30DebugAndroidTest testDebugUnitTest jacocoTestReport --stacktrace") + gradle(task: "generateKsPropFile generateGoogleServicesJson pixel2api30DebugAndroidTest testDebugUnitTest jacocoTestReport --stacktrace") end desc "Deploy to Firebase AppTester channel" From a1fc46388169a9ab82d9e481dedfae7332d9d575 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 17:06:07 +0100 Subject: [PATCH 044/174] remove unnecessary python scrips which are already available as gradle tasks --- scripts/generateGoogleServicesJson.py | 9 --------- scripts/generateKsPropFile.py | 22 ---------------------- 2 files changed, 31 deletions(-) delete mode 100644 scripts/generateGoogleServicesJson.py delete mode 100644 scripts/generateKsPropFile.py diff --git a/scripts/generateGoogleServicesJson.py b/scripts/generateGoogleServicesJson.py deleted file mode 100644 index 3cd613ce..00000000 --- a/scripts/generateGoogleServicesJson.py +++ /dev/null @@ -1,9 +0,0 @@ -import os - -outputPath = "app/google-services.json" - -fileContent = os.getenv('GOOGLE_SERVICES_JSON') - -fhand = open(outputPath, 'w') -fhand.write(str(fileContent)) -fhand.close() \ No newline at end of file diff --git a/scripts/generateKsPropFile.py b/scripts/generateKsPropFile.py deleted file mode 100644 index 7731df9a..00000000 --- a/scripts/generateKsPropFile.py +++ /dev/null @@ -1,22 +0,0 @@ -# This script reads the keystore properties from environment variable -# and generates a keystore.properties file in app module. -# -# More info about gradle properties: -# https://ubuntudroid.medium.com/handling-environment-variables-in-gradle-fb1b8bb6c758 - -import os - -outputPath = "app/keystore.properties" - -keystoreFile = os.getenv('KS_PATH') -keystorePassword = os.getenv('KS_PASSWORD') -keyAlias = os.getenv('KS_KEY_ALIAS') -keyPassword = os.getenv('KS_KEY_PASSWORD') - -fhand = open(outputPath, 'w') -fhand.write("# Gradle properties for module app\n\n") -fhand.write("release.file=" + str(keystoreFile) + "\n") -fhand.write("release.storePassword=" + str(keystorePassword) + '\n') -fhand.write("release.keyAlias=" + str(keyAlias) + '\n') -fhand.write("release.keyPassword=" + str(keyPassword)) -fhand.close() \ No newline at end of file From 327376b747437762ab086b44063df191a131c872 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 17:08:27 +0100 Subject: [PATCH 045/174] Update config.yml --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a2a4a407..7f14bbc9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,8 +37,8 @@ jobs: steps: - checkout - restore_gradle_cache - - ruby/install: - version: '3.0' +# - ruby/install: +# version: '3.0' - ruby/install-deps: with-cache: true - run: From 68ad67da6561ea570584e729dd2b34b99501b38c Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Sat, 11 Mar 2023 17:10:42 +0100 Subject: [PATCH 046/174] Update config.yml --- .circleci/config.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7f14bbc9..29704536 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,8 +37,6 @@ jobs: steps: - checkout - restore_gradle_cache -# - ruby/install: -# version: '3.0' - ruby/install-deps: with-cache: true - run: From 37cfa779558a5e93990702debfebc133b9358b64 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Fri, 30 Jun 2023 17:58:40 +0200 Subject: [PATCH 047/174] Update .gitignore --- .gitignore | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index cda68ade..7769739e 100644 --- a/.gitignore +++ b/.gitignore @@ -37,17 +37,7 @@ captures/ # IntelliJ *.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -# Android Studio 3 in .gitignore file. -.idea/caches -.idea/modules.xml -# Comment next line if keeping position of elements in Navigation Editor is relevant for you -.idea/navEditor.xml +.idea/* # Keystore files # Uncomment the following lines if you do not want to check your keystore files in. From 7a7666f325f77632626fc8a75d051f2b2b0d4668 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Fri, 30 Jun 2023 18:01:05 +0200 Subject: [PATCH 048/174] chore: disable cloud backup --- app/src/main/AndroidManifest.xml | 10 +++-- .../main/res/xml/data_extraction_rules.xml | 43 +++++++++++++++++++ 2 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 app/src/main/res/xml/data_extraction_rules.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 521743c2..a9a2fe6d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - + @@ -7,13 +8,16 @@ + android:theme="@style/AppTheme" + android:dataExtractionRules="@xml/data_extraction_rules" + tools:targetApi="s"> diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..f18f43fc --- /dev/null +++ b/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,43 @@ + + + + + + + + + + \ No newline at end of file From 1f1c853647121ce4fc4d5a43481cf06b82c4fd46 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 17:46:33 +0200 Subject: [PATCH 049/174] chore: update fastlane --- Gemfile.lock | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 7a0078bc..382d34e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,21 +3,21 @@ GEM specs: CFPropertyList (3.0.6) rexml - addressable (2.8.1) + addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) aws-eventstream (1.2.0) - aws-partitions (1.725.0) - aws-sdk-core (3.170.0) + aws-partitions (1.763.0) + aws-sdk-core (3.172.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.5) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.63.0) + aws-sdk-kms (1.64.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.119.1) + aws-sdk-s3 (1.122.0) aws-sdk-core (~> 3, >= 3.165.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.4) @@ -66,7 +66,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.2.6) - fastlane (2.212.1) + fastlane (2.212.2) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -107,7 +107,7 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-firebase_app_distribution (0.5.0) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.35.0) + google-apis-androidpublisher_v3 (0.42.0) google-apis-core (>= 0.11.0, < 2.a) google-apis-core (0.11.0) addressable (~> 2.5, >= 2.5.1) @@ -138,7 +138,7 @@ GEM google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.3.0) + googleauth (1.5.2) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -194,7 +194,6 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.8.2) - unf_ext (0.0.8.2-x64-mingw32) unicode-display_width (1.8.0) webrick (1.8.1) word_wrap (1.0.0) From fe91b0750cb5337162ad9f2ff209ced9667fed3d Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 17:47:08 +0200 Subject: [PATCH 050/174] refactor: use kotlin flow instead of live data to update the terminal output --- .../org/kabiri/android/usbterminal/MainActivity.kt | 11 +++++++---- .../usbterminal/viewmodel/MainActivityViewModel.kt | 7 +++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt index a3111b18..c6faf140 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt @@ -12,6 +12,7 @@ import android.widget.EditText import android.widget.TextView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import org.kabiri.android.usbterminal.extensions.scrollToLastLine import org.kabiri.android.usbterminal.viewmodel.MainActivityViewModel @@ -51,10 +52,12 @@ class MainActivity : AppCompatActivity() { ) } - viewModel.output.observe(this) { - tvOutput.apply { - text = it - scrollToLastLine() + lifecycleScope.launchWhenResumed { + viewModel.output.collect { + tvOutput.apply { + text = it + scrollToLastLine() + } } } diff --git a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt index 575ab12a..64a668e6 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt @@ -3,6 +3,8 @@ package org.kabiri.android.usbterminal.viewmodel import android.hardware.usb.UsbDevice import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import org.kabiri.android.usbterminal.arduino.ArduinoHelper import org.kabiri.android.usbterminal.model.OutputText @@ -17,8 +19,9 @@ class MainActivityViewModel private val arduinoHelper: ArduinoHelper, ): ViewModel() { - private val _outputLive = MutableLiveData("") - val output = _outputLive + private val _outputLive = MutableStateFlow("") + val output: StateFlow + get() = _outputLive fun askForConnectionPermission() = arduinoHelper.askForConnectionPermission() From 910575d9f380991c28f64f0c60abb4d675beb1c7 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 18:44:44 +0200 Subject: [PATCH 051/174] refactor: replace live data with kotlin flows --- .../android/usbterminal/MainActivity.kt | 18 +-- .../usbterminal/arduino/ArduinoHelper.kt | 103 ++++++++---------- .../ArduinoPermissionBroadcastReceiver.kt | 26 +++-- .../arduino/ArduinoSerialReceiver.kt | 22 ++-- .../viewmodel/MainActivityViewModel.kt | 31 ++++-- 5 files changed, 100 insertions(+), 100 deletions(-) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt index c6faf140..44607fe1 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt @@ -42,14 +42,16 @@ class MainActivity : AppCompatActivity() { viewModel.openDeviceAndPort(device) } - viewModel.getLiveOutput().observe(this) { - val spannable = SpannableString(it.text) - spannable.setSpan( - it.getAppearance(this), - 0, - it.text.length, - SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE - ) + lifecycleScope.launchWhenResumed { + viewModel.getLiveOutput().collect { + val spannable = SpannableString(it.text) + spannable.setSpan( + it.getAppearance(this@MainActivity), + 0, + it.text.length, + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } } lifecycleScope.launchWhenResumed { diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt index 27830246..b0649df4 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt @@ -8,11 +8,11 @@ import android.hardware.usb.UsbDevice import android.hardware.usb.UsbDeviceConnection import android.hardware.usb.UsbManager import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData import com.felhr.usbserial.UsbSerialDevice import com.felhr.usbserial.UsbSerialInterface +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine import org.kabiri.android.usbterminal.Constants import org.kabiri.android.usbterminal.R import javax.inject.Inject @@ -37,9 +37,9 @@ class ArduinoHelper private const val TAG = "ArduinoHelper" } - private val _liveOutput = MutableLiveData() - private val _liveInfoOutput = MutableLiveData() - private val _liveErrorOutput = MutableLiveData() + private val _liveOutput = MutableStateFlow("") + private val _liveInfoOutput = MutableStateFlow("") + private val _liveErrorOutput = MutableStateFlow("") private var usbManager: UsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager private lateinit var connection: UsbDeviceConnection @@ -50,41 +50,39 @@ class ArduinoHelper */ fun askForConnectionPermission() { val usbDevices = usbManager.deviceList - _liveInfoOutput.postValue(context.getString(R.string.helper_info_checking_attached_usb_devices)) + _liveInfoOutput.value = + context.getString(R.string.helper_info_checking_attached_usb_devices) if (usbDevices.isNotEmpty()) { for (device in usbDevices) { val deviceVID = device.value.vendorId if (deviceVID == 0x2341) { // Arduino vendor ID connect(device) } else { - _liveErrorOutput.postValue(context.getString(R.string.helper_error_device_not_found)) - _liveErrorOutput.postValue(context.getString(R.string.helper_error_connecting_anyway)) + _liveErrorOutput.value = + context.getString(R.string.helper_error_device_not_found) + _liveErrorOutput.value = context.getString(R.string.helper_error_connecting_anyway) connect(device) } } } else { - _liveErrorOutput.postValue(context.getString(R.string.helper_error_usb_devices_not_attached)) + _liveErrorOutput.value = context.getString(R.string.helper_error_usb_devices_not_attached) } } fun disconnect() { try { connection.close() - _liveOutput.postValue(context.getString(R.string.helper_info_serial_connection_closed)) + _liveOutput.value = context.getString(R.string.helper_info_serial_connection_closed) } catch (e: UninitializedPropertyAccessException) { - _liveErrorOutput.postValue( - context.getString( - R.string.helper_error_connection_not_ready_to_close - ) - ) - _liveErrorOutput.postValue("${e.localizedMessage}\n") + _liveErrorOutput.value = + context.getString(R.string.helper_error_connection_not_ready_to_close) + + _liveErrorOutput.value = "${e.localizedMessage}\n" } catch (e: Exception) { - _liveErrorOutput.postValue( - context.getString( + _liveErrorOutput.value = context.getString( R.string.helper_error_connection_failed_to_close ) - ) - _liveErrorOutput.postValue("${e.localizedMessage}\n") + _liveErrorOutput.value = "${e.localizedMessage}\n" } } @@ -99,7 +97,7 @@ class ArduinoHelper val filter = IntentFilter(Constants.ACTION_USB_PERMISSION) context.registerReceiver(arduinoPermReceiver, filter) // register the broadcast receiver usbManager.requestPermission(device.value, permissionIntent) - _liveInfoOutput.postValue(context.getString(R.string.helper_info_usb_permission_requested)) + _liveInfoOutput.value = context.getString(R.string.helper_info_usb_permission_requested) } /** @@ -118,21 +116,21 @@ class ArduinoHelper serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection) } catch (e: IllegalStateException) { Log.e(TAG, "${e.message}") - _liveErrorOutput.postValue(context.getString(R.string.helper_error_connection_closed_unexpectedly)) + _liveErrorOutput.value = context.getString(R.string.helper_error_connection_closed_unexpectedly) } catch (e: NullPointerException) { Log.e(TAG, "${e.message}") - _liveErrorOutput.postValue(context.getString( - R.string.helper_error_connection_failed_to_open)) + _liveErrorOutput.value = context.getString( + R.string.helper_error_connection_failed_to_open) } catch (e: Exception) { Log.e(TAG, "${e.message}") - _liveErrorOutput.postValue(context.getString( - R.string.helper_error_connection_failed_to_open_unknown)) + _liveErrorOutput.value = + context.getString(R.string.helper_error_connection_failed_to_open_unknown) } if (::serialPort.isInitialized) prepareSerialPort(serialPort) else { - _liveInfoOutput.postValue(context.getString(R.string.helper_error_serial_port_is_null)) + _liveInfoOutput.value = context.getString(R.string.helper_error_serial_port_is_null) connection.close() } } @@ -151,9 +149,10 @@ class ArduinoHelper it.setParity(UsbSerialInterface.PARITY_NONE) it.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF) it.read(arduinoSerialReceiver) // messages will be received from the receiver. - _liveInfoOutput.postValue(context.getString(R.string.helper_info_serial_connection_opened)) + _liveInfoOutput.value = context.getString(R.string.helper_info_serial_connection_opened) } else { // serial port not opened. - _liveErrorOutput.postValue(context.getString(R.string.helper_error_serial_connection_not_opened)) + _liveErrorOutput.value = + context.getString(R.string.helper_error_serial_connection_not_opened) } } } @@ -166,49 +165,39 @@ class ArduinoHelper if (::serialPort.isInitialized && command.isNotBlank()) { serialPort.write(command.toByteArray()) // go to next line because the answer might be sent in more than one part. - _liveOutput.postValue(context.getString(R.string.next_line)) + _liveOutput.value = context.getString(R.string.next_line) true } else { - _liveErrorOutput.postValue(context.getString(R.string.helper_error_serial_port_is_null)) + _liveErrorOutput.value = context.getString(R.string.helper_error_serial_port_is_null) false } } catch (e: Exception) { - _liveErrorOutput.postValue( - context.getString(R.string.helper_error_write_problem) + - " \n${e.localizedMessage}\n") + _liveErrorOutput.value = context.getString(R.string.helper_error_write_problem) + + " \n${e.localizedMessage}\n" Log.e(TAG, "$e") false } } - fun getLiveOutput(): LiveData { - - val liveDataMerger = MediatorLiveData() - liveDataMerger.addSource(_liveOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoPermReceiver.liveOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoSerialReceiver.liveOutput) { liveDataMerger.value = it } - - return liveDataMerger + fun getLiveOutput(): Flow { + + return _liveOutput + .combine(arduinoPermReceiver.liveOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveOutput) { a, b -> a + b } } - fun getLiveInfoOutput(): LiveData { - - val liveDataMerger = MediatorLiveData() - liveDataMerger.addSource(_liveInfoOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoPermReceiver.liveInfoOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoSerialReceiver.liveInfoOutput) { liveDataMerger.value = it } + fun getLiveInfoOutput(): Flow { - return liveDataMerger + return _liveInfoOutput + .combine(arduinoPermReceiver.liveInfoOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveInfoOutput) { a, b -> a + b } } - fun getLiveErrorOutput(): LiveData { - - val liveDataMerger = MediatorLiveData() - liveDataMerger.addSource(_liveErrorOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoPermReceiver.liveErrorOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(arduinoSerialReceiver.liveErrorOutput) { liveDataMerger.value = it } + fun getLiveErrorOutput(): Flow { - return liveDataMerger + return _liveErrorOutput + .combine(arduinoPermReceiver.liveErrorOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveErrorOutput) { a, b -> a + b } } } \ No newline at end of file diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoPermissionBroadcastReceiver.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoPermissionBroadcastReceiver.kt index dda0f115..d70301ee 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoPermissionBroadcastReceiver.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoPermissionBroadcastReceiver.kt @@ -8,6 +8,8 @@ import android.hardware.usb.UsbManager import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import org.kabiri.android.usbterminal.Constants import org.kabiri.android.usbterminal.R @@ -22,15 +24,15 @@ class ArduinoPermissionBroadcastReceiver: BroadcastReceiver() { private const val TAG = "ArduinoPermReceiver" } - private val _liveOutput = MutableLiveData() - private val _liveInfoOutput = MutableLiveData() - private val _liveErrorOutput = MutableLiveData() + private val _liveOutput = MutableStateFlow("") + private val _liveInfoOutput = MutableStateFlow("") + private val _liveErrorOutput = MutableStateFlow("") - val liveOutput: LiveData + val liveOutput: StateFlow get() = _liveOutput - val liveInfoOutput: LiveData + val liveInfoOutput: StateFlow get() = _liveInfoOutput - val liveErrorOutput: LiveData + val liveErrorOutput: StateFlow get() = _liveErrorOutput private val _liveGrantedDevice = MutableLiveData() @@ -46,20 +48,20 @@ class ArduinoPermissionBroadcastReceiver: BroadcastReceiver() { .getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) if (permissionGranted) { - _liveInfoOutput.postValue( + _liveInfoOutput.value = "${context?.getString(R.string.breceiver_info_usb_permission_granted)} ${device?.manufacturerName}" - ) Log.i(TAG, "USB permission granted by the user") device?.let { _liveGrantedDevice.postValue(it) } } else { Log.e(TAG, "USB permission was probably denied by the user") - _liveErrorOutput.postValue( + _liveErrorOutput.value = "${context?.getString(R.string.breceiver_error_usb_permission_denied)} ${device?.manufacturerName}" - ) } } - UsbManager.ACTION_USB_DEVICE_ATTACHED -> _liveOutput.postValue(context?.getString(R.string.breceiver_info_device_attached)) - UsbManager.ACTION_USB_DEVICE_DETACHED -> _liveOutput.postValue(context?.getString(R.string.breceiver_info_device_detached)) + UsbManager.ACTION_USB_DEVICE_ATTACHED -> _liveOutput.value = + context?.getString(R.string.breceiver_info_device_attached) ?: "" + UsbManager.ACTION_USB_DEVICE_DETACHED -> _liveOutput.value = + context?.getString(R.string.breceiver_info_device_detached) ?: "" } } } \ No newline at end of file diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoSerialReceiver.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoSerialReceiver.kt index b990c443..cea3438b 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoSerialReceiver.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoSerialReceiver.kt @@ -1,9 +1,9 @@ package org.kabiri.android.usbterminal.arduino import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import com.felhr.usbserial.UsbSerialInterface +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import java.io.UnsupportedEncodingException import java.nio.charset.Charset @@ -18,15 +18,15 @@ class ArduinoSerialReceiver: UsbSerialInterface.UsbReadCallback { private const val TAG = "ArduinoSerialReceiver" } - private val _liveOutput = MutableLiveData() - private val _liveInfoOutput = MutableLiveData() - private val _liveErrorOutput = MutableLiveData() + private val _liveOutput = MutableStateFlow("") + private val _liveInfoOutput = MutableStateFlow("") + private val _liveErrorOutput = MutableStateFlow("") - val liveOutput: LiveData + val liveOutput: StateFlow get() = _liveOutput - val liveInfoOutput: LiveData + val liveInfoOutput: StateFlow get() = _liveInfoOutput - val liveErrorOutput: LiveData + val liveErrorOutput: StateFlow get() = _liveErrorOutput override fun onReceivedData(message: ByteArray?) { @@ -34,14 +34,14 @@ class ArduinoSerialReceiver: UsbSerialInterface.UsbReadCallback { try { // reading the message from the arduino board. val encoded = String(message, Charset.defaultCharset()) Log.i(TAG, "message from arduino: $encoded") - _liveOutput.postValue(encoded) + _liveOutput.value = encoded } catch (e: UnsupportedEncodingException) { e.printStackTrace() Log.e(TAG, "Encoding problem occurred when reading the serial message: $e") - _liveErrorOutput.postValue("\n${e.localizedMessage}") + _liveErrorOutput.value = "\n${e.localizedMessage}" } catch (e: Exception) { Log.e(TAG, "Unknown error occurred when reading the serial message: $e") - _liveErrorOutput.postValue("\n${e.localizedMessage}") + _liveErrorOutput.value = "\n${e.localizedMessage}" } } ?: run { Log.e(TAG, "Message was null") diff --git a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt index 64a668e6..222f3a48 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt @@ -3,8 +3,12 @@ package org.kabiri.android.usbterminal.viewmodel import android.hardware.usb.UsbDevice import androidx.lifecycle.* import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.kabiri.android.usbterminal.arduino.ArduinoHelper import org.kabiri.android.usbterminal.model.OutputText @@ -39,32 +43,35 @@ class MainActivityViewModel * Transforms the outputs from ArduinoHelper into spannable text * and merges them in one single live data. */ - fun getLiveOutput(): LiveData { + suspend fun getLiveOutput(): StateFlow { - val liveOutput = arduinoHelper.getLiveOutput() - val liveInfoOutput = arduinoHelper.getLiveInfoOutput() - val liveErrorOutput = arduinoHelper.getLiveErrorOutput() + val serialOutput = arduinoHelper.getLiveOutput() + val serialInfoOutput = arduinoHelper.getLiveInfoOutput() + val serialErrorOutput = arduinoHelper.getLiveErrorOutput() - val liveSpannedOutput: LiveData = Transformations.map(liveOutput) { + val liveSpannedOutput: Flow = serialOutput.map { _outputLive.value = _outputLive.value + it return@map OutputText(it, OutputText.OutputType.TYPE_NORMAL) } - val liveSpannedInfoOutput: LiveData = Transformations.map(liveInfoOutput) { + val liveSpannedInfoOutput: Flow = serialInfoOutput.map { _outputLive.value = _outputLive.value + it return@map OutputText(it, OutputText.OutputType.TYPE_INFO) } - val liveSpannedErrorOutput: LiveData = Transformations.map(liveErrorOutput) { + val liveSpannedErrorOutput: Flow = serialErrorOutput.map { _outputLive.value = _outputLive.value + it return@map OutputText(it, OutputText.OutputType.TYPE_ERROR) } - val liveDataMerger = MediatorLiveData() - liveDataMerger.addSource(liveSpannedOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(liveSpannedInfoOutput) { liveDataMerger.value = it } - liveDataMerger.addSource(liveSpannedErrorOutput) { liveDataMerger.value = it } +// val liveDataMerger = MediatorLiveData() +// liveDataMerger.addSource(liveSpannedOutput) { liveDataMerger.value = it } +// liveDataMerger.addSource(liveSpannedInfoOutput) { liveDataMerger.value = it } +// liveDataMerger.addSource(liveSpannedErrorOutput) { liveDataMerger.value = it } - return liveDataMerger + return liveSpannedOutput + .combine(liveSpannedInfoOutput) { a, b -> b } + .combine(liveSpannedErrorOutput) { a, b -> b } + .stateIn(viewModelScope) } } \ No newline at end of file From 597c5af23f3441e484793bb90cc6225747ab902f Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 22:03:12 +0200 Subject: [PATCH 052/174] refactor: format code - no changes --- .../android/usbterminal/arduino/ArduinoHelper.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt index b0649df4..cc2c4792 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt @@ -60,12 +60,14 @@ class ArduinoHelper } else { _liveErrorOutput.value = context.getString(R.string.helper_error_device_not_found) - _liveErrorOutput.value = context.getString(R.string.helper_error_connecting_anyway) + _liveErrorOutput.value = + context.getString(R.string.helper_error_connecting_anyway) connect(device) } } } else { - _liveErrorOutput.value = context.getString(R.string.helper_error_usb_devices_not_attached) + _liveErrorOutput.value = + context.getString(R.string.helper_error_usb_devices_not_attached) } } @@ -116,7 +118,8 @@ class ArduinoHelper serialPort = UsbSerialDevice.createUsbSerialDevice(device, connection) } catch (e: IllegalStateException) { Log.e(TAG, "${e.message}") - _liveErrorOutput.value = context.getString(R.string.helper_error_connection_closed_unexpectedly) + _liveErrorOutput.value = + context.getString(R.string.helper_error_connection_closed_unexpectedly) } catch (e: NullPointerException) { Log.e(TAG, "${e.message}") _liveErrorOutput.value = context.getString( @@ -149,7 +152,8 @@ class ArduinoHelper it.setParity(UsbSerialInterface.PARITY_NONE) it.setFlowControl(UsbSerialInterface.FLOW_CONTROL_OFF) it.read(arduinoSerialReceiver) // messages will be received from the receiver. - _liveInfoOutput.value = context.getString(R.string.helper_info_serial_connection_opened) + _liveInfoOutput.value = + context.getString(R.string.helper_info_serial_connection_opened) } else { // serial port not opened. _liveErrorOutput.value = context.getString(R.string.helper_error_serial_connection_not_opened) @@ -168,7 +172,8 @@ class ArduinoHelper _liveOutput.value = context.getString(R.string.next_line) true } else { - _liveErrorOutput.value = context.getString(R.string.helper_error_serial_port_is_null) + _liveErrorOutput.value = + context.getString(R.string.helper_error_serial_port_is_null) false } } catch (e: Exception) { From ca1ef487a0dac520893540e27488b60e0e56de05 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 23:02:51 +0200 Subject: [PATCH 053/174] refactor: no changes --- .../usbterminal/arduino/ArduinoHelper.kt | 36 ++++++++----------- .../viewmodel/MainActivityViewModel.kt | 6 ++-- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt index cc2c4792..d7dba16c 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt @@ -38,8 +38,22 @@ class ArduinoHelper } private val _liveOutput = MutableStateFlow("") + val output: Flow + get() = _liveOutput + .combine(arduinoPermReceiver.liveOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveOutput) { a, b -> a + b } + private val _liveInfoOutput = MutableStateFlow("") + val infoOutput: Flow + get() = _liveInfoOutput + .combine(arduinoPermReceiver.liveInfoOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveInfoOutput) { a, b -> a + b } + private val _liveErrorOutput = MutableStateFlow("") + val errorOutput: Flow + get() = _liveErrorOutput + .combine(arduinoPermReceiver.liveErrorOutput) { a, b -> a + b } + .combine(arduinoSerialReceiver.liveErrorOutput) { a, b -> a + b } private var usbManager: UsbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager private lateinit var connection: UsbDeviceConnection @@ -183,26 +197,4 @@ class ArduinoHelper false } } - - fun getLiveOutput(): Flow { - - return _liveOutput - .combine(arduinoPermReceiver.liveOutput) { a, b -> a + b } - .combine(arduinoSerialReceiver.liveOutput) { a, b -> a + b } - } - - fun getLiveInfoOutput(): Flow { - - return _liveInfoOutput - .combine(arduinoPermReceiver.liveInfoOutput) { a, b -> a + b } - .combine(arduinoSerialReceiver.liveInfoOutput) { a, b -> a + b } - } - - fun getLiveErrorOutput(): Flow { - - return _liveErrorOutput - .combine(arduinoPermReceiver.liveErrorOutput) { a, b -> a + b } - .combine(arduinoSerialReceiver.liveErrorOutput) { a, b -> a + b } - } - } \ No newline at end of file diff --git a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt index 222f3a48..f4ede830 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/viewmodel/MainActivityViewModel.kt @@ -45,9 +45,9 @@ class MainActivityViewModel */ suspend fun getLiveOutput(): StateFlow { - val serialOutput = arduinoHelper.getLiveOutput() - val serialInfoOutput = arduinoHelper.getLiveInfoOutput() - val serialErrorOutput = arduinoHelper.getLiveErrorOutput() + val serialOutput = arduinoHelper.output + val serialInfoOutput = arduinoHelper.infoOutput + val serialErrorOutput = arduinoHelper.errorOutput val liveSpannedOutput: Flow = serialOutput.map { _outputLive.value = _outputLive.value + it From 81c5a26611564ab59dd899f68e8a57ed59644db0 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Tue, 18 Jul 2023 23:13:23 +0200 Subject: [PATCH 054/174] fix: clicking connect button multiple times outputs error regardless of the last message --- .../java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt index d7dba16c..2e492e78 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/arduino/ArduinoHelper.kt @@ -80,6 +80,7 @@ class ArduinoHelper } } } else { + _liveErrorOutput.value = "" _liveErrorOutput.value = context.getString(R.string.helper_error_usb_devices_not_attached) } From 550ba01703ec527d6ae1cd1168a0d21632fc6620 Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Wed, 19 Jul 2023 05:12:12 +0200 Subject: [PATCH 055/174] chore: update dependencies and add jetpack compse --- app/build.gradle.kts | 29 ++++++++++++++++++++++++----- build.gradle | 6 +++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 253fb968..b271179a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -22,6 +22,14 @@ android { targetCompatibility = JavaVersion.VERSION_1_8 } + buildFeatures { + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = "1.4.4" + } + defaultConfig { applicationId = "org.kabiri.android.usbterminal" minSdk = 23 @@ -168,8 +176,8 @@ val firebase_bom_version: String by project val hilt_version: String by project dependencies { - implementation("androidx.appcompat:appcompat:1.6.0") - implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("androidx.core:core-ktx:1.10.1") implementation("androidx.constraintlayout:constraintlayout:2.1.4") // Firebase @@ -182,13 +190,24 @@ dependencies { kapt("com.google.dagger:hilt-compiler:$hilt_version") // Coroutines - implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1") - implementation("androidx.activity:activity-compose:1.6.1") - implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") + implementation("androidx.activity:activity-compose:1.7.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1") implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") + // Compose Bom + val composeBom = platform("androidx.compose:compose-bom:2023.06.01") + implementation(composeBom) + androidTestImplementation(composeBom) + implementation("androidx.compose.foundation:foundation") + implementation("androidx.compose.material3:material3") + // Compose - Android Studio Preview support + implementation("androidx.compose.ui:ui-tooling-preview") + debugImplementation("androidx.compose.ui:ui-tooling") + implementation("androidx.activity:activity-compose:1.7.1") + // hilt testing // more info: // https://developer.android.com/training/dependency-injection/hilt-testing diff --git a/build.gradle b/build.gradle index 09ce2b2e..1bf710b7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - kotlin_version = '1.7.10' + kotlin_version = '1.8.10' hilt_version = '2.44' firebase_bom_version = '30.5.0' jacoco_version = '0.8.8' @@ -8,13 +8,13 @@ buildscript { dependencies { classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "com.google.gms:google-services:4.3.15" - classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.4" + classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.7" } } plugins { id("com.android.application") version "7.4.2" apply false - id("org.jetbrains.kotlin.android") version "1.7.0" apply false + id("org.jetbrains.kotlin.android") version "1.8.10" apply false id("org.sonarqube") version "3.5.0.2730" } From 52b6e1ace73b6debb975770793f16d1244febd3b Mon Sep 17 00:00:00 2001 From: Ali Kabiri Date: Wed, 19 Jul 2023 05:14:11 +0200 Subject: [PATCH 056/174] feature: add a compose view to load a snapshot list of OutputText --- .../android/usbterminal/MainActivity.kt | 21 +++++++++++++++++++ .../viewmodel/MainActivityViewModel.kt | 20 +++++++++++------- app/src/main/res/layout/activity_main.xml | 11 +++++++++- app/src/main/res/menu/activity_main_menu.xml | 1 + app/src/main/res/values/strings.xml | 1 + 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt index 44607fe1..2989a160 100644 --- a/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt +++ b/app/src/main/java/org/kabiri/android/usbterminal/MainActivity.kt @@ -12,6 +12,12 @@ import android.widget.EditText import android.widget.TextView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.BasicText +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import org.kabiri.android.usbterminal.extensions.scrollToLastLine @@ -33,6 +39,21 @@ class MainActivity : AppCompatActivity() { val etInput = findViewById(R.id.etInput) val tvOutput = findViewById(R.id.tvOutput) val btEnter = findViewById