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 @@
+
+
+
+
+