diff --git a/.drone.yml b/.drone.yml index a776c686d8..def510d77c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,7 +5,7 @@ name: tests-stable steps: - name: gplay - image: ghcr.io/nextcloud/continuous-integration-android8:2 + image: ghcr.io/nextcloud/continuous-integration-android8:3 privileged: true environment: LOG_USERNAME: @@ -65,7 +65,7 @@ name: tests-master steps: - name: gplay - image: ghcr.io/nextcloud/continuous-integration-android8:2 + image: ghcr.io/nextcloud/continuous-integration-android8:3 privileged: true environment: LOG_USERNAME: @@ -120,7 +120,7 @@ name: allScreenshots steps: - name: runAllScreenshots - image: ghcr.io/nextcloud/continuous-integration-android8:2 + image: ghcr.io/nextcloud/continuous-integration-android8:3 privileged: true environment: GIT_USERNAME: diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index 5b3a34c67e..df2d496e2e 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -32,11 +32,11 @@ jobs: with: repository: ${{ steps.get-vars.outputs.repo }} ref: ${{ steps.get-vars.outputs.branch }} - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0 with: distribution: "temurin" - java-version: 11 + java-version: 17 - name: Install dependencies run: | python3 -m pip install defusedxml diff --git a/.github/workflows/assembleFlavors.yml b/.github/workflows/assembleFlavors.yml index 1bb38f0940..303e54e6a5 100644 --- a/.github/workflows/assembleFlavors.yml +++ b/.github/workflows/assembleFlavors.yml @@ -16,11 +16,11 @@ jobs: flavor: [ Generic, Gplay, Huawei ] steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3 - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3 with: distribution: "temurin" - java-version: 11 + java-version: 17 - name: Build ${{ matrix.flavor }} run: | echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" >> gradle.properties diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d614731360..83adef01a6 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -16,10 +16,10 @@ jobs: task: [ detekt, spotlessKotlinCheck ] steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3 with: distribution: "temurin" - java-version: 11 + java-version: 17 - name: Check ${{ matrix.task }} run: ./gradlew ${{ matrix.task }} diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 80ff7a1275..64402bd599 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -17,12 +17,12 @@ jobs: id: check-secrets - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3 if: ${{ steps.check-secrets.outputs.ok == 'true' }} - - name: set up JDK 11 + - name: set up JDK 17 uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3 if: ${{ steps.check-secrets.outputs.ok == 'true' }} with: distribution: "temurin" - java-version: 11 + java-version: 17 - name: Install NDK and cmake if: ${{ steps.check-secrets.outputs.ok == 'true' }} run: | diff --git a/.github/workflows/screenShotTest.yml b/.github/workflows/screenShotTest.yml index 0ebcab45dc..6f10820b62 100644 --- a/.github/workflows/screenShotTest.yml +++ b/.github/workflows/screenShotTest.yml @@ -39,7 +39,7 @@ jobs: - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3 with: distribution: "temurin" - java-version: 11 + java-version: 17 - name: create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 854c9c26b3..854a046c1c 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -15,11 +15,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0 with: distribution: "temurin" - java-version: 11 + java-version: 17 - name: Delete old comments env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/app/build.gradle b/app/build.gradle index da7fa01d96..acb1b2b9c5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,6 @@ import org.gradle.internal.jvm.Jvm buildscript { dependencies { classpath "com.android.tools.build:gradle:$androidPluginVersion" - classpath 'com.hiya:jacoco-android:0.2' classpath 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.14' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.0" @@ -27,7 +26,7 @@ apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-parcelize' apply plugin: 'checkstyle' apply plugin: 'pmd' -apply plugin: 'com.hiya.jacoco-android' +apply from: "$rootProject.projectDir/jacoco.gradle" apply plugin: 'com.github.spotbugs' apply plugin: 'io.gitlab.arturbosch.detekt' apply plugin: 'shot' @@ -208,10 +207,16 @@ android { buildFeatures { dataBinding true viewBinding true + aidl true + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } lint { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 70b85191b4..1c6c33bd8a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ + diff --git a/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt index e0c052071c..3e57d5ba9c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt @@ -22,6 +22,7 @@ package com.nextcloud.client.jobs +import android.accounts.Account import android.app.NotificationManager import android.app.PendingIntent import android.content.Context @@ -37,6 +38,7 @@ import com.nextcloud.client.device.PowerManagementService import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.utils.FileUploaderDelegate import com.owncloud.android.R +import com.owncloud.android.authentication.AuthenticatorActivity import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.datamodel.UploadsStorageManager @@ -45,13 +47,17 @@ import com.owncloud.android.lib.common.OwnCloudAccount import com.owncloud.android.lib.common.OwnCloudClientManagerFactory import com.owncloud.android.lib.common.network.OnDatatransferProgressListener import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.files.FileUtils import com.owncloud.android.operations.UploadFileOperation +import com.owncloud.android.ui.activity.ConflictsResolveActivity import com.owncloud.android.ui.activity.UploadListActivity import com.owncloud.android.ui.notifications.NotificationUtils +import com.owncloud.android.utils.ErrorMessageAdapter import com.owncloud.android.utils.theme.ViewThemeUtils import java.io.File +import java.security.SecureRandom @Suppress("LongParameterList") class FilesUploadWorker( @@ -168,6 +174,9 @@ class FilesUploadWorker( } finally { uploadsStorageManager.updateDatabaseUploadResult(uploadResult, uploadFileOperation) + // / notify result + notifyUploadResult(uploadFileOperation, uploadResult) + // cancel notification notificationManager.cancel(FOREGROUND_SERVICE_ID) } @@ -220,6 +229,113 @@ class FilesUploadWorker( // TODO generalize for automated uploads } + /** + * adapted from [com.owncloud.android.files.services.FileUploader.notifyUploadResult] + */ + private fun notifyUploadResult( + uploadFileOperation: UploadFileOperation, + uploadResult: RemoteOperationResult + ) { + Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.code) + // Only notify if the upload fails + if (uploadResult.isSuccess || uploadResult.isCancelled) { + return + } + + val notDelayed = uploadResult.code != ResultCode.DELAYED_FOR_WIFI && + uploadResult.code != ResultCode.DELAYED_FOR_CHARGING && + uploadResult.code != ResultCode.DELAYED_IN_POWER_SAVE_MODE + + if (notDelayed && + uploadResult.code != ResultCode.LOCAL_FILE_NOT_FOUND && + uploadResult.code != ResultCode.LOCK_FAILED + ) { + var tickerId = R.string.uploader_upload_failed_ticker + + // check credentials error + val needsToUpdateCredentials = uploadResult.code == ResultCode.UNAUTHORIZED + if (needsToUpdateCredentials) { + tickerId = R.string.uploader_upload_failed_credentials_error + } else if (uploadResult.code == ResultCode.SYNC_CONFLICT) { + // check file conflict + tickerId = R.string.uploader_upload_failed_sync_conflict_error + } + notificationBuilder + .setTicker(context.getString(tickerId)) + .setContentTitle(context.getString(tickerId)) + .setAutoCancel(true) + .setOngoing(false) + .setProgress(0, 0, false) + + val content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, uploadFileOperation, context.resources) + + if (needsToUpdateCredentials) { + createUpdateCredentialsNotification(uploadFileOperation.user.toPlatformAccount()) + } else { + val intent = if (uploadResult.code == ResultCode.SYNC_CONFLICT) { + createResolveConflictIntent(uploadFileOperation) + } else { + createUploadListIntent(uploadFileOperation) + } + notificationBuilder.setContentIntent( + PendingIntent.getActivity( + context, + System.currentTimeMillis().toInt(), + intent, + PendingIntent.FLAG_IMMUTABLE + ) + ) + } + notificationBuilder.setContentText(content) + if (!uploadResult.isSuccess) { + notificationManager.notify(SecureRandom().nextInt(), notificationBuilder.build()) + } + } + } + + private fun createUploadListIntent(uploadFileOperation: UploadFileOperation): Intent { + return UploadListActivity.createIntent( + uploadFileOperation.file, + uploadFileOperation.user, + Intent.FLAG_ACTIVITY_CLEAR_TOP, + context + ) + } + + private fun createResolveConflictIntent(uploadFileOperation: UploadFileOperation): Intent { + return ConflictsResolveActivity.createIntent( + uploadFileOperation.file, + uploadFileOperation.user, + uploadFileOperation.ocUploadId, + Intent.FLAG_ACTIVITY_CLEAR_TOP, + context + ) + } + + private fun createUpdateCredentialsNotification(account: Account) { + // let the user update credentials with one click + val updateAccountCredentials = Intent(context, AuthenticatorActivity::class.java) + updateAccountCredentials.putExtra( + AuthenticatorActivity.EXTRA_ACCOUNT, + account + ) + updateAccountCredentials.putExtra( + AuthenticatorActivity.EXTRA_ACTION, + AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN + ) + updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND) + notificationBuilder.setContentIntent( + PendingIntent.getActivity( + context, + System.currentTimeMillis().toInt(), + updateAccountCredentials, + PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE + ) + ) + } + /** * see [com.owncloud.android.files.services.FileUploader.onTransferProgress] */ diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 6060cba5a5..6cbd269b7d 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -139,7 +139,7 @@ Hent kanditat til frigivelse fra Google Play store Release candidate Denne kandidat til frigivelse (RC) er et snapshot af den kommende version og forventes at være stabil. Testing af dit individuelle miljø kan hjælpe med at sikre dette. Indskriv dig til testing i Play Store eller led manuelt i \"Version\"s afdelingen af F-Droid. - Fundet en bug? Mærkværdighed? + Fundet en fejl? Mærkværdighed? Hjælp med at test Rapporter et problem på Github Konfigurer @@ -360,7 +360,7 @@ Filnavnet indeholder mindst ét ugyldigt tegn Filnavn Bevar dine data i sikkerhed og under din kontrol - Sikker kollaboration& fil udveksling + Sikker kollaboration & fil udveksling Nemt at bruge web mail, kalender & kontakter Skærmdeling, onlinemøder & web konferencer Mappe findes allerede @@ -509,6 +509,8 @@ Afvis Yderligere tilladelser nødvendige for at hente og sende filer. Ingen apps fundet til at vælge et billede med + Fastgør til hjemmeskærm + Åbn %1$s 389 KB pladsholder.txt 12:23:45 @@ -910,6 +912,10 @@ Enheds legitimationsoplysninger er sat op Fundet %d dubletpost. Fundet %d duplikerede poster. + + Eksporteret %d fil + Eksporteret %d filer + %1$d mappe %1$d mapper diff --git a/app/src/main/res/values-es-rEC/strings.xml b/app/src/main/res/values-es-rEC/strings.xml index 87c74fa0bc..066dea31c3 100644 --- a/app/src/main/res/values-es-rEC/strings.xml +++ b/app/src/main/res/values-es-rEC/strings.xml @@ -139,6 +139,7 @@ Se presentó un error al intentar copiar este archivo o carpeta No es posible copiar una carpeta dentro de una de sus sub carpetas El archivo ya existe en la carpeta destino + Imposible copiar. Por favor, compruebe si existe el archivo. Copiar liga Copiar/ mover a la carpeta encriptada no se encuentra soportado por el momento. Copiar a… @@ -280,6 +281,7 @@ No se encontró el archivo en el sistema de archivos local No hay más carpetas.  %1$s bitácora de aplicación Android + No se ha encontrado una app para enviar los registros. Por favor, instale un cliente de correo electrónico. Iniciar sesión Actualizar Cargando... @@ -363,6 +365,7 @@ Licencia Ninguno Administrar cuentas + Recomendar a un amigo Mostrar archivos ocultos Obtener el código fuente Carpeta local @@ -540,6 +543,7 @@ Virus detectado. ¡La carga no puede ser completada! Esperando a salir de modo de conservación de energía Aguardando la recarga del dispositivo + Esperando Wi-Fi no medido Usuario Dirección Correo electrónico diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 08c3bd98b3..a0668bed9f 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -524,6 +524,7 @@ Parola kodu silindi Parola depolandı Parola yanlış + Parola ile korunmuş PDF dosyaları açılamıyor. Lütfen bir dış PDF görüntüleyici kullanın. Yakınlaştırmak için sayfanın üzerine dokunun İzin ver Reddet diff --git a/appscan/build.gradle b/appscan/build.gradle index 5b6b13d318..3e40abb952 100644 --- a/appscan/build.gradle +++ b/appscan/build.gradle @@ -24,12 +24,14 @@ android { minifyEnabled false } } + compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } + kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } } diff --git a/build.gradle b/build.gradle index 8a6f4a3822..9dff784ad4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext { - androidPluginVersion = '7.4.2' + androidPluginVersion = '8.0.2' appCompatVersion = '1.6.1' jacoco_version = '0.8.10' kotlin_version = '1.8.21' diff --git a/gradle.properties b/gradle.properties index 94c2eed660..2577eff5cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,7 @@ NC_TEST_SERVER_USERNAME=test NC_TEST_SERVER_PASSWORD=test android.enableJetifier=true android.useAndroidX=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false #android.debug.obsoleteApi=true diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e749e03625..e0a910084b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Jan 13 08:21:45 CET 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/jacoco.gradle b/jacoco.gradle new file mode 100644 index 0000000000..0106717573 --- /dev/null +++ b/jacoco.gradle @@ -0,0 +1,109 @@ +apply plugin: 'jacoco' + +jacoco { + toolVersion = "$jacoco_version" +} + +// Force Jacoco Version + +subprojects { + configurations.all { + resolutionStrategy { + eachDependency { details -> + if ('org.jacoco' == details.requested.group) { + details.useVersion "$jacocoVersion" + } + } + } + } +} + +project.afterEvaluate { project -> + + tasks.withType(Test) { + jacoco.includeNoLocationClasses = true + jacoco.excludes = ['jdk.internal.*'] + } + + final flavor = "Gplay" + final buildType = "Debug" + final variant = "$flavor${buildType.capitalize()}" + final taskName = "jacocoTest${variant.capitalize()}UnitTestReport" + + task "$taskName"(type: JacocoReport, dependsOn: "test${variant.capitalize()}UnitTest") { + + reports { + csv.required = Boolean.FALSE + xml.required = Boolean.TRUE + html.required = Boolean.TRUE + } + + final fileFilter = [ + // data binding + '**/databinding/*', + 'android/databinding/**/*.class', + '**/android/databinding/*Binding.class', + '**/android/databinding/*', + '**/androidx/databinding/*', + '**/BR.*', + // android + '**/R.class', + '**/R$*.class', + '**/BuildConfig.*', + '**/Manifest*.*', + '**/*Test*.*', + 'android/**/*.*', + // kotlin + '**/*MapperImpl*.*', + '**/*$ViewInjector*.*', + '**/*$ViewBinder*.*', + '**/BuildConfig.*', + '**/*Component*.*', + '**/*BR*.*', + '**/Manifest*.*', + '**/*$Lambda$*.*', + '**/*Companion*.*', + '**/*Module*.*', + '**/*Dagger*.*', + '**/*Hilt*.*', + '**/*MembersInjector*.*', + '**/*_MembersInjector.class', + '**/*_Factory*.*', + '**/*_Provide*Factory*.*', + '**/*Extensions*.*', + // sealed and data classes + '**/*$Result.*', + '**/*$Result$*.*', + // adapters generated by moshi + '**/*JsonAdapter.*', + // Hilt + '**/*Module.kt', + '**/di/**', + 'dagger.hilt.internal/*', + 'hilt_aggregated_deps/*', + + '**/*$Result.*', /* filtering `sealed` and `data` classes */ + '**/*$Result$*.*',/* filtering `sealed` and `data` classes */ + '**/*Args*.*', /* filtering Navigation Component generated classes */ + '**/*Directions*.*', /* filtering Navigation Component generated classes */ + '**/*inlined*.class', /* filtering inlined classes */ + '**/composables/**' + /* INSERT ANY OTHER JUNK YOU WANT FILTERED OUT HERE */ + ] + + final androidKotlinTree = fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/${variant}", excludes: fileFilter) + final kotlinTree = fileTree(dir: "${project.buildDir}/classes/kotlin/main", excludes: fileFilter) + final javacTree = fileTree(dir: "${project.buildDir}/intermediates/javac/${variant}/classes", excludes: fileFilter) + + final mainSrc = "${project.projectDir}/src/main/java" + final productFlavorSrc = "${project.projectDir}/src/${flavor}/java" + final buildTypeSrc = "${project.projectDir}/src/${buildType}/java" + + sourceDirectories.setFrom files([mainSrc, productFlavorSrc, buildTypeSrc]) + classDirectories.setFrom files([androidKotlinTree, kotlinTree, javacTree]) + executionData.setFrom fileTree(dir: project.buildDir, includes: [ + "jacoco/test${variant.capitalize()}UnitTest.exec", + "outputs/unit_test_code_coverage/${variant}UnitTest/test${variant.capitalize()}UnitTest.exec", + ]) + } +}