From 60a2c23db40c3df3039d471d80153f41e4e7d5e9 Mon Sep 17 00:00:00 2001 From: Krainov Gleb Date: Mon, 12 Oct 2026 08:36:10 +0000 Subject: [PATCH 1/2] fix(auto-upload): start auto upload right after resuming global upload pause Resuming the global upload pause on the uploads screen only restarted FileUploadWorker. AutoUploadWorker stops without uploading while the pause is active, so its folders stayed idle until the next periodic AutoUploadRescanWorker run. Start the auto upload of all enabled synced folders when the pause is lifted. FilesSyncHelper.startAutoUploadForEnabledSyncedFolders is used instead of startAutoUploadForEnabledSyncedFoldersIfNotActive: the latter calls isAutoUploadScheduled, which blocks on WorkManager's ListenableFuture, and this code runs on the main thread from a menu click. The chosen method enqueues with ExistingWorkPolicy.KEEP when power saving is not overridden, so a worker that is already running or enqueued is left alone and no upload is cancelled mid transfer. A worker that stopped because of the pause has finished, so a new one is enqueued. Add an injectable FileUploadHelper provider to UploadListMenuProvider so the menu logic can be unit tested without the application component. Signed-off-by: Krainov Gleb Assisted-by: Claude-Code:claude-opus-5-5 --- .../fragment/uploadList/UploadListFragment.kt | 4 +- .../uploadList/UploadListMenuProvider.kt | 16 ++++- .../uploadList/UploadListMenuProviderTest.kt | 63 +++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 app/src/test/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProviderTest.kt diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListFragment.kt index b94de86606ce..c1de65f815f9 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListFragment.kt @@ -253,7 +253,9 @@ class UploadListFragment : activity, preferences, userAccountManager, - uploadsStorageManager + uploadsStorageManager, + syncedFolderProvider, + backgroundJobManager ) { uploadListAdapter.notifyDataSetChanged() } activity.addMenuProvider(menuProvider, viewLifecycleOwner, Lifecycle.State.RESUMED) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProvider.kt b/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProvider.kt index da637f6bcf75..921d4efae1dc 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProvider.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProvider.kt @@ -12,18 +12,24 @@ import android.view.MenuInflater import android.view.MenuItem import androidx.core.view.MenuProvider import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.client.jobs.upload.AlbumFileUploadWorker import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.preferences.AppPreferences import com.owncloud.android.R +import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.UploadsStorageManager +import com.owncloud.android.utils.FilesSyncHelper class UploadListMenuProvider( private val context: Context, private val preferences: AppPreferences, private val userAccountManager: UserAccountManager, private val uploadsStorageManager: UploadsStorageManager, + private val syncedFolderProvider: SyncedFolderProvider, + private val backgroundJobManager: BackgroundJobManager, + private val uploadHelper: () -> FileUploadHelper = { FileUploadHelper.instance() }, private val onGlobalPauseToggled: () -> Unit ) : MenuProvider { @@ -61,12 +67,18 @@ class UploadListMenuProvider( if (paused) { FileUploadWorker.pauseActiveUploads() AlbumFileUploadWorker.pauseActiveUploads() + } else { + FilesSyncHelper.startAutoUploadForEnabledSyncedFolders( + syncedFolderProvider, + backgroundJobManager, + overridePowerSaving = false + ) } - val uploadHelper = FileUploadHelper.instance() + val helper = uploadHelper() userAccountManager.allUsers.filterNotNull().forEach { user -> val ids = uploadsStorageManager.getCurrentUploadIds(user.accountName) - uploadHelper.cancelAndRestartUploadJob(user, ids) + helper.cancelAndRestartUploadJob(user, ids) } onGlobalPauseToggled() } diff --git a/app/src/test/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProviderTest.kt b/app/src/test/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProviderTest.kt new file mode 100644 index 000000000000..ded4c451c65e --- /dev/null +++ b/app/src/test/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProviderTest.kt @@ -0,0 +1,63 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.fragment.uploadList + +import android.content.Context +import android.view.MenuItem +import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.jobs.BackgroundJobManager +import com.nextcloud.client.jobs.upload.FileUploadHelper +import com.nextcloud.client.preferences.AppPreferences +import com.owncloud.android.R +import com.owncloud.android.datamodel.SyncedFolder +import com.owncloud.android.datamodel.SyncedFolderProvider +import com.owncloud.android.datamodel.UploadsStorageManager +import org.junit.Test +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +class UploadListMenuProviderTest { + + private val preferences: AppPreferences = mock { + on { isGlobalUploadPaused() } doReturn true + } + private val backgroundJobManager: BackgroundJobManager = mock() + private val uploadHelper: FileUploadHelper = mock() + private val enabledFolder: SyncedFolder = mock { + on { id } doReturn ENABLED_FOLDER_ID + on { isEnabled } doReturn true + } + private val syncedFolderProvider: SyncedFolderProvider = mock { + on { syncedFolders } doReturn mutableListOf(enabledFolder) + } + private val menuItem: MenuItem = mock { + on { itemId } doReturn R.id.action_toggle_global_pause + } + + private val menuProvider = UploadListMenuProvider( + mock(), + preferences, + mock(), + mock(), + syncedFolderProvider, + backgroundJobManager, + { uploadHelper } + ) {} + + @Test + fun `resuming global pause starts auto upload of enabled folders without waiting for the rescan`() { + menuProvider.onMenuItemSelected(menuItem) + + verify(backgroundJobManager).startAutoUpload(enabledFolder, false) + } + + private companion object { + const val ENABLED_FOLDER_ID = 1L + } +} From 394157044a17762081d9477f61eddcec882729fc Mon Sep 17 00:00:00 2001 From: Krainov Gleb Date: Wed, 4 Nov 2026 16:13:39 +0000 Subject: [PATCH 2/2] test(auto-upload): remove UploadListMenuProvider unit test The change is small and easy to verify manually, and a test per fix adds CI time. Remove UploadListMenuProviderTest and the FileUploadHelper provider parameter that existed only for it. UploadListMenuProvider calls FileUploadHelper.instance() directly again, as on master. Resuming the global upload pause still starts the auto upload of enabled synced folders. Assisted-by: Claude-Code:claude-opus-5-5 Signed-off-by: Krainov Gleb --- .../uploadList/UploadListMenuProvider.kt | 5 +- .../uploadList/UploadListMenuProviderTest.kt | 63 ------------------- 2 files changed, 2 insertions(+), 66 deletions(-) delete mode 100644 app/src/test/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProviderTest.kt diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProvider.kt b/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProvider.kt index 921d4efae1dc..7f38dd621fed 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProvider.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProvider.kt @@ -29,7 +29,6 @@ class UploadListMenuProvider( private val uploadsStorageManager: UploadsStorageManager, private val syncedFolderProvider: SyncedFolderProvider, private val backgroundJobManager: BackgroundJobManager, - private val uploadHelper: () -> FileUploadHelper = { FileUploadHelper.instance() }, private val onGlobalPauseToggled: () -> Unit ) : MenuProvider { @@ -75,10 +74,10 @@ class UploadListMenuProvider( ) } - val helper = uploadHelper() + val uploadHelper = FileUploadHelper.instance() userAccountManager.allUsers.filterNotNull().forEach { user -> val ids = uploadsStorageManager.getCurrentUploadIds(user.accountName) - helper.cancelAndRestartUploadJob(user, ids) + uploadHelper.cancelAndRestartUploadJob(user, ids) } onGlobalPauseToggled() } diff --git a/app/src/test/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProviderTest.kt b/app/src/test/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProviderTest.kt deleted file mode 100644 index ded4c451c65e..000000000000 --- a/app/src/test/java/com/owncloud/android/ui/fragment/uploadList/UploadListMenuProviderTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Nextcloud - Android Client - * - * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -package com.owncloud.android.ui.fragment.uploadList - -import android.content.Context -import android.view.MenuItem -import com.nextcloud.client.account.UserAccountManager -import com.nextcloud.client.jobs.BackgroundJobManager -import com.nextcloud.client.jobs.upload.FileUploadHelper -import com.nextcloud.client.preferences.AppPreferences -import com.owncloud.android.R -import com.owncloud.android.datamodel.SyncedFolder -import com.owncloud.android.datamodel.SyncedFolderProvider -import com.owncloud.android.datamodel.UploadsStorageManager -import org.junit.Test -import org.mockito.kotlin.doReturn -import org.mockito.kotlin.mock -import org.mockito.kotlin.verify - -class UploadListMenuProviderTest { - - private val preferences: AppPreferences = mock { - on { isGlobalUploadPaused() } doReturn true - } - private val backgroundJobManager: BackgroundJobManager = mock() - private val uploadHelper: FileUploadHelper = mock() - private val enabledFolder: SyncedFolder = mock { - on { id } doReturn ENABLED_FOLDER_ID - on { isEnabled } doReturn true - } - private val syncedFolderProvider: SyncedFolderProvider = mock { - on { syncedFolders } doReturn mutableListOf(enabledFolder) - } - private val menuItem: MenuItem = mock { - on { itemId } doReturn R.id.action_toggle_global_pause - } - - private val menuProvider = UploadListMenuProvider( - mock(), - preferences, - mock(), - mock(), - syncedFolderProvider, - backgroundJobManager, - { uploadHelper } - ) {} - - @Test - fun `resuming global pause starts auto upload of enabled folders without waiting for the rescan`() { - menuProvider.onMenuItemSelected(menuItem) - - verify(backgroundJobManager).startAutoUpload(enabledFolder, false) - } - - private companion object { - const val ENABLED_FOLDER_ID = 1L - } -}