diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index edb6551f40..d58330a807 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -30,7 +30,7 @@ jobs: if: ${{ always() }} run: scripts/deleteOldComments.sh "test" "Unit" ${{github.event.number}} - name: Run unit tests with coverage - uses: gradle/gradle-build-action@3b1b3b9a2104c2b47fbae53f3938079c00c9bb87 # v3.0.0 + uses: gradle/gradle-build-action@29c0906b64b8fc82467890bfb7a0a7ef34bda89e # v3.1.0 with: arguments: jacocoTestGplayDebugUnitTest - name: Upload failing results diff --git a/Gemfile.lock b/Gemfile.lock index 932f0648d0..6f88626c37 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,27 +1,27 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.5) + CFPropertyList (3.0.6) rexml - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) artifactory (3.0.15) atomos (0.1.3) - aws-eventstream (1.2.0) - aws-partitions (1.604.0) - aws-sdk-core (3.131.2) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.525.0) - aws-sigv4 (~> 1.1) + aws-eventstream (1.3.0) + aws-partitions (1.890.0) + aws-sdk-core (3.191.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.651.0) + aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.57.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-kms (1.77.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.114.0) - aws-sdk-core (~> 3, >= 3.127.0) + aws-sdk-s3 (1.143.0) + aws-sdk-core (~> 3, >= 3.191.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sigv4 (1.5.0) + aws-sigv4 (~> 1.8) + aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) claide (1.1.0) @@ -30,14 +30,13 @@ GEM commander (4.6.0) highline (~> 2.0.0) declarative (0.0.20) - digest-crc (0.6.4) + digest-crc (0.6.5) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) + domain_name (0.6.20240107) + dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.92.3) - faraday (1.10.0) + excon (0.109.0) + faraday (1.10.3) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -65,8 +64,8 @@ GEM faraday-retry (1.0.3) faraday_middleware (1.2.0) faraday (~> 1.0) - fastimage (2.2.6) - fastlane (2.207.0) + fastimage (2.3.0) + fastlane (2.214.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -90,7 +89,7 @@ GEM json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) @@ -107,9 +106,9 @@ GEM xcpretty-travis-formatter (>= 0.0.3) fastlane-plugin-huawei_appgallery_connect (1.0.23) gh_inspector (1.1.3) - google-apis-androidpublisher_v3 (0.25.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-core (0.7.0) + google-apis-androidpublisher_v3 (0.54.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) googleauth (>= 0.16.2, < 2.a) httpclient (>= 2.8.1, < 3.a) @@ -117,31 +116,29 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.a) rexml - webrick - google-apis-iamcredentials_v1 (0.13.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-playcustomapp_v1 (0.10.0) - google-apis-core (>= 0.7, < 2.a) - google-apis-storage_v1 (0.18.0) - google-apis-core (>= 0.7, < 2.a) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) + 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.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.6.1) + google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) google-cloud-env (1.6.0) faraday (>= 0.17.3, < 3.0) - google-cloud-errors (1.2.0) - google-cloud-storage (1.37.0) + google-cloud-errors (1.3.1) + google-cloud-storage (1.47.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.1) + google-apis-storage_v1 (~> 0.31.0) google-cloud-core (~> 1.6) googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (1.2.0) + googleauth (1.8.1) faraday (>= 0.17.3, < 3.a) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) @@ -149,37 +146,36 @@ GEM http-cookie (1.0.5) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.6.1) - json (2.6.2) - jwt (2.4.1) - memoist (0.16.2) - mini_magick (4.11.0) - mini_mime (1.1.2) + jmespath (1.6.2) + json (2.7.1) + jwt (2.7.1) + mini_magick (4.12.0) + mini_mime (1.1.5) multi_json (1.15.0) - multipart-post (2.0.0) + multipart-post (2.4.0) nanaimo (0.3.0) naturally (2.2.1) optparse (0.1.1) os (1.1.4) - plist (3.6.0) + plist (3.7.1) public_suffix (4.0.7) - rake (13.0.6) + rake (13.1.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.5) + rexml (3.2.6) rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) security (0.1.3) - signet (0.17.0) + signet (0.19.0) addressable (~> 2.8) 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) @@ -187,17 +183,13 @@ GEM unicode-display_width (~> 1.1, >= 1.1.1) trailblazer-option (0.1.2) tty-cursor (0.7.1) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8.2) unicode-display_width (1.8.0) - webrick (1.7.0) word_wrap (1.0.0) - xcodeproj (1.22.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) diff --git a/app/build.gradle b/app/build.gradle index 6c2a6f7bb9..df34d67a5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ buildscript { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.5" classpath "commons-httpclient:commons-httpclient:3.1@jar" // remove after entire switch to lib v2 - classpath 'com.karumi:shot:5.14.1' + classpath 'com.karumi:shot:6.1.0' classpath "org.jacoco:org.jacoco.core:$jacoco_version" classpath "org.jacoco:org.jacoco.report:$jacoco_version" classpath "org.jacoco:org.jacoco.agent:$jacoco_version" @@ -291,7 +291,7 @@ dependencies { qaImplementation project(':appscan') spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0' - spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.0' + spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.4' implementation "com.google.dagger:dagger:$daggerVersion" implementation "com.google.dagger:dagger-android:$daggerVersion" diff --git a/app/screenshots/gplay/debug/com.nextcloud.client.UploadListActivityActivityIT_openDrawer.png b/app/screenshots/gplay/debug/com.nextcloud.client.UploadListActivityActivityIT_openDrawer.png index 437678bd36..10d5bac2d7 100644 Binary files a/app/screenshots/gplay/debug/com.nextcloud.client.UploadListActivityActivityIT_openDrawer.png and b/app/screenshots/gplay/debug/com.nextcloud.client.UploadListActivityActivityIT_openDrawer.png differ diff --git a/app/src/androidTest/java/com/nextcloud/extensions/BundleExtensionTests.kt b/app/src/androidTest/java/com/nextcloud/extensions/BundleExtensionTests.kt new file mode 100644 index 0000000000..f59aa377ce --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/extensions/BundleExtensionTests.kt @@ -0,0 +1,89 @@ +/* + * Nextcloud Android client application + * + * @author Alper Ozturk + * Copyright (C) 2024 Alper Ozturk + * Copyright (C) 2024 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.extensions + +import android.os.Bundle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.nextcloud.test.model.OtherTestData +import com.nextcloud.test.model.TestData +import com.nextcloud.test.model.TestDataParcelable +import com.nextcloud.utils.extensions.getParcelableArgument +import com.nextcloud.utils.extensions.getSerializableArgument +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith + +@Suppress("FunctionNaming") +@RunWith(AndroidJUnit4::class) +class BundleExtensionTests { + + private val key = "testDataKey" + + @Test + fun test_get_serializable_argument_when_given_valid_bundle_should_return_expected_data() { + val bundle = Bundle() + val testObject = TestData("Hello") + bundle.putSerializable(key, testObject) + val retrievedObject = bundle.getSerializableArgument(key, TestData::class.java) + assertEquals(testObject, retrievedObject) + } + + @Test + fun test_get_serializable_argument_when_given_valid_bundle_and_wrong_class_type_should_return_null() { + val bundle = Bundle() + val testObject = TestData("Hello") + bundle.putSerializable(key, testObject) + val retrievedObject = bundle.getSerializableArgument(key, Array::class.java) + assertNull(retrievedObject) + } + + @Test + fun test_get_parcelable_argument_when_given_valid_bundle_and_wrong_class_type_should_return_null() { + val bundle = Bundle() + val testObject = TestData("Hello") + bundle.putSerializable(key, testObject) + val retrievedObject = bundle.getParcelableArgument(key, OtherTestData::class.java) + assertNull(retrievedObject) + } + + @Test + fun test_get_parcelable_argument_when_given_valid_bundle_should_return_expected_data() { + val bundle = Bundle() + val testObject = TestDataParcelable("Hello") + bundle.putParcelable(key, testObject) + val retrievedObject = bundle.getParcelableArgument(key, TestDataParcelable::class.java) + assertEquals(testObject, retrievedObject) + } + + @Test + fun test_get_serializable_argument_when_given_null_bundle_should_return_null() { + val retrievedObject = (null as Bundle?).getSerializableArgument(key, TestData::class.java) + assertNull(retrievedObject) + } + + @Test + fun test_get_parcelable_argument_when_given_null_bundle_should_return_null() { + val retrievedObject = (null as Bundle?).getParcelableArgument(key, TestDataParcelable::class.java) + assertNull(retrievedObject) + } +} diff --git a/app/src/androidTest/java/com/nextcloud/extensions/IntentExtensionTests.kt b/app/src/androidTest/java/com/nextcloud/extensions/IntentExtensionTests.kt new file mode 100644 index 0000000000..0b7a29919b --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/extensions/IntentExtensionTests.kt @@ -0,0 +1,89 @@ +/* + * Nextcloud Android client application + * + * @author Alper Ozturk + * Copyright (C) 2024 Alper Ozturk + * Copyright (C) 2024 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.extensions + +import android.content.Intent +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.nextcloud.test.model.OtherTestData +import com.nextcloud.test.model.TestData +import com.nextcloud.test.model.TestDataParcelable +import com.nextcloud.utils.extensions.getParcelableArgument +import com.nextcloud.utils.extensions.getSerializableArgument +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Test +import org.junit.runner.RunWith + +@Suppress("FunctionNaming") +@RunWith(AndroidJUnit4::class) +class IntentExtensionTests { + + private val key = "testDataKey" + + @Test + fun test_get_serializable_argument_when_given_valid_intent_should_return_expected_data() { + val intent = Intent() + val testObject = TestData("Hello") + intent.putExtra(key, testObject) + val retrievedObject = intent.getSerializableArgument(key, TestData::class.java) + assertEquals(testObject, retrievedObject) + } + + @Test + fun test_get_serializable_argument_when_given_valid_intent_and_wrong_class_type_should_return_null() { + val intent = Intent() + val testObject = TestData("Hello") + intent.putExtra(key, testObject) + val retrievedObject = intent.getSerializableArgument(key, Array::class.java) + assertNull(retrievedObject) + } + + @Test + fun test_get_parcelable_argument_when_given_valid_intent_and_wrong_class_type_should_return_null() { + val intent = Intent() + val testObject = TestData("Hello") + intent.putExtra(key, testObject) + val retrievedObject = intent.getParcelableArgument(key, OtherTestData::class.java) + assertNull(retrievedObject) + } + + @Test + fun test_get_parcelable_argument_when_given_valid_intent_should_return_expected_data() { + val intent = Intent() + val testObject = TestDataParcelable("Hello") + intent.putExtra(key, testObject) + val retrievedObject = intent.getParcelableArgument(key, TestDataParcelable::class.java) + assertEquals(testObject, retrievedObject) + } + + @Test + fun test_get_serializable_argument_when_given_null_intent_should_return_null() { + val retrievedObject = (null as Intent?).getSerializableArgument(key, TestData::class.java) + assertNull(retrievedObject) + } + + @Test + fun test_get_parcelable_argument_when_given_null_intent_should_return_null() { + val retrievedObject = (null as Intent?).getParcelableArgument(key, TestDataParcelable::class.java) + assertNull(retrievedObject) + } +} diff --git a/app/src/androidTest/java/com/nextcloud/test/model/TestModels.kt b/app/src/androidTest/java/com/nextcloud/test/model/TestModels.kt new file mode 100644 index 0000000000..e735c16f31 --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/test/model/TestModels.kt @@ -0,0 +1,54 @@ +/* + * Nextcloud Android client application + * + * @author Alper Ozturk + * Copyright (C) 2024 Alper Ozturk + * Copyright (C) 2024 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.test.model + +import android.os.Parcel +import android.os.Parcelable +import kotlinx.parcelize.Parcelize +import java.io.Serializable + +@Parcelize +class OtherTestData : Parcelable + +data class TestData(val message: String) : Serializable + +data class TestDataParcelable(val message: String) : Parcelable { + constructor(parcel: Parcel) : this(parcel.readString() ?: "") + + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeString(message) + } + + override fun describeContents(): Int { + return 0 + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): TestDataParcelable { + return TestDataParcelable(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt index 71213ff592..ef8bfaaa98 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/FileDetailFragmentStaticServerIT.kt @@ -44,6 +44,8 @@ class FileDetailFragmentStaticServerIT : AbstractIT() { var file = getFile("gps.jpg") val oCFile = OCFile("/").apply { storagePath = file.absolutePath + fileId = 12 + fileDataStorageManager.saveFile(this) } @Test diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 99c86cbe36..c83a924b96 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -81,7 +81,6 @@ class FileUploadHelper { } } - @Suppress("ComplexCondition") fun retryFailedUploads( uploadsStorageManager: UploadsStorageManager, connectivityService: ConnectivityService, @@ -92,7 +91,44 @@ class FileUploadHelper { if (failedUploads == null || failedUploads.isEmpty()) { return } + retryUploads( + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService, + failedUploads + ) + } + fun retryCancelledUploads( + uploadsStorageManager: UploadsStorageManager, + connectivityService: ConnectivityService, + accountManager: UserAccountManager, + powerManagementService: PowerManagementService + ): Boolean { + val cancelledUploads = uploadsStorageManager.cancelledUploadsForCurrentAccount + if (cancelledUploads == null || cancelledUploads.isEmpty()) { + return false + } + + return retryUploads( + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService, + cancelledUploads + ) + } + + @Suppress("ComplexCondition") + private fun retryUploads( + uploadsStorageManager: UploadsStorageManager, + connectivityService: ConnectivityService, + accountManager: UserAccountManager, + powerManagementService: PowerManagementService, + failedUploads: Array + ): Boolean { + var showNotExistMessage = false val (gotNetwork, _, gotWifi) = connectivityService.connectivity val batteryStatus = powerManagementService.battery val charging = batteryStatus.isCharging || batteryStatus.isFull @@ -105,6 +141,8 @@ class FileUploadHelper { } val isDeleted = !File(failedUpload.localPath).exists() if (isDeleted) { + showNotExistMessage = true + // 2A. for deleted files, mark as permanently failed if (failedUpload.lastResult != UploadResult.FILE_NOT_FOUND) { failedUpload.lastResult = UploadResult.FILE_NOT_FOUND @@ -117,6 +155,8 @@ class FileUploadHelper { retryUpload(failedUpload, uploadUser.get()) } } + + return showNotExistMessage } @Suppress("LongParameterList") @@ -146,7 +186,7 @@ class FileUploadHelper { backgroundJobManager.startFilesUploadJob(user) } - fun cancelFileUpload(remotePath: String, accountName: String) { + fun removeFileUpload(remotePath: String, accountName: String) { try { val user = accountManager.getUser(accountName).get() @@ -160,6 +200,14 @@ class FileUploadHelper { } } + fun cancelFileUpload(remotePath: String, accountName: String) { + uploadsStorageManager.getUploadByRemotePath(remotePath).run { + removeFileUpload(remotePath, accountName) + uploadStatus = UploadStatus.UPLOAD_CANCELLED + uploadsStorageManager.storeUpload(this) + } + } + fun cancelAndRestartUploadJob(user: User) { backgroundJobManager.run { cancelFilesUploadJob(user) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index 91b2586ef8..35e9c83de7 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -143,6 +143,8 @@ class FileUploadWorker( val accountName = inputData.getString(ACCOUNT) ?: return Result.failure() var currentPage = uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(-1, accountName) + notificationManager.dismissWorkerNotifications() + while (currentPage.isNotEmpty() && !isStopped) { if (preferences.isGlobalUploadPaused) { Log_OC.d(TAG, "Upload is paused, skip uploading files!") diff --git a/app/src/main/java/com/nextcloud/utils/extensions/BundleExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/BundleExtensions.kt index 18ded1895a..67e86f3ea6 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/BundleExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/BundleExtensions.kt @@ -24,22 +24,49 @@ package com.nextcloud.utils.extensions import android.os.Build import android.os.Bundle import android.os.Parcelable +import com.owncloud.android.lib.common.utils.Log_OC import java.io.Serializable -fun Bundle.getSerializableArgument(key: String, type: Class): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this.getSerializable(key, type) - } else { - @Suppress("UNCHECKED_CAST", "DEPRECATION") - this.getSerializable(key) as T +@Suppress("TopLevelPropertyNaming") +private const val tag = "BundleExtension" + +fun Bundle?.getSerializableArgument(key: String, type: Class): T? { + if (this == null) { + return null + } + + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this.getSerializable(key, type) + } else { + @Suppress("UNCHECKED_CAST", "DEPRECATION") + if (type.isInstance(this.getSerializable(key))) { + this.getSerializable(key) as T + } else { + null + } + } + } catch (e: ClassCastException) { + Log_OC.e(tag, e.localizedMessage) + null } } -fun Bundle.getParcelableArgument(key: String, type: Class): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this.getParcelable(key, type) - } else { - @Suppress("DEPRECATION") - this.getParcelable(key) +fun Bundle?.getParcelableArgument(key: String, type: Class): T? { + if (this == null) { + return null + } + + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this.getParcelable(key, type) + } else { + @Suppress("DEPRECATION") + this.getParcelable(key) + } + } catch (e: ClassCastException) { + Log_OC.e(tag, e.localizedMessage) + e.printStackTrace() + null } } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/IntentExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/IntentExtensions.kt index 493c388db6..30789ba423 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/IntentExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/IntentExtensions.kt @@ -24,22 +24,48 @@ package com.nextcloud.utils.extensions import android.content.Intent import android.os.Build import android.os.Parcelable +import com.owncloud.android.lib.common.utils.Log_OC import java.io.Serializable -fun Intent.getSerializableArgument(key: String, type: Class): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this.getSerializableExtra(key, type) - } else { - @Suppress("UNCHECKED_CAST", "DEPRECATION") - this.getSerializableExtra(key) as T +@Suppress("TopLevelPropertyNaming") +private const val tag = "IntentExtension" + +fun Intent?.getSerializableArgument(key: String, type: Class): T? { + if (this == null) { + return null + } + + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this.getSerializableExtra(key, type) + } else { + @Suppress("UNCHECKED_CAST", "DEPRECATION") + if (type.isInstance(this.getSerializableExtra(key))) { + this.getSerializableExtra(key) as T + } else { + null + } + } + } catch (e: ClassCastException) { + Log_OC.e(tag, e.localizedMessage) + null } } -fun Intent.getParcelableArgument(key: String, type: Class): T? { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this.getParcelableExtra(key, type) - } else { - @Suppress("DEPRECATION") - this.getParcelableExtra(key) +fun Intent?.getParcelableArgument(key: String, type: Class): T? { + if (this == null) { + return null + } + + return try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this.getParcelableExtra(key, type) + } else { + @Suppress("DEPRECATION") + this.getParcelableExtra(key) + } + } catch (e: ClassCastException) { + Log_OC.e(tag, e.localizedMessage) + null } } diff --git a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java index be3fdf15e1..e5feb111e9 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java @@ -569,7 +569,7 @@ public class UploadsStorageManager extends Observable { } public OCUpload[] getCurrentAndPendingUploadsForAccount(final @NonNull String accountName) { - return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + + return getUploads("( " + ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_FOR_WIFI.getValue() + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + @@ -578,7 +578,7 @@ public class UploadsStorageManager extends Observable { "==" + UploadResult.DELAYED_FOR_CHARGING.getValue() + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() + - " AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", + " ) AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", accountName); } @@ -588,7 +588,7 @@ public class UploadsStorageManager extends Observable { * If afterId is -1, returns the first page */ public List getCurrentAndPendingUploadsForAccountPageAscById(final long afterId, final @NonNull String accountName) { - final String selection = ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + + final String selection = "( " + ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_IN_PROGRESS.value + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_FOR_WIFI.getValue() + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + @@ -597,7 +597,7 @@ public class UploadsStorageManager extends Observable { "==" + UploadResult.DELAYED_FOR_CHARGING.getValue() + " OR " + ProviderTableMeta.UPLOADS_LAST_RESULT + "==" + UploadResult.DELAYED_IN_POWER_SAVE_MODE.getValue() + - " AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?"; + " ) AND " + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?"; return getUploadPage(afterId, false, selection, accountName); } @@ -630,6 +630,13 @@ public class UploadsStorageManager extends Observable { ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", user.getAccountName()); } + public OCUpload[] getCancelledUploadsForCurrentAccount() { + User user = currentAccountProvider.getUser(); + + return getUploads(ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_CANCELLED.value + AND + + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", user.getAccountName()); + } + /** * Get all uploads which where successfully completed. */ @@ -700,6 +707,20 @@ public class UploadsStorageManager extends Observable { return deleted; } + public void clearCancelledUploadsForCurrentAccount() { + User user = currentAccountProvider.getUser(); + final long deleted = getDB().delete( + ProviderTableMeta.CONTENT_URI_UPLOADS, + ProviderTableMeta.UPLOADS_STATUS + "==" + UploadStatus.UPLOAD_CANCELLED.value + AND + + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "== ?", new String[]{user.getAccountName()} + ); + + Log_OC.d(TAG, "delete all cancelled uploads"); + if (deleted > 0) { + notifyObserversNow(); + } + } + public long clearSuccessfulUploads() { User user = currentAccountProvider.getUser(); final long deleted = getDB().delete( @@ -851,7 +872,12 @@ public class UploadsStorageManager extends Observable { /** * Upload was successful. */ - UPLOAD_SUCCEEDED(2); + UPLOAD_SUCCEEDED(2), + + /** + * Upload was cancelled by the user. + */ + UPLOAD_CANCELLED(3); private final int value; @@ -867,6 +893,8 @@ public class UploadsStorageManager extends Observable { return UPLOAD_FAILED; case 2: return UPLOAD_SUCCEEDED; + case 3: + return UPLOAD_CANCELLED; } return null; } diff --git a/app/src/main/java/com/owncloud/android/db/OCUpload.java b/app/src/main/java/com/owncloud/android/db/OCUpload.java index 5de0074cf9..d88a6a3f29 100644 --- a/app/src/main/java/com/owncloud/android/db/OCUpload.java +++ b/app/src/main/java/com/owncloud/android/db/OCUpload.java @@ -390,6 +390,10 @@ public class OCUpload implements Parcelable { return this.useWifiOnly; } + public boolean exists() { + return new File(localPath).exists(); + } + public boolean isWhileChargingOnly() { return this.whileChargingOnly; } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt index 40b06108b0..0b55719087 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt @@ -94,7 +94,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener Decision.CANCEL -> {} Decision.KEEP_LOCAL -> { upload?.let { - FileUploadHelper.instance().cancelFileUpload(it.remotePath, it.accountName) + FileUploadHelper.instance().removeFileUpload(it.remotePath, it.accountName) } FileUploadHelper.instance().uploadUpdatedFile( user, @@ -106,7 +106,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener Decision.KEEP_BOTH -> { upload?.let { - FileUploadHelper.instance().cancelFileUpload(it.remotePath, it.accountName) + FileUploadHelper.instance().removeFileUpload(it.remotePath, it.accountName) } FileUploadHelper.instance().uploadUpdatedFile( user, @@ -129,7 +129,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener } upload?.let { - FileUploadHelper.instance().cancelFileUpload(it.remotePath, it.accountName) + FileUploadHelper.instance().removeFileUpload(it.remotePath, it.accountName) UploadNotificationManager( applicationContext, diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java index 17350ce3fb..3e55bbdea3 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -2486,7 +2486,12 @@ public class FileDisplayActivity extends FileActivity * visibility earlier using {@link #setSortListGroup(boolean, boolean)} */ private void popSortListGroupVisibility() { - boolean popped = previousSortGroupState.pop(); - showSortListGroup(popped); + showSortListGroup(false); + + if (previousSortGroupState.isEmpty()) { + return; + } + + previousSortGroupState.pop(); } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java index 00f9e2933d..a7165f3cf8 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java @@ -74,6 +74,7 @@ import com.owncloud.android.lib.resources.shares.ShareeUser; import com.owncloud.android.operations.RefreshFolderOperation; import com.owncloud.android.operations.RemoteOperationFailedException; import com.owncloud.android.ui.activity.ComponentsGetter; +import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.fragment.SearchType; import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface; import com.owncloud.android.ui.preview.PreviewTextFragment; @@ -160,6 +161,10 @@ public class OCFileListAdapter extends RecyclerView.Adapter headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_close); - case FAILED -> headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_dots_vertical); + case CANCELLED, FAILED -> + headerViewHolder.binding.uploadListAction.setImageResource(R.drawable.ic_dots_vertical); + } headerViewHolder.binding.uploadListAction.setOnClickListener(v -> { switch (group.type) { case CURRENT -> { - // cancel all current uploads for (OCUpload upload : group.getItems()) { uploadHelper.cancelFileUpload(upload.getRemotePath(), upload.getAccountName()); } loadUploadItemsFromDb(); } case FINISHED -> { - // clear successfully uploaded section uploadsStorageManager.clearSuccessfulUploads(); loadUploadItemsFromDb(); } case FAILED -> { - // show popup with option clear or retry filed uploads - createFailedPopupMenu(headerViewHolder); + showFailedPopupMenu(headerViewHolder); + } + case CANCELLED -> { + showCancelledPopupMenu(headerViewHolder); } } }); } - private void createFailedPopupMenu(HeaderViewHolder headerViewHolder) { + private void showFailedPopupMenu(HeaderViewHolder headerViewHolder) { PopupMenu failedPopup = new PopupMenu(MainApp.getAppContext(), headerViewHolder.binding.uploadListAction); failedPopup.inflate(R.menu.upload_list_failed_options); failedPopup.setOnMenuItemClickListener(i -> { @@ -173,6 +175,47 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter { + int itemId = i.getItemId(); + + if (itemId == R.id.action_upload_list_cancelled_clear) { + uploadsStorageManager.clearCancelledUploadsForCurrentAccount(); + loadUploadItemsFromDb(); + } else if (itemId == R.id.action_upload_list_cancelled_resume) { + retryCancelledUploads(); + } + + return true; + }); + + popup.show(); + } + + private void retryCancelledUploads() { + new Thread(() -> { + boolean showNotExistMessage = FileUploadHelper.Companion.instance().retryCancelledUploads( + uploadsStorageManager, + connectivityService, + accountManager, + powerManagementService); + + parentActivity.runOnUiThread(this::loadUploadItemsFromDb); + parentActivity.runOnUiThread(() -> { + if (showNotExistMessage) { + showNotExistMessage(); + } + }); + }).start(); + } + + private void showNotExistMessage() { + DisplayUtils.showSnackMessage(parentActivity, R.string.upload_action_file_not_exist_message); + } + @Override public void onBindFooterViewHolder(SectionedViewHolder holder, int section) { // not needed @@ -197,7 +240,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter itemViewHolder.binding.uploadDate.setVisibility(View.GONE); - case UPLOAD_SUCCEEDED -> itemViewHolder.binding.uploadStatus.setVisibility(View.GONE); + case UPLOAD_SUCCEEDED, UPLOAD_CANCELLED -> + itemViewHolder.binding.uploadStatus.setVisibility(View.GONE); } - // show status if same file conflict or local file deleted - if (item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED && item.getLastResult() != UploadResult.UPLOADED) { + // show status if same file conflict or local file deleted or upload cancelled + if ((item.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED && item.getLastResult() != UploadResult.UPLOADED) + || item.getUploadStatus() == UploadStatus.UPLOAD_CANCELLED) { + itemViewHolder.binding.uploadStatus.setVisibility(View.VISIBLE); itemViewHolder.binding.uploadDate.setVisibility(View.GONE); itemViewHolder.binding.uploadFileSize.setVisibility(View.GONE); @@ -371,7 +426,9 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter { if (uploadResult == UploadResult.CREDENTIAL_ERROR) { @@ -655,8 +712,16 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter status = getUploadFailedStatusText(upload.getLastResult()); - default -> status = "Uncontrolled status: " + upload.getUploadStatus(); + case UPLOAD_FAILED -> { + status = getUploadFailedStatusText(upload.getLastResult()); + } + case UPLOAD_CANCELLED -> { + status = parentActivity.getString(R.string.upload_manually_cancelled); + } + + default -> { + status = "Uncontrolled status: " + upload.getUploadStatus(); + } } return status; } @@ -857,7 +922,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter + + + + + + diff --git a/app/src/main/res/menu/upload_list_cancelled_options.xml b/app/src/main/res/menu/upload_list_cancelled_options.xml new file mode 100644 index 0000000000..f1ae30312d --- /dev/null +++ b/app/src/main/res/menu/upload_list_cancelled_options.xml @@ -0,0 +1,32 @@ + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6fd30bc936..d1f9c83143 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -152,6 +152,7 @@ Uploads Current Failed/pending restart + Cancelled Uploaded Completed Same file found on remote, skipping upload @@ -852,6 +853,10 @@ Pause all uploads Resume all uploads Dismiss notification + Some files not exists those files cannot be resumed + Resume cancelled uploads + Clear cancelled uploads + Upload was cancelled by user Clear all notifications Loading is taking longer than expected Failed to clear notifications. diff --git a/gradle/verification-keyring.keys b/gradle/verification-keyring.keys index 2095a4621a..5c90cec442 100644 --- a/gradle/verification-keyring.keys +++ b/gradle/verification-keyring.keys @@ -2324,6 +2324,42 @@ ikmfPIGVw73RF3HXjJ8GVqTkqbo4ZpgTw/7Z3+fAYE/vxquhnpl2HvE= =5tlI -----END PGP PUBLIC KEY BLOCK----- +pub D56C721C1CFF1424 +uid Harald Kuhr + +sub 82889D6A1813F02D +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.68 + +mQENBF+242IBCADRO5E77O+3zYLz2iYkOuIfmnrx31GjdLHGzWpX5WDin/PlTGnD +uUk2JoNwFCmHgAh6waBYVnjsaRTaAaj4W/22APeEQLK1Bfi8sBn1PPKsGDf/4f6s +ZsKcJ1INPo9WsN234INyz4YkFFnI7QSeCzqWsk/l0V5vir+cEX3JjQ4nGWluA0/d +BoTXGkGTofUBUt2KflnEn4p2/CXbbwGSpLnESSBdoI0JX2uAUTZvgSnvNjTDZD5c +Yr2o4/4zv7d+3lcHBXdCanuTa1m0nxWnROizqxetilPm5jxk53uhq5zHpamdnHUp +cAif9OhXtU2iUEu13jTd9oUktgVKRTxK8qhVABEBAAG0I0hhcmFsZCBLdWhyIDxo +YXJhbGQua3VockBnbWFpbC5jb20+uQENBF+242IBCAC623pjBREUo5VKRy7fC9Y2 +q5T/bVMJRuOk0FLHNnoxapaWjqpI7lmbfHq4xeRHlTjqs1Z230cW/gIJ2GzjVx2v +ecgo9eXgQu9djl5JtEblZyNZbYrJBik4Sr60Cs5iKpMovPIrEG3xOVTiizs88ca/ +WzuWuQgOiSFyf+XUDmv3JwFbKzpt8rJla6AR0adAr2CqBTJkSf6BgWVcIuGHbWzX +Ldl/+4LNn7hTnnMM+UeaUjnu6j6XTWbf+jn0extF6xJgS1f+3WGo9WyHlh7PXHAv +otJ04dAnR0fQadjS6zNkHzTuOlB6HEacF8szibG6yGWfElPj7N20haeFWPiZ9QXN +ABEBAAGJATwEGAEIACYCGwwWIQRFPqMTKN59iqpVrU7VbHIcHP8UJAUCY3uxyAUJ +B4c1ZgAKCRDVbHIcHP8UJNr5B/4p8PxIfUoQsshBcAYun4776BfNoM7OVzHQJyqz +Q3TcJUqspyHnROXBhQay212p1ft1u10QU4gB1JsK6T31OUxzwbHSUseqxS+ZBOrJ +Vt5C+PdhuhMXJonKPUNy+mSPb0lfOFb911/v4HgKCwwFpAr3X+A2a3FMgXE2pHYd +MTCt428cnKugiXBF8fPRk1MJgyg2Ykp188V2NCiE/H0k1eaqFP97VxFycPrFHii7 +1II4THdhIa9yo9TVPNjTMdaZRj4i/7yzTAhE3MgcllM2x6YQqzuYR+djQI7isATu +QOxHvi176qvZwDv4UEG6LbzRjCxEP+6ImXUfIBvlx/MM3EUiiQE8BBgBCAAmFiEE +RT6jEyjefYqqVa1O1WxyHBz/FCQFAl+242ICGwwFCQPCZwAACgkQ1WxyHBz/FCRg +3gf+KNRDkSlwwENVg+j14xlIKiwQad4I6/+HDjuAmhtWlTJCrrpQrJgxgyOSdXs5 +cm5ZEY/KEsFWjEz9dRqtmKN7W0wCkwAF0vl4sez73sZyD3X5z42vxF2JLavdUruY +03mKS5w1Umjwy7StcRWEdl/1hBBuV/U30vSslyuWkXNoqoBLYOR+Q0KjdkSeUpfv +jFNQROOg9dLKrCzmeF3O1HN/iDEe6K6bvSly3S8/f3OkdvE4VocZ7+ketcrg5lmN +lzltDy5yfsvU2/VVjGdI48J6i33CahJYT8diQcd+y7jH1B3bC+OZ0Jk3SIK97udJ +tJscrv0/jtIwo5zy8SqldnhB6g== +=OReJ +-----END PGP PUBLIC KEY BLOCK----- + pub D7C92B70FA1C814D uid Matthew Sicker (Signing Key) @@ -2953,6 +2989,43 @@ rg1uIDOAX2LIQbC3qLz9iwD2rdbOLOkCQDYIa5Vs3j3RT/cw1A== =r0Sk -----END PGP PUBLIC KEY BLOCK----- +pub ECEAC3B11AD0E0A0 +uid Priyanka + +sub 33BA5E7DBAA319CC +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.68 + +mQGNBF+zYT4BDADOvqnDCp47DHqAxbJ5DmnKetnYyhJwNo4S5l+Wlx6x5lVJPR+7 +w6KfHXPDlBgoHJp3j0w6zWijHzGX1eK4Z4c95pE6S+7v/qM4otItJte+bYMNd+Lm +VkYVVaBwWUxI0vVlgqojRUvrQGPhey+iEfcGrp28xHpuZtoXE0GUjlAaSowp9sBm +SNxR3NMTXcE1joigGquwRzuSSXaFCTcCKdZ+xyvnjBpr+oVXv0WHfvGPhlgYKylq +jBaZGE88Wp20TZ7DQaMaP6XmZg6nSB0iKo1HO+VDMHgne/QvBiXEnkw72YnDEfk+ +eNomIvrumfhWfnC3SOqWY7k1FuKQrtbBdOhUTcqTaUFCezWoZ403jxc9DoKYDZvG +5LjQ07wcwrnB9ldBsyvdLqujp+LKcS+9KZMTGOnzyTQyyjpRHqFtP/zzZpNW5EWd +MIk51PebfGuBwCa+RgE8CSuEwoNhdA+GAqhr20Fivf4OJ134qm0xPmLmx/WYlugL +GnZgRfFjlv7bKgkAEQEAAbQdUHJpeWFua2EgPHByaWd1cHRhQGFkb2JlLmNvbT65 +AY0EX7NhPgEMANX4DBl2KhJ+52g6ehpK9f76xDWMd2J75qaY59feSN1+1D48dbVu +0YgHjdCOh5xk2KcKKo3oY3a/L4GFlucWvnnvwQAVKuZZSS2nP5ZTepFKah8pElWM +gtx2Bd+XgV5JCZgNSB+7LPXlBltcbPEdaguDbORCmy/ACuVEJ/IcJgDCpBWVjF8q +zmC47vCEUKSdcsZ2EIYr0tqrZYAt6AtNBlpV8EbrBgGIKmIxeg3b7Las8cj+0pQG +K2Nuszfpku2+HA7QlaaH2/W+sV8Hi0U7Hq/MRtx4V3+W8l3FzA+il0FVGijQgE3U +QoNpTy5Tl5Ic1yVw4gOFy6TreMNr3KbEU+J2MGtgPnbde8LuZ016NdmnA05/AzJ3 +CJ5aCyj/h5k5q4YRA0S0osqm47D8ooIZPGZ7LMVqrjm3vIuoJXav6rAmTgNnHU2N +CGHWMsMDOPfpG7dNMfn0aAb2DL9JNMdqEPTWOGhEqsXupvLSNe/mKxyN19fzIWst +pX2mWjBUtcTNFwARAQABiQG8BBgBCAAmFiEE71IUrWVM0F8NqRYJ7OrDsRrQ4KAF +Al+zYT4CGwwFCQPCZwAACgkQ7OrDsRrQ4KAwfAv/YfM2nS/19QDxnfgSaJAXdtit +OoXzIBVqZ44Z5LgqRkloLm/kSzOhzX53yxwAvVDkaH//5GUp7jqIg21ejULOfewC +Upbth1x9XUnOA55Tz54Z3Emfnshbp04XVj5V+7VJG0UoRfOl8typI2/RPN0h0rQD +KxO2h8cuIP9vlGY1AXFP9mirI02BLdm1S7ugAchrfEcvmKwXPcltlDA487S01iSd +nDSBOtAvIVIrpkV5eIu0e58KbdxLiqP+dvpvQyGRFx3ktOIoStqx5Qea+/ZX8uxI +X7cDvdPbYmi4jel/RfIg07HDjjLjGDnfUDw2gwHdlySWd/przYqAocjJUB+0tCc3 +kV4CM2caga5PRHuYRoFl/UeCKgZfH20jNjq6Fw88s5jlzMDyfGB5oBU1S9F5fiV1 +qQhnaRFDkP2iG/CN9kbL90vRVANHVw9odIiS4Yd3sTX2vnx4eP+6h4ynXL0p01y4 +K+jUtu4w+gsJ3GJvywKbXKy6DkVXcc+umnc318/Y +=UhYJ +-----END PGP PUBLIC KEY BLOCK----- + pub EE9E7DC9D92FC896 uid Eddie Aftandilian @@ -3208,6 +3281,35 @@ VDzc34e+Nr/b2pN05MDHA0dXmb/irwPBl0mTOgAgC805qkR14xhd1GeL6MEA34k8 =CmMl -----END PGP PUBLIC KEY BLOCK----- +pub FB63BCA973FAA119 +uid potato + +sub 28ECAB16140A23E5 +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.68 + +mQENBFv8rqkBCAC8J4YBW1ZjXpovURQJcMd0kYgEjVzV0GHHVp9/NJwQ7NrhqGeE +ktSZV4ctxWoPXYqAMbCjqyRfXQw28olj7L55m1VUIT10i20uDixdeDmAPcI5d1z9 +yljknFcOWSUBX+cYUKiYmel1Xb8l8qlzp31jL7TJdeW7fyYBiQqoxgHzAbc8ju8S +TymAVhf8vCee21C/ppAU0uhK7yVi3Liuhjl3jKDFRz0j9MkmrHeKxr+LLZfAZI9M +UCShsmm66anJThtnvxqod/186/7LbNsOfhbb6zCAfPTmb9EB9AncFwNgW4OR+f/C +yAZfr+sIDywrtOt3LXj0yaIYxGEIwXzxJW0xABEBAAG0GHBvdGF0byA8emg3OTMy +NUAxNjMuY29tPrkBDQRb/K6pAQgA4QmtZ8QjosaPwwTGG2w5wVWG/gtvclcJO/ns +wlhWSDiVjmyMknDzP2/sKT50MGzFLf1Fd1B39keYcKoRVyonV0LtZ8KlWgNipi7h +b9MGfrYotVQsP0XWhqUVJE80SBvOiWwAnind07SP2Ivl5jlbrgsIzvO2JpGTzraQ +Ae+AyKkVaqWXc7EOw8kuU9Q5rcagcSLyl3/9K9vP1wYcXo5qfiWc9I1dAnsPOTyY +S9ajFRY20+7yP5Fbsl6MyP7kYHSdfLSmEwzdQrSgaZQbNE+63eNA0vzWXmbpc/A+ +hq4SMK87qKJMAkd8Aaba+U7icBJqmONdZyM1YiqOVrnnjL+QAwARAQABiQE8BBgB +CAAmFiEEUiV1UMUMAD+K0c9/+2O8qXP6oRkFAlv8rqkCGwwFCQPCZwAACgkQ+2O8 +qXP6oRnpeAf/UUxitjRhq7ueiq4pz3mJNwX9M2BbLLkg1dR80MGHGAACb2DaOTXg +Sb1H4ZkB9JzrJKXUJB5J4f2BB/yrDQKmS+szgSd+NeQyFs+r6VSDiSwpnye8yDJg +E6AhGzo5HKFHu17N1w6NGkAQBL+GEgvtojbZDaPzpRSu78rIP2rFymeCiL83eZgP +nqAgKXI+jMiAGQwn8z2TbaiaKHmKKNm4GTZOEfjIkMOLNDt+th82/rLA9cJk+Gna +Q7Q6jnMm02XFBUIiIOCDTzK9BSCAV7SVAwfdlO3Be1KNITO/Rsx4LpHbRMyEf7u8 +3ZOclcevOY17eceSCwjy3C4mU9FhaByKkg== +=kRMk +-----END PGP PUBLIC KEY BLOCK----- + pub FD116C1969FCCFF3 uid Sean Leary @@ -3383,6 +3485,35 @@ kYlgaHe+RYIWqM1P1t8RfE7OH4kAeRSawjrQjgVp =Uv6G -----END PGP PUBLIC KEY BLOCK----- +pub 02DE09238A0E4D34 +uid Drew Noakes + +sub FCF369F756EF6105 +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.68 + +mQENBF0gmvMBCADCD96pOjHwsBOztrn7Vy22JMbGIK9LVTk0VC2HLNW/jcl0tTNi +wAIZ7F5Nat9H/fL20dlwvS81W5OlrFImY77HH9qzg+S+WuXlU5ze55kU6RaDWtee +VilasdwWxAMrtcPVo18DjDP+ey+hva8pvArdl3CEuva3gr9idz4GvWrGnkTCagJt +6VRig6XoalF4qVfvI5jkyX2vhUij/adpSXXFcPNcHcBCgUhD7BSgQIXtR/x8iaI8 +/RDCkto0mI/avZQvCjGKO1jleq1zpaD/h2Ga3Mb0hJpOL4HI4HYQcT0Hu0pfcUPx +AJrD1uZrR8h/2fZYtYgDYdpglYp0auZcGeHpABEBAAG0IERyZXcgTm9ha2VzIDxn +cGdAZHJld25vYWtlcy5jb20+uQENBF0gmvMBCAC3lRXIZGaFb9BZfF61dlDzZcpd +ctAN+WV7oLYVY+RECD83wxDqjDBTcsjgBgWXEbTJtaM4wgZ6ae3lQfPLiaL2dpuh +eZRYJx6fuRtYr0z4dqEmvLvoMFujAnb/fKVYm0dpHZw4D50BfDsIrEgMRNwoLJyr +z+rybU+fyygK6liuR6EoFLF2J5mw/WMW3il6Zv/Ly4NWWFvND9VPmiQYglItXXM5 +V5W0ifEe8Ckjajm7HlwqN7rl0MXLLnSGuC8ICB9xJQMOOb4m1wboASDi+yJ+54Gr +5BbrQ20DoWXahFSZhRO+QxEcWfcbB4FeEUSVSmQilZNjXgLKDN6/cMtK870vABEB +AAGJATwEGAEIACYWIQTgGqswFhjSOznb1BAC3gkjig5NNAUCXSCa8wIbDAUJA8Jn +AAAKCRAC3gkjig5NNGX8B/46vKeGgxJ5m+ZnPgi68hUuPM08TkyK6uYD27M60X0g +dVixchk0CRspycmeJvpvTh/6XDOiBStxD0zYcDoKjA7JNhIv3L+FIdP45VlyR6j9 +Ol1nNK9xVL3OifS942Q5vUaprZ+aakXGNfZXBpsVBAbnMrXdCNsjwNrgEZliNnEQ +iDwIQkjgM3c8gIllfn75y6PhYHJDYFj8M0IcWzpqRrCYiR+lqx17U4CavWJJXVfv +/n60Dd6u5MqL3Ket8Pc3bqknLMLzgp0YKlOYylOJpt7zkXz7X1AUSbkV7XfdOtUI +i+e239pgHg1ROFTbISLcgj+kLpKT/eb8cdbtzrx3q7CP +=PaUP +-----END PGP PUBLIC KEY BLOCK----- + pub 0374CF2E8DD1BDFD sub F2E4DE8FA750E060 -----BEGIN PGP PUBLIC KEY BLOCK----- @@ -3882,6 +4013,51 @@ jR7YX5D8RUnPYZnzIzID+ECD0JeFuyBMZI3y8Zog5w1Ce1wnzA== =T2aE -----END PGP PUBLIC KEY BLOCK----- +pub 08A4A4958D61FC3D +uid sksamuel + +sub 54EB00732104EF7A +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.68 + +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 +tE3KtJP0/IOVuzK22tPjBdeoI9FptQ== +=c4O7 +-----END PGP PUBLIC KEY BLOCK----- + pub 0B45DDD344B5FFD1 uid Josh Holtz (For maven) @@ -4933,6 +5109,22 @@ HxWaEMJtpSkIvHIBz9qoAroGtNFzz2oF4ElRABEBAAE= =1QGy -----END PGP PUBLIC KEY BLOCK----- +pub 2838A2C567F74226 +uid Seth Tisue + +sub B293A312CEB2E9F6 +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.68 + +mDMEYVOgzRYJKwYBBAHaRw8BAQdAPnTbK24vDd1YxFLwAmpdoemwlJMKH7PyGSe/ +ab65sry0JVNldGggVGlzdWUgPHNldGgudGlzdWVAbGlnaHRiZW5kLmNvbT64OARh +U6DNEgorBgEEAZdVAQUBAQdAsh50gWqK3tkXRhXWTv8bKOfUUFs7RLvna3KUwhIX +nXADAQgHiH4EGBYKACYWIQQI7z7CaKgEl+0gMIEoOKLFZ/dCJgUCYVOgzQIbDAUJ +A8JnAAAKCRAoOKLFZ/dCJtYHAQD90Eu8uvLofLKxoY+hbjn+dJ+fzcMZ5I2xoc1s +li442QEAp72J6Hz7p0Vyu1u05NZvb+jLBwWyI0P7Hq2pfR3qFg0= +=rrV8 +-----END PGP PUBLIC KEY BLOCK----- + pub 29579F18FA8FD93B sub 9DF7F2349731D55B -----BEGIN PGP PUBLIC KEY BLOCK----- @@ -6172,6 +6364,22 @@ eKej8y3YRDgQU+O9SrfNjf8PhhpG98C+k/yOh/tty5HGNfdvyMB8TdVxzTd5uHMN =m6Ii -----END PGP PUBLIC KEY BLOCK----- +pub 5365A8A69292AF1A +uid Seth Tisue + +sub 6DAC12FEF3928B80 +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.68 + +mDMEYVTpMBYJKwYBBAHaRw8BAQdAgCkGHxDJ2ObI6x5cwp6Cl85hJQ5vIZFH/0+1 +wnaiXA20JVNldGggVGlzdWUgPHNldGgudGlzdWVAbGlnaHRiZW5kLmNvbT64OARh +VOkwEgorBgEEAZdVAQUBAQdAsJWY4kW6HpMZV8X5VYH0oq18gI8vVaQPTK+UXiu3 +FkEDAQgHiH4EGBYKACYWIQRgDSEhmWPyKCAKcjdTZaimkpKvGgUCYVTpMAIbDAUJ +A8JnAAAKCRBTZaimkpKvGlyYAQC0bd1I8QbHqLzhkSv7NyKCh8SDTj/xb9DXPF24 +09RywQEAjKh4ayeMU3KgTAoCps9UZ1tw/tySVUY0qIzV4dDP1w4= +=82EY +-----END PGP PUBLIC KEY BLOCK----- + pub 55C7E5E701832382 -----BEGIN PGP PUBLIC KEY BLOCK----- Version: BCPG v1.68 @@ -6354,6 +6562,22 @@ V3u1xg+t7/QlghTMoJAA0H5G =hS0U -----END PGP PUBLIC KEY BLOCK----- +pub 59E05CE618187ED4 +uid Taro L. Saito (For GitHub Actions) + +sub 8857595B73BFD468 +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: BCPG v1.68 + +mDMEYuRVGhYJKwYBBAHaRw8BAQdA2Dp4m1Yhtb1g94pQzzL24FuP6b9KXF8lP9Dh +hZnynhe0M1Rhcm8gTC4gU2FpdG8gKEZvciBHaXRIdWIgQWN0aW9ucykgPGxlb0B4 +ZXJpYWwub3JnPrg4BGLkVRoSCisGAQQBl1UBBQEBB0Atu9kejBi+6wfOT0a9z/LY +EEdNXM/VX6xt1onKToPPdQMBCAeIeAQYFgoAIBYhBMHLp17JvQuvgGGTVFngXOYY +GH7UBQJi5FUaAhsMAAoJEFngXOYYGH7UlMABAKyRCazhVyUFg5FOpAnmckBY38Ca +MGPPLXVyY8Kr6dYFAP9wYLu7nsDZCOXkAgS+et4Pk1WZCggoYUkxsX1o0KZXBQ== +=7Gio +-----END PGP PUBLIC KEY BLOCK----- + pub 5B05CCDE140C2876 sub 9D29AE4A6B50E01F -----BEGIN PGP PUBLIC KEY BLOCK----- diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index ea3fcfaf03..0f79bbb918 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -7,9 +7,6 @@ - - - @@ -25,6 +22,7 @@ + @@ -50,7 +48,10 @@ - + + + + @@ -92,9 +93,13 @@ + - + + + + @@ -121,12 +126,15 @@ + + + @@ -227,6 +235,7 @@ + @@ -251,6 +260,7 @@ + @@ -260,6 +270,7 @@ + @@ -299,6 +310,11 @@ + + + + + @@ -307,6 +323,11 @@ + + + + + @@ -315,6 +336,14 @@ + + + + + + + + @@ -328,6 +357,19 @@ + + + + + + + + + + + + + @@ -336,6 +378,11 @@ + + + + + @@ -346,6 +393,14 @@ + + + + + + + + @@ -666,6 +721,11 @@ + + + + + @@ -674,6 +734,19 @@ + + + + + + + + + + + + + @@ -682,6 +755,19 @@ + + + + + + + + + + + + + @@ -690,6 +776,14 @@ + + + + + + + + @@ -698,6 +792,14 @@ + + + + + + + + @@ -706,6 +808,14 @@ + + + + + + + + @@ -714,6 +824,14 @@ + + + + + + + + @@ -722,6 +840,14 @@ + + + + + + + + @@ -730,6 +856,14 @@ + + + + + + + + @@ -738,6 +872,14 @@ + + + + + + + + @@ -746,6 +888,14 @@ + + + + + + + + @@ -854,6 +1004,11 @@ + + + + + @@ -886,6 +1041,11 @@ + + + + + @@ -1101,6 +1261,14 @@ + + + + + + + + @@ -1122,6 +1290,14 @@ + + + + + + + + @@ -1138,6 +1314,14 @@ + + + + + + + + @@ -1146,6 +1330,14 @@ + + + + + + + + @@ -1680,6 +1872,9 @@ + + + @@ -2911,6 +3106,11 @@ + + + + +