diff --git a/.github/workflows/android-test.yml b/.github/workflows/android-test.yml new file mode 100644 index 0000000..e499e2f --- /dev/null +++ b/.github/workflows/android-test.yml @@ -0,0 +1,99 @@ +name: Android Emulator Tests +on: [ push, pull_request ] + +jobs: + check-if-tests-exist: + runs-on: ubuntu-latest + outputs: + status: ${{ steps.check-androidTest.outputs.NOT_EMPTY }} + min-sdk-version: ${{ steps.get-sdk-version.outputs.MIN_SDK_VERSION }} + target-sdk-version: ${{ steps.get-sdk-version.outputs.TARGET_SDK_VERSION }} + app-id: ${{ steps.get-app-id.outputs.APP_ID }} + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: "recursive" + - name: Check if androidTest folder is not empty + run: | + echo "NOT_EMPTY=$([ "$(ls -A app/src/androidTest)" ] && echo 'true' || echo 'false')" + echo "NOT_EMPTY=$([ "$(ls -A app/src/androidTest)" ] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT + id: check-androidTest + - name: Get min and target sdk + if: steps.check-androidTest.outputs.NOT_EMPTY == 'true' + id: get-sdk-version + run: | + echo "MIN_SDK_VERSION=$(cat app/build.gradle | grep minSdkVersion | rev | cut -d' ' -f 1 | rev)" >> $GITHUB_OUTPUT + echo "TARGET_SDK_VERSION=$(cat app/build.gradle | grep targetSdkVersion | rev | cut -d' ' -f 1 | rev)" >> $GITHUB_OUTPUT + - name: Get app ID + id: get-app-id + run: | + echo "APP_ID=$(cat app/build.gradle | grep applicationId | rev | cut -d' ' -f 1 | rev | tr -d '"')" >> $GITHUB_OUTPUT + + test: + needs: check-if-tests-exist + if: needs.check-if-tests-exist.outputs.status == 'true' + runs-on: ubuntu-latest + strategy: + matrix: + api-level: [34, "${{ needs.check-if-tests-exist.outputs.min-sdk-version }}", "${{ needs.check-if-tests-exist.outputs.target-sdk-version }}"] + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: 'recursive' + + - name: Enable KVM group perms + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Gradle cache + uses: gradle/gradle-build-action@v3 + + - name: AVD cache + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: Set up JDK environment + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: 17 + + - name: create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + target: ${{ matrix.api-level >= 30 && 'google_apis' || 'default' }} + arch: ${{ matrix.api-level < 21 && 'x86' || 'x86_64' }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: Run connected tests + uses: ReactiveCircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + target: ${{ matrix.api-level >= 30 && 'google_apis' || 'default' }} + arch: ${{ matrix.api-level < 21 && 'x86' || 'x86_64' }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: | + adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}} || true + adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}}.test || true + adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}}.androidTest || true + chmod +x gradlew + ./gradlew :app:connectedCheck --stacktrace + adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}} || true + adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}}.test || true + adb uninstall ${{needs.check-if-tests-exist.outputs.app-id}}.androidTest || true diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index ad4099a..6e50db1 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -2,7 +2,7 @@ name: Changelog Generation on: release: - types: [published, released] + types: [published] workflow_dispatch: jobs: @@ -16,6 +16,7 @@ jobs: - uses: rhysd/changelog-from-release/action@v3 with: file: CHANGELOG.md + pull_request: true github_token: ${{ secrets.GITHUB_TOKEN }} commit_summary_template: 'update changelog for %s changes' args: -l 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fe5b0a9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,67 @@ +name: Continuous Integration +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: "recursive" + + - name: Set up JDK environment + uses: actions/setup-java@v3 + with: + distribution: "zulu" + java-version: 17 + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Run local unit tests + run: bash ./gradlew test --stacktrace + + build: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: "recursive" + + - name: Set up JDK environment + uses: actions/setup-java@v3 + with: + distribution: "zulu" + java-version: 17 + + - name: Make gradlew executable + run: chmod +x ./gradlew + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - name: Run lint check + run: bash ./gradlew lint + + - name: Upload lint result + uses: actions/upload-artifact@v4 + with: + name: lint-results-debug + path: app/build/reports/lint-results-debug.html + + - name: Build the app + run: bash ./gradlew build --stacktrace + + - name: Build debug apk + run: bash ./gradlew assembleDebug + + - name: Upload debug apk + uses: actions/upload-artifact@v4 + with: + name: debug-apk + path: app/build/outputs/apk/debug/*.apk \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index b80fa2c..5fb4a5c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,5 +1,3 @@ [submodule "libs/privacy-friendly-backup-api"] path = libs/privacy-friendly-backup-api - url = git@github.com:SecUSo/privacy-friendly-backup-api.git -[submodule "libs/privacy-friendly-backup-api/"] url = https://github.com/SecUSo/privacy-friendly-backup-api.git diff --git a/app/build.gradle b/app/build.gradle index 72e29ac..36467a6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,13 +2,13 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 33 defaultConfig { applicationId 'org.secuso.privacyfriendlytapemeasure' minSdkVersion 21 - targetSdkVersion 33 - versionCode 4 - versionName '1.1' + compileSdk 34 + targetSdkVersion 34 + versionCode 5 + versionName '1.1.1' } buildTypes { release { @@ -18,32 +18,30 @@ android { } productFlavors { } + namespace 'org.secuso.privacyfriendlycameraruler' + lint { + lintConfig = file("lint.xml") + } + kotlin { + jvmToolchain(17) + } } dependencies { - testImplementation 'junit:junit:4.12' - implementation 'androidx.appcompat:appcompat:1.0.0' + testImplementation 'junit:junit:4.13.2' + implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.drawerlayout:drawerlayout:1.2.0' - implementation 'androidx.fragment:fragment:1.6.0' + implementation 'androidx.fragment:fragment-ktx:1.8.3' implementation 'androidx.viewpager:viewpager:1.0.0' - implementation 'com.google.android.material:material:1.0.0' + implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.getbase:floatingactionbutton:1.10.1' // Backup implementation project(path: ':backup-api') - def work_version = "2.4.0" + def work_version = "2.9.1" implementation "androidx.work:work-runtime:$work_version" implementation "androidx.work:work-runtime-ktx:$work_version" androidTestImplementation "androidx.work:work-testing:$work_version" - implementation 'androidx.sqlite:sqlite-ktx:2.3.1' - - constraints { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") { - because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") - } - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0") { - because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") - } - } + implementation 'androidx.sqlite:sqlite-ktx:2.4.0' } diff --git a/app/lint.xml b/app/lint.xml new file mode 100644 index 0000000..1b66a25 --- /dev/null +++ b/app/lint.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/src/androidTest/java/org/secuso/privacyfriendlycameraruler/ApplicationTest.java b/app/src/androidTest/java/org/secuso/privacyfriendlycameraruler/ApplicationTest.java deleted file mode 100644 index d705ae3..0000000 --- a/app/src/androidTest/java/org/secuso/privacyfriendlycameraruler/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.secuso.privacyfriendlycameraruler; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18d6bf5..475f3ac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> - + = Build.VERSION_CODES.M) { - // check if we have the permission we need -> if not request it and turn on the light afterwards - if (ContextCompat.checkSelfPermission(thisActivity, - Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(thisActivity, - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 0); - return; - } - } - Intent galleryIntent = new Intent(); - galleryIntent.setType("image/*"); - galleryIntent.setAction(Intent.ACTION_GET_CONTENT); - galleryIntent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(galleryIntent, PICK_IMAGE_REQUEST); + openImagePicker(); } }); @@ -258,6 +247,25 @@ public void onClick(View view) { overridePendingTransition(0, 0); } + private void openImagePicker() { + Intent getIntent = new Intent(Intent.ACTION_GET_CONTENT); + getIntent.setType("image/*"); + Intent pickIntent = new Intent(Intent.ACTION_PICK); + pickIntent.setDataAndType(android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); + Intent chooserIntent = Intent.createChooser(getIntent, getResources().getString(R.string.select_image_from_gallery)); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{pickIntent}); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, PERMISSION_READ_EXTERNAL_STORAGE_REQUEST); + } else { + startActivityForResult(chooserIntent, PICK_IMAGE_REQUEST); + } + } else { + startActivityForResult(chooserIntent, PICK_IMAGE_REQUEST); + } + } + @Override public boolean onTouchEvent(MotionEvent event) { int touchPoint = drawView.clickInTouchpoint(event); @@ -407,6 +415,20 @@ public boolean onOptionsItemSelected(MenuItem item) { return false; } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == PERMISSION_READ_EXTERNAL_STORAGE_REQUEST) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + openImagePicker(); + } else { + Toast.makeText(this, "storage permission denied", Toast.LENGTH_LONG).show(); + } + } + } + /** * Receive response from external camera and gallery apps. * @@ -416,9 +438,10 @@ public boolean onOptionsItemSelected(MenuItem item) { */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); InputStream stream = null; if (resultCode == Activity.RESULT_OK) { - if (requestCode == PICK_IMAGE_REQUEST) { + if (requestCode == PICK_IMAGE_REQUEST && data.getData() != null) { try { if (photo != null) { photo.recycle(); diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index caae844..01268e4 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -270,4 +270,5 @@ Sie können einstellen, welche Maßeinheit im Kameralineal benutzt wird. Sie können bestimmte Kategorien von vordefinierten Referenzobjekten ausblenden lassen. Diese werden dann nicht im Menü bei der Referenzobjektwahl angezeigt. Hier können Sie (bis zu 10) nutzerdefinierte Referenzobjekte anlegen und verwalten. + Bild aus der Galerie auswählen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d485007..89fe4cc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -274,4 +274,5 @@ You can set the units of measurements to be used in the camera ruler. You can disable certain types of predefined reference objects. They won\'t show up in the menu during reference object choice. Here you can define and manage user defined reference objects (up to 10). + Select image from gallery diff --git a/build.gradle b/build.gradle index 2b34868..6c9ba76 100644 --- a/build.gradle +++ b/build.gradle @@ -2,13 +2,13 @@ buildscript { repositories { - jcenter() + mavenCentral() google() } - ext.kotlin_version = "1.7.20" + ext.kotlin_version = "1.9.20" dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.6.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -18,11 +18,11 @@ buildscript { allprojects { repositories { - jcenter() + mavenCentral() google() } } -task clean(type: Delete) { +tasks.register('clean', Delete) { delete rootProject.buildDir } diff --git a/gradle.properties b/gradle.properties index 915f0e6..10c8f5e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,5 +16,8 @@ # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true +android.defaults.buildfeatures.buildconfig=true android.enableJetifier=true +android.nonFinalResIds=false +android.nonTransitiveRClass=false android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e6888f0..340dde2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/libs/privacy-friendly-backup-api b/libs/privacy-friendly-backup-api index 2548856..8687a8d 160000 --- a/libs/privacy-friendly-backup-api +++ b/libs/privacy-friendly-backup-api @@ -1 +1 @@ -Subproject commit 254885602ee4501a572f56306fbf2b3712cfc834 +Subproject commit 8687a8dbd170866707dc41bcb879375400ef697a