Răsfoiți Sursa

Merge pull request #12327 from nextcloud/refactor/remove-file-upload-service

Remove File Upload Service
Tobias Kaminsky 1 an în urmă
părinte
comite
1dc6262406
92 a modificat fișierele cu 1504 adăugiri și 2772 ștergeri
  1. 2 2
      app/src/androidTest/java/com/nextcloud/client/files/download/DownloaderServiceTest.kt
  2. 3 3
      app/src/androidTest/java/com/nextcloud/client/files/download/RegistryTest.kt
  3. 6 6
      app/src/androidTest/java/com/nextcloud/client/files/download/TransferManagerConnectionTest.kt
  4. 6 5
      app/src/androidTest/java/com/nextcloud/client/files/download/TransferManagerTest.kt
  5. 2 2
      app/src/androidTest/java/com/owncloud/android/AbstractIT.java
  6. 4 4
      app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java
  7. 23 23
      app/src/androidTest/java/com/owncloud/android/UploadIT.java
  8. 4 4
      app/src/androidTest/java/com/owncloud/android/files/FileMenuFilterIT.kt
  9. 0 38
      app/src/androidTest/java/com/owncloud/android/files/services/FileUploadWorkerIT.kt
  10. 36 38
      app/src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt
  11. 2 2
      app/src/androidTest/java/com/owncloud/android/ui/helpers/UriUploaderIT.kt
  12. 3 3
      app/src/debug/java/com/nextcloud/test/TestActivity.kt
  13. 1 1
      app/src/generic/java/com/owncloud/android/utils/PushUtils.java
  14. 2 6
      app/src/main/AndroidManifest.xml
  15. 3 3
      app/src/main/java/com/nextcloud/client/di/AppComponent.java
  16. 3 7
      app/src/main/java/com/nextcloud/client/di/ComponentsModule.java
  17. 4 3
      app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt
  18. 6 5
      app/src/main/java/com/nextcloud/client/documentscan/GeneratePdfFromImagesWork.kt
  19. 4 4
      app/src/main/java/com/nextcloud/client/editimage/EditImageActivity.kt
  20. 1 1
      app/src/main/java/com/nextcloud/client/etm/EtmViewModel.kt
  21. 2 2
      app/src/main/java/com/nextcloud/client/etm/pages/EtmFileTransferFragment.kt
  22. 2 2
      app/src/main/java/com/nextcloud/client/files/Registry.kt
  23. 2 2
      app/src/main/java/com/nextcloud/client/files/Request.kt
  24. 5 4
      app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt
  25. 1 0
      app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt
  26. 17 4
      app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt
  27. 3 3
      app/src/main/java/com/nextcloud/client/jobs/ContactsBackupWork.kt
  28. 1 1
      app/src/main/java/com/nextcloud/client/jobs/FilesExportWork.kt
  29. 7 6
      app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt
  30. 0 466
      app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt
  31. 2 2
      app/src/main/java/com/nextcloud/client/jobs/download/DownloadNotificationManager.kt
  32. 1 1
      app/src/main/java/com/nextcloud/client/jobs/download/DownloadTask.kt
  33. 1 1
      app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadError.kt
  34. 1 1
      app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadHelper.kt
  35. 1 1
      app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadIntents.kt
  36. 9 3
      app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadWorker.kt
  37. 3 3
      app/src/main/java/com/nextcloud/client/jobs/transfer/FileTransferService.kt
  38. 1 1
      app/src/main/java/com/nextcloud/client/jobs/transfer/Transfer.kt
  39. 1 1
      app/src/main/java/com/nextcloud/client/jobs/transfer/TransferManager.kt
  40. 1 1
      app/src/main/java/com/nextcloud/client/jobs/transfer/TransferManagerConnection.kt
  41. 3 3
      app/src/main/java/com/nextcloud/client/jobs/transfer/TransferManagerImpl.kt
  42. 1 1
      app/src/main/java/com/nextcloud/client/jobs/transfer/TransferState.kt
  43. 277 0
      app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt
  44. 334 0
      app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt
  45. 16 18
      app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderDelegate.kt
  46. 136 0
      app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt
  47. 5 7
      app/src/main/java/com/nextcloud/client/jobs/upload/PostUploadAction.kt
  48. 195 0
      app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt
  49. 1 1
      app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt
  50. 1 1
      app/src/main/java/com/nextcloud/client/jobs/upload/UploadTrigger.kt
  51. 2 0
      app/src/main/java/com/nextcloud/model/WorkerState.kt
  52. 3 3
      app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java
  53. 5 4
      app/src/main/java/com/owncloud/android/db/OCUpload.java
  54. 12 14
      app/src/main/java/com/owncloud/android/files/FileMenuFilter.java
  55. 0 1409
      app/src/main/java/com/owncloud/android/files/services/FileUploader.java
  56. 0 1
      app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java
  57. 39 46
      app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java
  58. 3 4
      app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java
  59. 6 6
      app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java
  60. 7 11
      app/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java
  61. 1 1
      app/src/main/java/com/owncloud/android/services/SyncFolderHandler.java
  62. 7 7
      app/src/main/java/com/owncloud/android/ui/activity/ComponentsGetter.java
  63. 36 23
      app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt
  64. 19 32
      app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
  65. 28 48
      app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
  66. 3 72
      app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java
  67. 8 8
      app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java
  68. 3 3
      app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt
  69. 2 2
      app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java
  70. 23 49
      app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java
  71. 2 1
      app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt
  72. 3 1
      app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java
  73. 4 3
      app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt
  74. 37 49
      app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java
  75. 5 6
      app/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java
  76. 1 1
      app/src/main/java/com/owncloud/android/ui/dialog/SetupEncryptionDialogFragment.kt
  77. 7 7
      app/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java
  78. 24 14
      app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
  79. 3 4
      app/src/main/java/com/owncloud/android/ui/fragment/FileFragment.java
  80. 3 3
      app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java
  81. 9 5
      app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java
  82. 8 8
      app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt
  83. 23 58
      app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java
  84. 1 1
      app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt
  85. 1 1
      app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java
  86. 8 9
      app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java
  87. 0 156
      app/src/main/java/com/owncloud/android/utils/FilesUploadHelper.kt
  88. 3 3
      app/src/main/java/third_parties/sufficientlysecure/SaveCalendar.java
  89. 1 1
      app/src/main/res/values/setup.xml
  90. 1 0
      app/src/main/res/values/strings.xml
  91. 2 2
      app/src/test/java/com/owncloud/android/ui/activity/SyncedFoldersActivityTest.java
  92. 6 0
      app/src/test/java/com/owncloud/android/ui/adapter/GalleryAdapterTest.kt

+ 2 - 2
app/src/androidTest/java/com/nextcloud/client/files/downloader/DownloaderServiceTest.kt → app/src/androidTest/java/com/nextcloud/client/files/download/DownloaderServiceTest.kt

@@ -17,12 +17,12 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.files.download
 
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.rule.ServiceTestRule
 import com.nextcloud.client.account.MockUser
-import com.nextcloud.client.files.transfer.FileTransferService
+import com.nextcloud.client.jobs.transfer.FileTransferService
 import io.mockk.MockKAnnotations
 import org.junit.Assert.assertTrue
 import org.junit.Before

+ 3 - 3
app/src/androidTest/java/com/nextcloud/client/files/downloader/RegistryTest.kt → app/src/androidTest/java/com/nextcloud/client/files/download/RegistryTest.kt

@@ -17,14 +17,14 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.files.download
 
 import com.nextcloud.client.account.User
 import com.nextcloud.client.files.DownloadRequest
 import com.nextcloud.client.files.Registry
 import com.nextcloud.client.files.Request
-import com.nextcloud.client.files.transfer.Transfer
-import com.nextcloud.client.files.transfer.TransferState
+import com.nextcloud.client.jobs.transfer.Transfer
+import com.nextcloud.client.jobs.transfer.TransferState
 import com.owncloud.android.datamodel.OCFile
 import io.mockk.CapturingSlot
 import io.mockk.MockKAnnotations

+ 6 - 6
app/src/androidTest/java/com/nextcloud/client/files/downloader/TransferManagerConnectionTest.kt → app/src/androidTest/java/com/nextcloud/client/files/download/TransferManagerConnectionTest.kt

@@ -17,17 +17,17 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.files.download
 
 import android.content.ComponentName
 import android.content.Context
 import com.nextcloud.client.account.MockUser
 import com.nextcloud.client.files.DownloadRequest
-import com.nextcloud.client.files.transfer.FileTransferService
-import com.nextcloud.client.files.transfer.Transfer
-import com.nextcloud.client.files.transfer.TransferManager
-import com.nextcloud.client.files.transfer.TransferManagerConnection
-import com.nextcloud.client.files.transfer.TransferState
+import com.nextcloud.client.jobs.transfer.FileTransferService
+import com.nextcloud.client.jobs.transfer.Transfer
+import com.nextcloud.client.jobs.transfer.TransferManager
+import com.nextcloud.client.jobs.transfer.TransferManagerConnection
+import com.nextcloud.client.jobs.transfer.TransferState
 import com.owncloud.android.datamodel.OCFile
 import io.mockk.MockKAnnotations
 import io.mockk.every

+ 6 - 5
app/src/androidTest/java/com/nextcloud/client/files/downloader/TransferManagerTest.kt → app/src/androidTest/java/com/nextcloud/client/files/download/TransferManagerTest.kt

@@ -17,7 +17,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.files.download
 
 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
 import com.nextcloud.client.account.User
@@ -25,10 +25,11 @@ import com.nextcloud.client.core.ManualAsyncRunner
 import com.nextcloud.client.core.OnProgressCallback
 import com.nextcloud.client.files.DownloadRequest
 import com.nextcloud.client.files.Request
-import com.nextcloud.client.files.transfer.Transfer
-import com.nextcloud.client.files.transfer.TransferManagerImpl
-import com.nextcloud.client.files.transfer.TransferState
-import com.nextcloud.client.files.upload.UploadTask
+import com.nextcloud.client.jobs.download.DownloadTask
+import com.nextcloud.client.jobs.transfer.Transfer
+import com.nextcloud.client.jobs.transfer.TransferManagerImpl
+import com.nextcloud.client.jobs.transfer.TransferState
+import com.nextcloud.client.jobs.upload.UploadTask
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.lib.common.OwnCloudClient
 import io.mockk.MockKAnnotations

+ 2 - 2
app/src/androidTest/java/com/owncloud/android/AbstractIT.java

@@ -21,6 +21,7 @@ import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManagerImpl;
 import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.preferences.AppPreferencesImpl;
@@ -35,7 +36,6 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientFactory;
@@ -400,7 +400,7 @@ public abstract class AbstractIT {
             null,
             ocUpload,
             NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             targetContext,
             false,
             false,

+ 4 - 4
app/src/androidTest/java/com/owncloud/android/AbstractOnServerIT.java

@@ -13,13 +13,13 @@ import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManagerImpl;
 import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.java.util.Optional;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientFactory;
@@ -178,7 +178,7 @@ public abstract class AbstractOnServerIT extends AbstractIT {
     }
 
     public void uploadOCUpload(OCUpload ocUpload) {
-        uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_COPY);
+        uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_COPY);
     }
 
     public void uploadOCUpload(OCUpload ocUpload, int localBehaviour) {
@@ -253,8 +253,8 @@ public abstract class AbstractOnServerIT extends AbstractIT {
         assertNotNull(uploadedFile.getRemoteId());
         assertNotNull(uploadedFile.getPermissions());
 
-        if (localBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY ||
-            localBehaviour == FileUploader.LOCAL_BEHAVIOUR_MOVE) {
+        if (localBehaviour == FileUploadWorker.LOCAL_BEHAVIOUR_COPY ||
+            localBehaviour == FileUploadWorker.LOCAL_BEHAVIOUR_MOVE) {
             assertTrue(new File(uploadedFile.getStoragePath()).exists());
         }
     }

+ 23 - 23
app/src/androidTest/java/com/owncloud/android/UploadIT.java

@@ -24,12 +24,12 @@ package com.owncloud.android;
 import com.nextcloud.client.account.UserAccountManagerImpl;
 import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.accounts.AccountUtils;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -134,7 +134,7 @@ public class UploadIT extends AbstractOnServerIT {
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
-        uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_COPY);
+        uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_COPY);
 
         File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
@@ -150,7 +150,7 @@ public class UploadIT extends AbstractOnServerIT {
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
-        uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_MOVE);
+        uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_MOVE);
 
         File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
@@ -166,7 +166,7 @@ public class UploadIT extends AbstractOnServerIT {
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
-        uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_FORGET);
+        uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_FORGET);
 
         File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
@@ -182,7 +182,7 @@ public class UploadIT extends AbstractOnServerIT {
                                          FOLDER + "nonEmpty.txt",
                                          account.name);
 
-        uploadOCUpload(ocUpload, FileUploader.LOCAL_BEHAVIOUR_DELETE);
+        uploadOCUpload(ocUpload, FileUploadWorker.LOCAL_BEHAVIOUR_DELETE);
 
         File originalFile = new File(FileStorageUtils.getTemporalPath(account.name) + "/nonEmpty.txt");
         OCFile uploadedFile = fileDataStorageManager.getFileByDecryptedRemotePath(FOLDER + "nonEmpty.txt");
@@ -222,7 +222,7 @@ public class UploadIT extends AbstractOnServerIT {
             null,
             ocUpload,
             NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             targetContext,
             false,
             true,
@@ -270,7 +270,7 @@ public class UploadIT extends AbstractOnServerIT {
             null,
             ocUpload,
             NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             targetContext,
             false,
             true,
@@ -315,7 +315,7 @@ public class UploadIT extends AbstractOnServerIT {
             null,
             ocUpload,
             NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             targetContext,
             true,
             false,
@@ -345,7 +345,7 @@ public class UploadIT extends AbstractOnServerIT {
             null,
             ocUpload,
             NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             targetContext,
             true,
             false,
@@ -400,7 +400,7 @@ public class UploadIT extends AbstractOnServerIT {
             null,
             ocUpload,
             NameCollisionPolicy.DEFAULT,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             targetContext,
             true,
             false,
@@ -433,7 +433,7 @@ public class UploadIT extends AbstractOnServerIT {
                 null,
                 ocUpload,
                 NameCollisionPolicy.DEFAULT,
-                FileUploader.LOCAL_BEHAVIOUR_COPY,
+                FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
                 targetContext,
                 false,
                 false,
@@ -480,18 +480,18 @@ public class UploadIT extends AbstractOnServerIT {
 
         assertTrue(
                 new UploadFileOperation(
-                        uploadsStorageManager,
-                        connectivityServiceMock,
-                        powerManagementServiceMock,
-                        user,
-                        null,
-                        ocUpload,
-                        NameCollisionPolicy.DEFAULT,
-                        FileUploader.LOCAL_BEHAVIOUR_COPY,
-                        targetContext,
-                        false,
-                        false,
-                        getStorageManager()
+                    uploadsStorageManager,
+                    connectivityServiceMock,
+                    powerManagementServiceMock,
+                    user,
+                    null,
+                    ocUpload,
+                    NameCollisionPolicy.DEFAULT,
+                    FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
+                    targetContext,
+                    false,
+                    false,
+                    getStorageManager()
                 )
                         .setRemoteFolderToBeCreated()
                         .execute(client)

+ 4 - 4
app/src/androidTest/java/com/owncloud/android/files/FileMenuFilterIT.kt

@@ -23,7 +23,8 @@ package com.owncloud.android.files
 import androidx.test.core.app.launchActivity
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.nextcloud.client.account.User
-import com.nextcloud.client.files.downloader.FileDownloadWorker
+import com.nextcloud.client.jobs.download.FileDownloadWorker
+import com.nextcloud.client.jobs.upload.FileUploadHelper
 import com.nextcloud.test.TestActivity
 import com.nextcloud.utils.EditorUtils
 import com.owncloud.android.AbstractIT
@@ -31,7 +32,6 @@ import com.owncloud.android.R
 import com.owncloud.android.datamodel.ArbitraryDataProvider
 import com.owncloud.android.datamodel.FileDataStorageManager
 import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.lib.resources.files.model.FileLockType
 import com.owncloud.android.lib.resources.status.CapabilityBooleanType
 import com.owncloud.android.lib.resources.status.OCCapability
@@ -59,7 +59,7 @@ class FileMenuFilterIT : AbstractIT() {
     private lateinit var mockStorageManager: FileDataStorageManager
 
     @MockK
-    private lateinit var mockFileUploaderBinder: FileUploader.FileUploaderBinder
+    private lateinit var mockFileUploaderBinder: FileUploadHelper
 
     @MockK
     private lateinit var mockFileDownloadProgressListener: FileDownloadWorker.FileDownloadProgressListener
@@ -76,7 +76,7 @@ class FileMenuFilterIT : AbstractIT() {
     fun setup() {
         MockKAnnotations.init(this)
         every { mockFileUploaderBinder.isUploading(any(), any()) } returns false
-        every { mockComponentsGetter.fileUploaderBinder } returns mockFileUploaderBinder
+        every { mockComponentsGetter.fileUploaderHelper } returns mockFileUploaderBinder
         every { mockFileDownloadProgressListener.isDownloading(any(), any()) } returns false
         every { mockComponentsGetter.fileDownloadProgressListener } returns mockFileDownloadProgressListener
         every { mockOperationsServiceBinder.isSynchronizing(any(), any()) } returns false

+ 0 - 38
app/src/androidTest/java/com/owncloud/android/files/services/FileUploadWorkerIT.kt

@@ -1,38 +0,0 @@
-/*
- *
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2022 Tobias Kaminsky
- * Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.files.services
-
-import org.junit.After
-import org.junit.Before
-
-class FileUploadWorkerIT : FileUploaderIT() {
-    @Before
-    fun forceUploadWorker() {
-        FileUploader.setForceNewUploadWorker(true)
-    }
-
-    @After
-    fun resetForceUploadWorker() {
-        FileUploader.setForceNewUploadWorker(false)
-    }
-}

+ 36 - 38
app/src/androidTest/java/com/owncloud/android/files/services/FileUploaderIT.kt

@@ -26,6 +26,8 @@ import com.nextcloud.client.account.UserAccountManager
 import com.nextcloud.client.account.UserAccountManagerImpl
 import com.nextcloud.client.device.BatteryStatus
 import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.jobs.upload.FileUploadHelper
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.client.network.Connectivity
 import com.nextcloud.client.network.ConnectivityService
 import com.owncloud.android.AbstractOnServerIT
@@ -138,11 +140,11 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
     fun testKeepLocalAndOverwriteRemoteStatic() {
         val file = getDummyFile("chunkedFile.txt")
 
-        FileUploader.uploadNewFile(
+        FileUploadHelper().uploadNewFiles(
             user,
-            file.absolutePath,
-            "/testFile.txt",
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            arrayOf(file.absolutePath),
+            arrayOf("/testFile.txt"),
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             true,
             UploadFileOperation.CREATED_BY_USER,
             false,
@@ -160,11 +162,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
         val ocFile2 = OCFile("/testFile.txt")
         ocFile2.storagePath = getDummyFile("empty.txt").absolutePath
 
-        FileUploader.uploadUpdateFile(
-            targetContext,
+        FileUploadHelper().uploadUpdatedFile(
             user,
-            ocFile2,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            arrayOf(ocFile2),
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             NameCollisionPolicy.OVERWRITE
         )
 
@@ -195,7 +196,7 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
                 null,
                 ocUpload,
                 NameCollisionPolicy.DEFAULT,
-                FileUploader.LOCAL_BEHAVIOUR_COPY,
+                FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
                 targetContext,
                 false,
                 false,
@@ -223,7 +224,7 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
                 null,
                 ocUpload2,
                 NameCollisionPolicy.RENAME,
-                FileUploader.LOCAL_BEHAVIOUR_COPY,
+                FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
                 targetContext,
                 false,
                 false,
@@ -255,11 +256,11 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
     fun testKeepBothStatic() {
         val file = getDummyFile("nonEmpty.txt")
 
-        FileUploader.uploadNewFile(
+        FileUploadHelper().uploadNewFiles(
             user,
-            file.absolutePath,
-            "/testFile.txt",
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            arrayOf(file.absolutePath),
+            arrayOf("/testFile.txt"),
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             true,
             UploadFileOperation.CREATED_BY_USER,
             false,
@@ -277,11 +278,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
         val ocFile2 = OCFile("/testFile.txt")
         ocFile2.storagePath = getDummyFile("empty.txt").absolutePath
 
-        FileUploader.uploadUpdateFile(
-            targetContext,
+        FileUploadHelper().uploadUpdatedFile(
             user,
-            ocFile2,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            arrayOf(ocFile2),
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             NameCollisionPolicy.RENAME
         )
 
@@ -315,7 +315,7 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
                 null,
                 ocUpload,
                 NameCollisionPolicy.DEFAULT,
-                FileUploader.LOCAL_BEHAVIOUR_COPY,
+                FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
                 targetContext,
                 false,
                 false,
@@ -342,7 +342,7 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
                 null,
                 ocUpload2,
                 NameCollisionPolicy.CANCEL,
-                FileUploader.LOCAL_BEHAVIOUR_COPY,
+                FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
                 targetContext,
                 false,
                 false,
@@ -364,11 +364,11 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
     fun testKeepServerStatic() {
         val file = getDummyFile("chunkedFile.txt")
 
-        FileUploader.uploadNewFile(
+        FileUploadHelper().uploadNewFiles(
             user,
-            file.absolutePath,
-            "/testFile.txt",
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            arrayOf(file.absolutePath),
+            arrayOf("/testFile.txt"),
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             true,
             UploadFileOperation.CREATED_BY_USER,
             false,
@@ -386,11 +386,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
         val ocFile2 = OCFile("/testFile.txt")
         ocFile2.storagePath = getDummyFile("empty.txt").absolutePath
 
-        FileUploader.uploadUpdateFile(
-            targetContext,
+        FileUploadHelper().uploadUpdatedFile(
             user,
-            ocFile2,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            arrayOf(ocFile2),
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             NameCollisionPolicy.CANCEL
         )
 
@@ -419,7 +418,7 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
                 null,
                 ocUpload,
                 NameCollisionPolicy.CANCEL,
-                FileUploader.LOCAL_BEHAVIOUR_COPY,
+                FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
                 targetContext,
                 false,
                 false,
@@ -445,7 +444,7 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
             null,
             ocUpload2,
             NameCollisionPolicy.CANCEL,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             targetContext,
             false,
             false,
@@ -469,11 +468,11 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
     fun testKeepCancelStatic() {
         val file = getDummyFile("chunkedFile.txt")
 
-        FileUploader.uploadNewFile(
+        FileUploadHelper().uploadNewFiles(
             user,
-            file.absolutePath,
-            "/testFile.txt",
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            arrayOf(file.absolutePath),
+            arrayOf("/testFile.txt"),
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             true,
             UploadFileOperation.CREATED_BY_USER,
             false,
@@ -491,11 +490,10 @@ abstract class FileUploaderIT : AbstractOnServerIT() {
         val ocFile2 = OCFile("/testFile.txt")
         ocFile2.storagePath = getDummyFile("empty.txt").absolutePath
 
-        FileUploader.uploadUpdateFile(
-            targetContext,
+        FileUploadHelper().uploadUpdatedFile(
             user,
-            ocFile2,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            arrayOf(ocFile2),
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             NameCollisionPolicy.CANCEL
         )
 

+ 2 - 2
app/src/androidTest/java/com/owncloud/android/ui/helpers/UriUploaderIT.kt

@@ -2,9 +2,9 @@ package com.owncloud.android.ui.helpers
 
 import android.net.Uri
 import androidx.test.core.app.launchActivity
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.test.TestActivity
 import com.owncloud.android.AbstractIT
-import com.owncloud.android.files.services.FileUploader
 import org.junit.Assert
 import org.junit.Test
 
@@ -38,7 +38,7 @@ class UriUploaderIT : AbstractIT() {
             listOf(Uri.parse(path)),
             "",
             activity.user.orElseThrow(::RuntimeException),
-            FileUploader.LOCAL_BEHAVIOUR_MOVE,
+            FileUploadWorker.LOCAL_BEHAVIOUR_MOVE,
             false,
             null
         )

+ 3 - 3
app/src/debug/java/com/nextcloud/test/TestActivity.kt

@@ -25,7 +25,8 @@ import android.os.Bundle
 import android.view.View
 import androidx.fragment.app.Fragment
 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
-import com.nextcloud.client.files.downloader.FileDownloadWorker
+import com.nextcloud.client.jobs.download.FileDownloadWorker
+import com.nextcloud.client.jobs.upload.FileUploadHelper
 import com.nextcloud.client.network.Connectivity
 import com.nextcloud.client.network.ConnectivityService
 import com.nextcloud.utils.EditorUtils
@@ -34,7 +35,6 @@ import com.owncloud.android.databinding.TestLayoutBinding
 import com.owncloud.android.datamodel.ArbitraryDataProviderImpl
 import com.owncloud.android.datamodel.FileDataStorageManager
 import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.lib.resources.status.OCCapability
 import com.owncloud.android.lib.resources.status.OwnCloudVersion
 import com.owncloud.android.services.OperationsService
@@ -126,7 +126,7 @@ class TestActivity :
         TODO("Not yet implemented")
     }
 
-    override fun getFileUploaderBinder(): FileUploader.FileUploaderBinder? {
+    override fun getFileUploaderHelper(): FileUploadHelper? {
         return null
     }
 

+ 1 - 1
app/src/generic/java/com/owncloud/android/utils/PushUtils.java

@@ -25,9 +25,9 @@ package com.owncloud.android.utils;
 import android.content.Context;
 
 import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.preferences.AppPreferencesImpl;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.datamodel.SignatureVerification;
-import com.nextcloud.client.preferences.AppPreferencesImpl;
 
 import java.security.Key;
 

+ 2 - 6
app/src/main/AndroidManifest.xml

@@ -170,7 +170,7 @@
             android:name="com.nextcloud.client.jobs.NotificationWork$NotificationReceiver"
             android:exported="false" />
         <receiver
-            android:name="com.owncloud.android.files.services.FileUploader$UploadNotificationActionReceiver"
+            android:name="com.nextcloud.client.jobs.upload.FileUploadHelper$UploadNotificationActionReceiver"
             android:exported="false" />
         <receiver
             android:name="com.nextcloud.client.widget.DashboardWidgetProvider"
@@ -394,11 +394,7 @@
             android:name=".services.OperationsService"
             android:exported="false" />
         <service
-            android:name="com.nextcloud.client.files.transfer.FileTransferService"
-            android:foregroundServiceType="dataSync"
-            android:exported="false" />
-        <service
-            android:name=".files.services.FileUploader"
+            android:name="com.nextcloud.client.jobs.transfer.FileTransferService"
             android:foregroundServiceType="dataSync"
             android:exported="false" />
         <service

+ 3 - 3
app/src/main/java/com/nextcloud/client/di/AppComponent.java

@@ -26,9 +26,10 @@ import com.nextcloud.appReview.InAppReviewModule;
 import com.nextcloud.client.appinfo.AppInfoModule;
 import com.nextcloud.client.database.DatabaseModule;
 import com.nextcloud.client.device.DeviceModule;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
 import com.nextcloud.client.integrations.IntegrationsModule;
 import com.nextcloud.client.jobs.JobsModule;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.client.network.NetworkModule;
 import com.nextcloud.client.onboarding.OnboardingModule;
 import com.nextcloud.client.preferences.PreferencesModule;
@@ -36,7 +37,6 @@ import com.owncloud.android.MainApp;
 import com.owncloud.android.media.MediaControlView;
 import com.owncloud.android.ui.ThemeableSwitchPreference;
 import com.owncloud.android.ui.whatsnew.ProgressIndicator;
-import com.owncloud.android.utils.FilesUploadHelper;
 
 import javax.inject.Singleton;
 
@@ -70,7 +70,7 @@ public interface AppComponent {
 
     void inject(ThemeableSwitchPreference switchPreference);
 
-    void inject(FilesUploadHelper filesUploadHelper);
+    void inject(FileUploadHelper fileUploadHelper);
 
     void inject(FileDownloadHelper fileDownloadHelper);
 

+ 3 - 7
app/src/main/java/com/nextcloud/client/di/ComponentsModule.java

@@ -24,10 +24,11 @@ import com.nextcloud.client.documentscan.DocumentScanActivity;
 import com.nextcloud.client.editimage.EditImageActivity;
 import com.nextcloud.client.etm.EtmActivity;
 import com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment;
-import com.nextcloud.client.files.transfer.FileTransferService;
 import com.nextcloud.client.jobs.BackgroundJobManagerImpl;
 import com.nextcloud.client.jobs.NotificationWork;
 import com.nextcloud.client.jobs.TestJob;
+import com.nextcloud.client.jobs.transfer.FileTransferService;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.client.logger.ui.LogsActivity;
 import com.nextcloud.client.logger.ui.LogsViewModel;
 import com.nextcloud.client.media.PlayerService;
@@ -46,7 +47,6 @@ import com.owncloud.android.MainApp;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.authentication.DeepLinkLoginActivity;
 import com.owncloud.android.files.BootupBroadcastReceiver;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.providers.DiskLruImageCacheFileProvider;
 import com.owncloud.android.providers.DocumentsStorageProvider;
 import com.owncloud.android.providers.FileContentProvider;
@@ -133,7 +133,6 @@ import com.owncloud.android.ui.preview.PreviewTextFragment;
 import com.owncloud.android.ui.preview.PreviewTextStringFragment;
 import com.owncloud.android.ui.preview.pdf.PreviewPdfFragment;
 import com.owncloud.android.ui.trashbin.TrashbinActivity;
-import com.owncloud.android.utils.FilesUploadHelper;
 
 import dagger.Module;
 import dagger.android.ContributesAndroidInjector;
@@ -320,9 +319,6 @@ abstract class ComponentsModule {
     @ContributesAndroidInjector
     abstract ReceiveExternalFilesActivity.DialogInputUploadFilename dialogInputUploadFilename();
 
-    @ContributesAndroidInjector
-    abstract FileUploader fileUploader();
-
     @ContributesAndroidInjector
     abstract BootupBroadcastReceiver bootupBroadcastReceiver();
 
@@ -456,7 +452,7 @@ abstract class ComponentsModule {
     abstract PreviewBitmapActivity previewBitmapActivity();
 
     @ContributesAndroidInjector
-    abstract FilesUploadHelper filesUploadHelper();
+    abstract FileUploadHelper fileUploadHelper();
 
     @ContributesAndroidInjector
     abstract SslUntrustedCertDialog sslUntrustedCertDialog();

+ 4 - 3
app/src/main/java/com/nextcloud/client/documentscan/DocumentScanViewModel.kt

@@ -30,9 +30,10 @@ import androidx.lifecycle.viewModelScope
 import com.nextcloud.client.account.CurrentAccountProvider
 import com.nextcloud.client.di.IoDispatcher
 import com.nextcloud.client.jobs.BackgroundJobManager
+import com.nextcloud.client.jobs.upload.FileUploadHelper
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.client.logger.Logger
 import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.files.services.NameCollisionPolicy
 import com.owncloud.android.operations.UploadFileOperation
 import com.owncloud.android.ui.helpers.FileOperationsHelper
@@ -183,11 +184,11 @@ class DocumentScanViewModel @Inject constructor(
             uploadFolder + OCFile.PATH_SEPARATOR + File(it).name
         }.toTypedArray()
 
-        FileUploader.uploadNewFile(
+        FileUploadHelper.instance().uploadNewFiles(
             currentAccountProvider.user,
             pageList.toTypedArray(),
             uploadPaths,
-            FileUploader.LOCAL_BEHAVIOUR_DELETE,
+            FileUploadWorker.LOCAL_BEHAVIOUR_DELETE,
             true,
             UploadFileOperation.CREATED_BY_USER,
             false,

+ 6 - 5
app/src/main/java/com/nextcloud/client/documentscan/GeneratePdfFromImagesWork.kt

@@ -32,10 +32,11 @@ import androidx.work.WorkerParameters
 import com.nextcloud.client.account.AnonymousUser
 import com.nextcloud.client.account.User
 import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.jobs.upload.FileUploadHelper
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.client.logger.Logger
 import com.owncloud.android.R
 import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.files.services.NameCollisionPolicy
 import com.owncloud.android.operations.UploadFileOperation
 import com.owncloud.android.ui.notifications.NotificationUtils
@@ -122,11 +123,11 @@ class GeneratePdfFromImagesWork(
     private fun uploadFile(user: User, uploadFolder: String, pdfPath: String) {
         val uploadPath = uploadFolder + OCFile.PATH_SEPARATOR + File(pdfPath).name
 
-        FileUploader.uploadNewFile(
+        FileUploadHelper().uploadNewFiles(
             user,
-            pdfPath,
-            uploadPath,
-            FileUploader.LOCAL_BEHAVIOUR_DELETE, // MIME type will be detected from file name
+            arrayOf(pdfPath),
+            arrayOf(uploadPath),
+            FileUploadWorker.LOCAL_BEHAVIOUR_DELETE, // MIME type will be detected from file name
             true,
             UploadFileOperation.CREATED_BY_USER,
             false,

+ 4 - 4
app/src/main/java/com/nextcloud/client/editimage/EditImageActivity.kt

@@ -34,17 +34,17 @@ import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
 import com.canhub.cropper.CropImageView
 import com.nextcloud.client.di.Injectable
+import com.nextcloud.client.jobs.upload.FileUploadHelper
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.utils.extensions.getParcelableArgument
 import com.owncloud.android.R
 import com.owncloud.android.databinding.ActivityEditImageBinding
 import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.files.services.NameCollisionPolicy
 import com.owncloud.android.lib.common.operations.OnRemoteOperationListener
 import com.owncloud.android.operations.UploadFileOperation
 import com.owncloud.android.ui.activity.FileActivity
 import com.owncloud.android.utils.DisplayUtils
-import com.owncloud.android.utils.FilesUploadHelper
 import com.owncloud.android.utils.MimeType
 import java.io.File
 
@@ -112,7 +112,7 @@ class EditImageActivity :
             resultUri?.substring(resultUri.lastIndexOf('.'))
 
         resultUri?.let {
-            FilesUploadHelper().uploadNewFiles(
+            FileUploadHelper().uploadNewFiles(
                 user = storageManager.user,
                 localPaths = arrayOf(it),
                 remotePaths = arrayOf(file.parentRemotePath + File.separator + newFileName),
@@ -121,7 +121,7 @@ class EditImageActivity :
                 requiresWifi = false,
                 requiresCharging = false,
                 nameCollisionPolicy = NameCollisionPolicy.RENAME,
-                localBehavior = FileUploader.LOCAL_BEHAVIOUR_DELETE
+                localBehavior = FileUploadWorker.LOCAL_BEHAVIOUR_DELETE
             )
         }
     }

+ 1 - 1
app/src/main/java/com/nextcloud/client/etm/EtmViewModel.kt

@@ -35,9 +35,9 @@ import com.nextcloud.client.etm.pages.EtmBackgroundJobsFragment
 import com.nextcloud.client.etm.pages.EtmFileTransferFragment
 import com.nextcloud.client.etm.pages.EtmMigrations
 import com.nextcloud.client.etm.pages.EtmPreferencesFragment
-import com.nextcloud.client.files.transfer.TransferManagerConnection
 import com.nextcloud.client.jobs.BackgroundJobManager
 import com.nextcloud.client.jobs.JobInfo
+import com.nextcloud.client.jobs.transfer.TransferManagerConnection
 import com.nextcloud.client.migrations.MigrationInfo
 import com.nextcloud.client.migrations.MigrationsDb
 import com.nextcloud.client.migrations.MigrationsManager

+ 2 - 2
app/src/main/java/com/nextcloud/client/etm/pages/EtmFileTransferFragment.kt

@@ -15,8 +15,8 @@ import androidx.recyclerview.widget.RecyclerView
 import com.nextcloud.client.etm.EtmBaseFragment
 import com.nextcloud.client.files.DownloadRequest
 import com.nextcloud.client.files.UploadRequest
-import com.nextcloud.client.files.transfer.Transfer
-import com.nextcloud.client.files.transfer.TransferManager
+import com.nextcloud.client.jobs.transfer.Transfer
+import com.nextcloud.client.jobs.transfer.TransferManager
 import com.owncloud.android.R
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.db.OCUpload

+ 2 - 2
app/src/main/java/com/nextcloud/client/files/Registry.kt

@@ -20,8 +20,8 @@
  */
 package com.nextcloud.client.files
 
-import com.nextcloud.client.files.transfer.Transfer
-import com.nextcloud.client.files.transfer.TransferState
+import com.nextcloud.client.jobs.transfer.Transfer
+import com.nextcloud.client.jobs.transfer.TransferState
 import com.owncloud.android.datamodel.OCFile
 import java.util.UUID
 import kotlin.math.max

+ 2 - 2
app/src/main/java/com/nextcloud/client/files/Request.kt

@@ -23,8 +23,8 @@ package com.nextcloud.client.files
 import android.os.Parcel
 import android.os.Parcelable
 import com.nextcloud.client.account.User
-import com.nextcloud.client.files.upload.PostUploadAction
-import com.nextcloud.client.files.upload.UploadTrigger
+import com.nextcloud.client.jobs.upload.PostUploadAction
+import com.nextcloud.client.jobs.upload.UploadTrigger
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.datamodel.UploadsStorageManager
 import com.owncloud.android.db.OCUpload

+ 5 - 4
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt

@@ -34,8 +34,9 @@ import com.nextcloud.client.device.DeviceInfo
 import com.nextcloud.client.device.PowerManagementService
 import com.nextcloud.client.documentscan.GeneratePDFUseCase
 import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
-import com.nextcloud.client.files.downloader.FileDownloadWorker
 import com.nextcloud.client.integrations.deck.DeckApi
+import com.nextcloud.client.jobs.download.FileDownloadWorker
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.client.logger.Logger
 import com.nextcloud.client.network.ConnectivityService
 import com.nextcloud.client.preferences.AppPreferences
@@ -102,7 +103,7 @@ class BackgroundJobFactory @Inject constructor(
                 CalendarBackupWork::class -> createCalendarBackupWork(context, workerParameters)
                 CalendarImportWork::class -> createCalendarImportWork(context, workerParameters)
                 FilesExportWork::class -> createFilesExportWork(context, workerParameters)
-                FilesUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
+                FileUploadWorker::class -> createFilesUploadWorker(context, workerParameters)
                 FileDownloadWorker::class -> createFilesDownloadWorker(context, workerParameters)
                 GeneratePdfFromImagesWork::class -> createPDFGenerateWork(context, workerParameters)
                 HealthStatusWork::class -> createHealthStatusWork(context, workerParameters)
@@ -241,8 +242,8 @@ class BackgroundJobFactory @Inject constructor(
         )
     }
 
-    private fun createFilesUploadWorker(context: Context, params: WorkerParameters): FilesUploadWorker {
-        return FilesUploadWorker(
+    private fun createFilesUploadWorker(context: Context, params: WorkerParameters): FileUploadWorker {
+        return FileUploadWorker(
             uploadsStorageManager,
             connectivityService,
             powerManagementService,

+ 1 - 0
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManager.kt

@@ -144,6 +144,7 @@ interface BackgroundJobManager {
     fun startFilesUploadJob(user: User)
     fun getFileUploads(user: User): LiveData<List<JobInfo>>
     fun cancelFilesUploadJob(user: User)
+    fun isStartFileUploadJobScheduled(user: User): Boolean
 
     fun cancelFilesDownloadJob(user: User, fileId: Long)
 

+ 17 - 4
app/src/main/java/com/nextcloud/client/jobs/BackgroundJobManagerImpl.kt

@@ -38,7 +38,8 @@ import com.nextcloud.client.account.User
 import com.nextcloud.client.core.Clock
 import com.nextcloud.client.di.Injectable
 import com.nextcloud.client.documentscan.GeneratePdfFromImagesWork
-import com.nextcloud.client.files.downloader.FileDownloadWorker
+import com.nextcloud.client.jobs.download.FileDownloadWorker
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.client.preferences.AppPreferences
 import com.nextcloud.utils.extensions.isWorkScheduled
 import com.owncloud.android.datamodel.OCFile
@@ -499,14 +500,26 @@ internal class BackgroundJobManagerImpl(
 
         workManager.enqueue(request)
     }
+
+    private fun startFileUploadJobTag(user: User): String {
+        return JOB_FILES_UPLOAD + user.accountName
+    }
+
+    override fun isStartFileUploadJobScheduled(user: User): Boolean {
+        return workManager.isWorkScheduled(startFileUploadJobTag(user))
+    }
+
     override fun startFilesUploadJob(user: User) {
-        val data = workDataOf(FilesUploadWorker.ACCOUNT to user.accountName)
+        val data = workDataOf(FileUploadWorker.ACCOUNT to user.accountName)
+
+        val tag = startFileUploadJobTag(user)
 
-        val request = oneTimeRequestBuilder(FilesUploadWorker::class, JOB_FILES_UPLOAD, user)
+        val request = oneTimeRequestBuilder(FileUploadWorker::class, JOB_FILES_UPLOAD, user)
+            .addTag(tag)
             .setInputData(data)
             .build()
 
-        workManager.enqueueUniqueWork(JOB_FILES_UPLOAD + user.accountName, ExistingWorkPolicy.KEEP, request)
+        workManager.enqueueUniqueWork(tag, ExistingWorkPolicy.KEEP, request)
     }
 
     private fun startFileDownloadJobTag(user: User, fileId: Long): String {

+ 3 - 3
app/src/main/java/com/nextcloud/client/jobs/ContactsBackupWork.kt

@@ -36,9 +36,9 @@ import androidx.work.WorkerParameters
 import com.nextcloud.client.account.User
 import com.nextcloud.client.account.UserAccountManager
 import com.nextcloud.client.files.UploadRequest
-import com.nextcloud.client.files.transfer.TransferManagerConnection
-import com.nextcloud.client.files.upload.PostUploadAction
-import com.nextcloud.client.files.upload.UploadTrigger
+import com.nextcloud.client.jobs.transfer.TransferManagerConnection
+import com.nextcloud.client.jobs.upload.PostUploadAction
+import com.nextcloud.client.jobs.upload.UploadTrigger
 import com.owncloud.android.R
 import com.owncloud.android.datamodel.ArbitraryDataProvider
 import com.owncloud.android.datamodel.FileDataStorageManager

+ 1 - 1
app/src/main/java/com/nextcloud/client/jobs/FilesExportWork.kt

@@ -33,7 +33,7 @@ import androidx.core.app.NotificationCompat
 import androidx.work.Worker
 import androidx.work.WorkerParameters
 import com.nextcloud.client.account.User
-import com.nextcloud.client.files.downloader.FileDownloadHelper
+import com.nextcloud.client.jobs.download.FileDownloadHelper
 import com.owncloud.android.R
 import com.owncloud.android.datamodel.FileDataStorageManager
 import com.owncloud.android.datamodel.OCFile

+ 7 - 6
app/src/main/java/com/nextcloud/client/jobs/FilesSyncWork.kt

@@ -34,6 +34,8 @@ import androidx.work.ForegroundInfo
 import androidx.work.WorkerParameters
 import com.nextcloud.client.account.UserAccountManager
 import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.jobs.upload.FileUploadHelper
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.client.network.ConnectivityService
 import com.nextcloud.client.preferences.SubFolderRule
 import com.owncloud.android.R
@@ -45,7 +47,6 @@ import com.owncloud.android.datamodel.MediaFolderType
 import com.owncloud.android.datamodel.SyncedFolder
 import com.owncloud.android.datamodel.SyncedFolderProvider
 import com.owncloud.android.datamodel.UploadsStorageManager
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.lib.common.utils.Log_OC
 import com.owncloud.android.operations.UploadFileOperation
 import com.owncloud.android.ui.activity.SettingsActivity
@@ -209,7 +210,7 @@ class FilesSyncWork(
             uploadAction = syncedFolder.uploadAction
         }
 
-        FileUploader.uploadNewFile(
+        FileUploadHelper.instance().uploadNewFiles(
             user,
             localPaths,
             remotePaths,
@@ -292,10 +293,10 @@ class FilesSyncWork(
 
     private fun getUploadAction(action: String): Int? {
         return when (action) {
-            "LOCAL_BEHAVIOUR_FORGET" -> FileUploader.LOCAL_BEHAVIOUR_FORGET
-            "LOCAL_BEHAVIOUR_MOVE" -> FileUploader.LOCAL_BEHAVIOUR_MOVE
-            "LOCAL_BEHAVIOUR_DELETE" -> FileUploader.LOCAL_BEHAVIOUR_DELETE
-            else -> FileUploader.LOCAL_BEHAVIOUR_FORGET
+            "LOCAL_BEHAVIOUR_FORGET" -> FileUploadWorker.LOCAL_BEHAVIOUR_FORGET
+            "LOCAL_BEHAVIOUR_MOVE" -> FileUploadWorker.LOCAL_BEHAVIOUR_MOVE
+            "LOCAL_BEHAVIOUR_DELETE" -> FileUploadWorker.LOCAL_BEHAVIOUR_DELETE
+            else -> FileUploadWorker.LOCAL_BEHAVIOUR_FORGET
         }
     }
 }

+ 0 - 466
app/src/main/java/com/nextcloud/client/jobs/FilesUploadWorker.kt

@@ -1,466 +0,0 @@
-/*
- *
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2022 Tobias Kaminsky
- * Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
- */
-
-package com.nextcloud.client.jobs
-
-import android.accounts.Account
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.os.Build
-import androidx.core.app.NotificationCompat
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
-import androidx.work.Worker
-import androidx.work.WorkerParameters
-import com.nextcloud.client.account.User
-import com.nextcloud.client.account.UserAccountManager
-import com.nextcloud.client.device.PowerManagementService
-import com.nextcloud.client.network.ConnectivityService
-import com.nextcloud.client.utils.FileUploaderDelegate
-import com.owncloud.android.R
-import com.owncloud.android.authentication.AuthenticatorActivity
-import com.owncloud.android.datamodel.FileDataStorageManager
-import com.owncloud.android.datamodel.ThumbnailsCacheManager
-import com.owncloud.android.datamodel.UploadsStorageManager
-import com.owncloud.android.db.OCUpload
-import com.owncloud.android.files.services.FileUploader
-import com.owncloud.android.lib.common.OwnCloudAccount
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
-import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
-import com.owncloud.android.lib.common.operations.RemoteOperationResult
-import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
-import com.owncloud.android.lib.common.utils.Log_OC
-import com.owncloud.android.lib.resources.files.FileUtils
-import com.owncloud.android.operations.UploadFileOperation
-import com.owncloud.android.ui.activity.ConflictsResolveActivity
-import com.owncloud.android.ui.activity.UploadListActivity
-import com.owncloud.android.ui.notifications.NotificationUtils
-import com.owncloud.android.utils.ErrorMessageAdapter
-import com.owncloud.android.utils.FilesUploadHelper
-import com.owncloud.android.utils.theme.ViewThemeUtils
-import java.io.File
-import java.security.SecureRandom
-
-@Suppress("LongParameterList")
-class FilesUploadWorker(
-    val uploadsStorageManager: UploadsStorageManager,
-    val connectivityService: ConnectivityService,
-    val powerManagementService: PowerManagementService,
-    val userAccountManager: UserAccountManager,
-    val viewThemeUtils: ViewThemeUtils,
-    val localBroadcastManager: LocalBroadcastManager,
-    private val backgroundJobManager: BackgroundJobManager,
-    val context: Context,
-    params: WorkerParameters
-) : Worker(context, params), OnDatatransferProgressListener {
-    private var lastPercent = 0
-    private val notificationBuilder: NotificationCompat.Builder =
-        NotificationUtils.newNotificationBuilder(context, viewThemeUtils)
-    private val notificationManager: NotificationManager =
-        context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
-    private val fileUploaderDelegate = FileUploaderDelegate()
-
-    override fun doWork(): Result {
-        backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
-
-        val accountName = inputData.getString(ACCOUNT)
-        if (accountName.isNullOrEmpty()) {
-            Log_OC.w(TAG, "User was null for file upload worker")
-
-            val result = Result.failure()
-            backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
-            return result // user account is needed
-        }
-
-        /*
-         * As pages are retrieved by sorting uploads by ID, if new uploads are added while uploading the current ones,
-         * they will be present in the pages that follow.
-         */
-        var currentPage = uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(-1, accountName)
-        while (currentPage.isNotEmpty() && !isStopped) {
-            Log_OC.d(TAG, "Handling ${currentPage.size} uploads for account $accountName")
-            val lastId = currentPage.last().uploadId
-            handlePendingUploads(currentPage, accountName)
-            currentPage =
-                uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(lastId, accountName)
-        }
-
-        Log_OC.d(TAG, "No more pending uploads for account $accountName, stopping work")
-        val result = Result.success()
-        backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
-        return result // user account is needed
-    }
-
-    private fun handlePendingUploads(uploads: List<OCUpload>, accountName: String) {
-        val user = userAccountManager.getUser(accountName)
-
-        for (upload in uploads) {
-            if (isStopped) {
-                break
-            }
-            // create upload file operation
-            if (user.isPresent) {
-                val uploadFileOperation = createUploadFileOperation(upload, user.get())
-
-                currentUploadFileOperation = uploadFileOperation
-                val result = upload(uploadFileOperation, user.get())
-                currentUploadFileOperation = null
-
-                fileUploaderDelegate.sendBroadcastUploadFinished(
-                    uploadFileOperation,
-                    result,
-                    uploadFileOperation.oldFile?.storagePath,
-                    context,
-                    localBroadcastManager
-                )
-            } else {
-                // user not present anymore, remove upload
-                uploadsStorageManager.removeUpload(upload.uploadId)
-            }
-        }
-    }
-
-    /**
-     * from @{link FileUploader#retryUploads()}
-     */
-    private fun createUploadFileOperation(upload: OCUpload, user: User): UploadFileOperation {
-        return UploadFileOperation(
-            uploadsStorageManager,
-            connectivityService,
-            powerManagementService,
-            user,
-            null,
-            upload,
-            upload.nameCollisionPolicy,
-            upload.localAction,
-            context,
-            upload.isUseWifiOnly,
-            upload.isWhileChargingOnly,
-            true,
-            FileDataStorageManager(user, context.contentResolver)
-        ).apply {
-            addDataTransferProgressListener(this@FilesUploadWorker)
-        }
-    }
-
-    @Suppress("TooGenericExceptionCaught")
-    private fun upload(uploadFileOperation: UploadFileOperation, user: User): RemoteOperationResult<Any?> {
-        lateinit var uploadResult: RemoteOperationResult<Any?>
-
-        // start notification
-        createNotification(uploadFileOperation)
-
-        try {
-            val storageManager = uploadFileOperation.storageManager
-
-            // always get client from client manager, to get fresh credentials in case of update
-            val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context)
-            val uploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, context)
-            uploadResult = uploadFileOperation.execute(uploadClient)
-
-            // generate new Thumbnail
-            val task = ThumbnailsCacheManager.ThumbnailGenerationTask(storageManager, user)
-            val file = File(uploadFileOperation.originalStoragePath)
-            val remoteId: String? = uploadFileOperation.file.remoteId
-            task.execute(ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, remoteId))
-        } catch (e: Exception) {
-            Log_OC.e(TAG, "Error uploading", e)
-            uploadResult = RemoteOperationResult<Any?>(e)
-        } finally {
-            // only update db if operation finished and worker didn't get canceled
-            if (!(isStopped && uploadResult.isCancelled)) {
-                uploadsStorageManager.updateDatabaseUploadResult(uploadResult, uploadFileOperation)
-
-                // / notify result
-                notifyUploadResult(uploadFileOperation, uploadResult)
-
-                // cancel notification
-                notificationManager.cancel(FOREGROUND_SERVICE_ID)
-            }
-        }
-
-        return uploadResult
-    }
-
-    /**
-     * adapted from [com.owncloud.android.files.services.FileUploader.notifyUploadStart]
-     */
-    private fun createNotification(uploadFileOperation: UploadFileOperation) {
-        val notificationActionIntent = Intent(context, FileUploader.UploadNotificationActionReceiver::class.java)
-        notificationActionIntent.putExtra(FileUploader.EXTRA_ACCOUNT_NAME, uploadFileOperation.user.accountName)
-        notificationActionIntent.putExtra(FileUploader.EXTRA_REMOTE_PATH, uploadFileOperation.remotePath)
-        notificationActionIntent.action = FileUploader.ACTION_CANCEL_BROADCAST
-
-        val pendingIntent = PendingIntent.getBroadcast(
-            context,
-            SecureRandom().nextInt(),
-            notificationActionIntent,
-            PendingIntent.FLAG_IMMUTABLE
-        )
-
-        notificationBuilder
-            .setOngoing(true)
-            .setSmallIcon(R.drawable.notification_icon)
-            .setTicker(context.getString(R.string.uploader_upload_in_progress_ticker))
-            .setProgress(MAX_PROGRESS, 0, false)
-            .setContentText(
-                String.format(
-                    context.getString(R.string.uploader_upload_in_progress_content),
-                    0,
-                    uploadFileOperation.fileName
-                )
-            )
-            .clearActions() // to make sure there is only one action
-            .addAction(R.drawable.ic_action_cancel_grey, context.getString(R.string.common_cancel), pendingIntent)
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            notificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD)
-        }
-
-        // includes a pending intent in the notification showing the details
-        val intent = UploadListActivity.createIntent(
-            uploadFileOperation.file,
-            uploadFileOperation.user,
-            Intent.FLAG_ACTIVITY_CLEAR_TOP,
-            context
-        )
-        notificationBuilder.setContentIntent(
-            PendingIntent.getActivity(
-                context,
-                System.currentTimeMillis().toInt(),
-                intent,
-                PendingIntent.FLAG_IMMUTABLE
-            )
-        )
-
-        if (!uploadFileOperation.isInstantPicture && !uploadFileOperation.isInstantVideo) {
-            notificationManager.notify(FOREGROUND_SERVICE_ID, notificationBuilder.build())
-        } // else wait until the upload really start (onTransferProgress is called), so that if it's discarded
-
-        // due to lack of Wifi, no notification is shown
-        // TODO generalize for automated uploads
-    }
-
-    private fun createConflictResolveAction(context: Context, uploadFileOperation: UploadFileOperation): PendingIntent {
-        val intent = ConflictsResolveActivity.createIntent(
-            uploadFileOperation.file,
-            uploadFileOperation.user,
-            uploadFileOperation.ocUploadId,
-            Intent.FLAG_ACTIVITY_CLEAR_TOP,
-            context
-        )
-
-        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
-            PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_MUTABLE)
-        } else {
-            PendingIntent.getActivity(
-                context,
-                0,
-                intent,
-                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
-            )
-        }
-    }
-
-    private fun addConflictResolveActionToNotification(uploadFileOperation: UploadFileOperation) {
-        val intent: PendingIntent = createConflictResolveAction(context, uploadFileOperation)
-
-        notificationBuilder.addAction(
-            R.drawable.ic_cloud_upload,
-            context.getString(R.string.upload_list_resolve_conflict),
-            intent
-        )
-    }
-
-    private fun addUploadListContentIntent(uploadFileOperation: UploadFileOperation) {
-        val uploadListIntent = createUploadListIntent(uploadFileOperation)
-
-        notificationBuilder.setContentIntent(
-            PendingIntent.getActivity(
-                context,
-                System.currentTimeMillis().toInt(),
-                uploadListIntent,
-                PendingIntent.FLAG_IMMUTABLE
-            )
-        )
-    }
-
-    /**
-     * adapted from [com.owncloud.android.files.services.FileUploader.notifyUploadResult]
-     */
-    private fun notifyUploadResult(
-        uploadFileOperation: UploadFileOperation,
-        uploadResult: RemoteOperationResult<Any?>
-    ) {
-        Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.code)
-
-        if (uploadResult.isSuccess) {
-            cancelOldErrorNotification(uploadFileOperation)
-            return
-        }
-
-        // Only notify if the upload fails
-        if (uploadResult.isCancelled) {
-            return
-        }
-
-        val notDelayed = uploadResult.code != ResultCode.DELAYED_FOR_WIFI &&
-            uploadResult.code != ResultCode.DELAYED_FOR_CHARGING &&
-            uploadResult.code != ResultCode.DELAYED_IN_POWER_SAVE_MODE
-
-        if (notDelayed &&
-            uploadResult.code != ResultCode.LOCAL_FILE_NOT_FOUND &&
-            uploadResult.code != ResultCode.LOCK_FAILED
-        ) {
-            var tickerId = R.string.uploader_upload_failed_ticker
-
-            // check credentials error
-            val needsToUpdateCredentials = uploadResult.code == ResultCode.UNAUTHORIZED
-            if (needsToUpdateCredentials) {
-                tickerId = R.string.uploader_upload_failed_credentials_error
-            } else if (uploadResult.code == ResultCode.SYNC_CONFLICT) {
-                // check file conflict
-                tickerId = R.string.uploader_upload_failed_sync_conflict_error
-            }
-            notificationBuilder
-                .setTicker(context.getString(tickerId))
-                .setContentTitle(context.getString(tickerId))
-                .setAutoCancel(true)
-                .setOngoing(false)
-                .setProgress(0, 0, false)
-                .clearActions()
-
-            val content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, uploadFileOperation, context.resources)
-
-            addUploadListContentIntent(uploadFileOperation)
-
-            if (uploadResult.code == ResultCode.SYNC_CONFLICT) {
-                addConflictResolveActionToNotification(uploadFileOperation)
-            }
-
-            if (needsToUpdateCredentials) {
-                createUpdateCredentialsNotification(uploadFileOperation.user.toPlatformAccount())
-            }
-
-            notificationBuilder.setContentText(content)
-
-            notificationManager.notify(
-                NotificationUtils.createUploadNotificationTag(uploadFileOperation.file),
-                NOTIFICATION_ERROR_ID,
-                notificationBuilder.build()
-            )
-        }
-    }
-
-    private fun createUploadListIntent(uploadFileOperation: UploadFileOperation): Intent {
-        return UploadListActivity.createIntent(
-            uploadFileOperation.file,
-            uploadFileOperation.user,
-            Intent.FLAG_ACTIVITY_CLEAR_TOP,
-            context
-        )
-    }
-
-    private fun createUpdateCredentialsNotification(account: Account) {
-        // let the user update credentials with one click
-        val updateAccountCredentials = Intent(context, AuthenticatorActivity::class.java)
-        updateAccountCredentials.putExtra(
-            AuthenticatorActivity.EXTRA_ACCOUNT,
-            account
-        )
-        updateAccountCredentials.putExtra(
-            AuthenticatorActivity.EXTRA_ACTION,
-            AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
-        )
-        updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-        updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
-        updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND)
-        notificationBuilder.setContentIntent(
-            PendingIntent.getActivity(
-                context,
-                System.currentTimeMillis().toInt(),
-                updateAccountCredentials,
-                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
-            )
-        )
-    }
-
-    /**
-     * see [com.owncloud.android.files.services.FileUploader.onTransferProgress]
-     */
-    override fun onTransferProgress(
-        progressRate: Long,
-        totalTransferredSoFar: Long,
-        totalToTransfer: Long,
-        fileAbsoluteName: String
-    ) {
-        val percent = (MAX_PROGRESS * totalTransferredSoFar.toDouble() / totalToTransfer.toDouble()).toInt()
-        if (percent != lastPercent) {
-            notificationBuilder.setProgress(MAX_PROGRESS, percent, false)
-            val fileName: String =
-                fileAbsoluteName.substring(fileAbsoluteName.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1)
-            val text = String.format(context.getString(R.string.uploader_upload_in_progress_content), percent, fileName)
-            notificationBuilder.setContentText(text)
-            notificationManager.notify(FOREGROUND_SERVICE_ID, notificationBuilder.build())
-            FilesUploadHelper.onTransferProgress(
-                currentUploadFileOperation?.user?.accountName,
-                currentUploadFileOperation?.remotePath,
-                progressRate,
-                totalTransferredSoFar,
-                totalToTransfer,
-                fileAbsoluteName
-            )
-            currentUploadFileOperation?.let { cancelOldErrorNotification(it) }
-        }
-        lastPercent = percent
-    }
-
-    private fun cancelOldErrorNotification(uploadFileOperation: UploadFileOperation) {
-        // cancel for old file because of file conflicts
-        if (uploadFileOperation.oldFile != null) {
-            notificationManager.cancel(
-                NotificationUtils.createUploadNotificationTag(uploadFileOperation.oldFile),
-                NOTIFICATION_ERROR_ID
-            )
-        }
-        notificationManager.cancel(
-            NotificationUtils.createUploadNotificationTag(uploadFileOperation.file),
-            NOTIFICATION_ERROR_ID
-        )
-    }
-
-    override fun onStopped() {
-        super.onStopped()
-        currentUploadFileOperation?.cancel(null)
-        notificationManager.cancel(FOREGROUND_SERVICE_ID)
-    }
-
-    companion object {
-        val TAG: String = FilesUploadWorker::class.java.simpleName
-        private const val FOREGROUND_SERVICE_ID: Int = 412
-        const val NOTIFICATION_ERROR_ID: Int = 413
-        private const val MAX_PROGRESS: Int = 100
-        const val ACCOUNT = "data_account"
-        var currentUploadFileOperation: UploadFileOperation? = null
-    }
-}

+ 2 - 2
app/src/main/java/com/nextcloud/client/files/downloader/DownloadNotificationManager.kt → app/src/main/java/com/nextcloud/client/jobs/download/DownloadNotificationManager.kt

@@ -19,7 +19,7 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.jobs.download
 
 import android.app.Notification
 import android.app.NotificationManager
@@ -45,7 +45,6 @@ class DownloadNotificationManager(
     private val context: Context,
     private val viewThemeUtils: ViewThemeUtils
 ) {
-
     private var notification: Notification
     private var notificationBuilder: NotificationCompat.Builder
     private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -53,6 +52,7 @@ class DownloadNotificationManager(
     init {
         notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply {
             setContentTitle(context.getString(R.string.downloader_download_in_progress_ticker))
+            setTicker(context.getString(R.string.downloader_download_in_progress_ticker))
             setSmallIcon(R.drawable.notification_icon)
             setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon))
 

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/downloader/DownloadTask.kt → app/src/main/java/com/nextcloud/client/jobs/download/DownloadTask.kt

@@ -17,7 +17,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.jobs.download
 
 import android.content.ContentResolver
 import android.content.Context

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/downloader/FileDownloadError.kt → app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadError.kt

@@ -19,7 +19,7 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.jobs.download
 
 enum class FileDownloadError {
     Failed, Cancelled

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/downloader/FileDownloadHelper.kt → app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadHelper.kt

@@ -19,7 +19,7 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.jobs.download
 
 import com.nextcloud.client.account.User
 import com.nextcloud.client.jobs.BackgroundJobManager

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/downloader/FileDownloadIntents.kt → app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadIntents.kt

@@ -19,7 +19,7 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.jobs.download
 
 import android.content.Context
 import android.content.Intent

+ 9 - 3
app/src/main/java/com/nextcloud/client/files/downloader/FileDownloadWorker.kt → app/src/main/java/com/nextcloud/client/jobs/download/FileDownloadWorker.kt

@@ -19,7 +19,7 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
-package com.nextcloud.client.files.downloader
+package com.nextcloud.client.jobs.download
 
 import android.accounts.Account
 import android.accounts.AccountManager
@@ -126,7 +126,11 @@ class FileDownloadWorker(
             val requestDownloads = getRequestDownloads()
 
             notificationManager =
-                DownloadNotificationManager(workerId ?: SecureRandom().nextInt(), context, viewThemeUtils)
+                DownloadNotificationManager(
+                    workerId ?: SecureRandom().nextInt(),
+                    context,
+                    viewThemeUtils
+                )
             addAccountUpdateListener()
 
             val foregroundInfo = ForegroundServiceHelper.createWorkerForegroundInfo(
@@ -263,7 +267,7 @@ class FileDownloadWorker(
         am.addOnAccountsUpdatedListener(this, null, false)
     }
 
-    @Suppress("TooGenericExceptionCaught")
+    @Suppress("TooGenericExceptionCaught", "DEPRECATION")
     private fun downloadFile(downloadKey: String) {
         currentDownload = pendingDownloads.get(downloadKey)
 
@@ -310,6 +314,7 @@ class FileDownloadWorker(
         }
     }
 
+    @Suppress("DEPRECATION")
     private fun getOCAccountForDownload(): OwnCloudAccount {
         val currentDownloadAccount = currentDownload?.user?.toPlatformAccount()
         val currentDownloadUser = accountManager.getUser(currentDownloadAccount?.name)
@@ -409,6 +414,7 @@ class FileDownloadWorker(
         }
     }
 
+    @Suppress("DEPRECATION")
     override fun onAccountsUpdated(accounts: Array<out Account>?) {
         if (!accountManager.exists(currentDownload?.user?.toPlatformAccount())) {
             currentDownload?.cancel()

+ 3 - 3
app/src/main/java/com/nextcloud/client/files/transfer/FileTransferService.kt → app/src/main/java/com/nextcloud/client/jobs/transfer/FileTransferService.kt

@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.transfer
+package com.nextcloud.client.jobs.transfer
 
 import android.app.Service
 import android.content.Context
@@ -30,8 +30,8 @@ import com.nextcloud.client.core.LocalBinder
 import com.nextcloud.client.device.PowerManagementService
 import com.nextcloud.client.files.Direction
 import com.nextcloud.client.files.Request
-import com.nextcloud.client.files.downloader.DownloadTask
-import com.nextcloud.client.files.upload.UploadTask
+import com.nextcloud.client.jobs.download.DownloadTask
+import com.nextcloud.client.jobs.upload.UploadTask
 import com.nextcloud.client.logger.Logger
 import com.nextcloud.client.network.ClientFactory
 import com.nextcloud.client.network.ConnectivityService

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/transfer/Transfer.kt → app/src/main/java/com/nextcloud/client/jobs/transfer/Transfer.kt

@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.transfer
+package com.nextcloud.client.jobs.transfer
 
 import com.nextcloud.client.files.Direction
 import com.nextcloud.client.files.DownloadRequest

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/transfer/TransferManager.kt → app/src/main/java/com/nextcloud/client/jobs/transfer/TransferManager.kt

@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.transfer
+package com.nextcloud.client.jobs.transfer
 
 import com.nextcloud.client.files.Request
 import com.owncloud.android.datamodel.OCFile

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/transfer/TransferManagerConnection.kt → app/src/main/java/com/nextcloud/client/jobs/transfer/TransferManagerConnection.kt

@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.transfer
+package com.nextcloud.client.jobs.transfer
 
 import android.content.Context
 import android.content.Intent

+ 3 - 3
app/src/main/java/com/nextcloud/client/files/transfer/TransferManagerImpl.kt → app/src/main/java/com/nextcloud/client/jobs/transfer/TransferManagerImpl.kt

@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.transfer
+package com.nextcloud.client.jobs.transfer
 
 import com.nextcloud.client.core.AsyncRunner
 import com.nextcloud.client.core.IsCancelled
@@ -28,8 +28,8 @@ import com.nextcloud.client.files.DownloadRequest
 import com.nextcloud.client.files.Registry
 import com.nextcloud.client.files.Request
 import com.nextcloud.client.files.UploadRequest
-import com.nextcloud.client.files.downloader.DownloadTask
-import com.nextcloud.client.files.upload.UploadTask
+import com.nextcloud.client.jobs.download.DownloadTask
+import com.nextcloud.client.jobs.upload.UploadTask
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.operations.UploadFileOperation
 import java.util.UUID

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/transfer/TransferState.kt → app/src/main/java/com/nextcloud/client/jobs/transfer/TransferState.kt

@@ -17,7 +17,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.transfer
+package com.nextcloud.client.jobs.transfer
 
 enum class TransferState {
     PENDING,

+ 277 - 0
app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt

@@ -0,0 +1,277 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client.jobs.upload
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.nextcloud.client.account.User
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.jobs.BackgroundJobManager
+import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.currentUploadFileOperation
+import com.nextcloud.client.network.ConnectivityService
+import com.nextcloud.java.util.Optional
+import com.owncloud.android.MainApp
+import com.owncloud.android.datamodel.OCFile
+import com.owncloud.android.datamodel.UploadsStorageManager
+import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus
+import com.owncloud.android.db.OCUpload
+import com.owncloud.android.db.UploadResult
+import com.owncloud.android.files.services.NameCollisionPolicy
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
+import com.owncloud.android.lib.common.utils.Log_OC
+import java.io.File
+import javax.inject.Inject
+
+@Suppress("TooManyFunctions")
+class FileUploadHelper {
+
+    @Inject
+    lateinit var backgroundJobManager: BackgroundJobManager
+
+    @Inject
+    lateinit var accountManager: UserAccountManager
+
+    @Inject
+    lateinit var uploadsStorageManager: UploadsStorageManager
+
+    init {
+        MainApp.getAppComponent().inject(this)
+    }
+
+    companion object {
+        private val TAG = FileUploadWorker::class.java.simpleName
+
+        val mBoundListeners = HashMap<String, OnDatatransferProgressListener>()
+
+        private var instance: FileUploadHelper? = null
+
+        fun instance(): FileUploadHelper {
+            return instance ?: synchronized(this) {
+                instance ?: FileUploadHelper().also { instance = it }
+            }
+        }
+
+        fun buildRemoteName(accountName: String, remotePath: String): String {
+            return accountName + remotePath
+        }
+    }
+
+    @Suppress("ComplexCondition")
+    fun retryFailedUploads(
+        uploadsStorageManager: UploadsStorageManager,
+        connectivityService: ConnectivityService,
+        accountManager: UserAccountManager,
+        powerManagementService: PowerManagementService
+    ) {
+        val failedUploads = uploadsStorageManager.failedUploads
+        if (failedUploads == null || failedUploads.isEmpty()) {
+            return
+        }
+
+        val (gotNetwork, _, gotWifi) = connectivityService.connectivity
+        val batteryStatus = powerManagementService.battery
+        val charging = batteryStatus.isCharging || batteryStatus.isFull
+        val isPowerSaving = powerManagementService.isPowerSavingEnabled
+        var uploadUser = Optional.empty<User>()
+        for (failedUpload in failedUploads) {
+            // 1. extract failed upload owner account and cache it between loops (expensive query)
+            if (!uploadUser.isPresent || !uploadUser.get().nameEquals(failedUpload.accountName)) {
+                uploadUser = accountManager.getUser(failedUpload.accountName)
+            }
+            val isDeleted = !File(failedUpload.localPath).exists()
+            if (isDeleted) {
+                // 2A. for deleted files, mark as permanently failed
+                if (failedUpload.lastResult != UploadResult.FILE_NOT_FOUND) {
+                    failedUpload.lastResult = UploadResult.FILE_NOT_FOUND
+                    uploadsStorageManager.updateUpload(failedUpload)
+                }
+            } else if (!isPowerSaving && gotNetwork &&
+                canUploadBeRetried(failedUpload, gotWifi, charging) && !connectivityService.isInternetWalled
+            ) {
+                // 2B. for existing local files, try restarting it if possible
+                retryUpload(failedUpload, uploadUser.get())
+            }
+        }
+    }
+
+    @Suppress("LongParameterList")
+    fun uploadNewFiles(
+        user: User,
+        localPaths: Array<String>,
+        remotePaths: Array<String>,
+        localBehavior: Int,
+        createRemoteFolder: Boolean,
+        createdBy: Int,
+        requiresWifi: Boolean,
+        requiresCharging: Boolean,
+        nameCollisionPolicy: NameCollisionPolicy
+    ) {
+        val uploads = localPaths.mapIndexed { index, localPath ->
+            OCUpload(localPath, remotePaths[index], user.accountName).apply {
+                this.nameCollisionPolicy = nameCollisionPolicy
+                isUseWifiOnly = requiresWifi
+                isWhileChargingOnly = requiresCharging
+                uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS
+                this.createdBy = createdBy
+                isCreateRemoteFolder = createRemoteFolder
+                localAction = localBehavior
+            }
+        }
+        uploadsStorageManager.storeUploads(uploads)
+        backgroundJobManager.startFilesUploadJob(user)
+    }
+
+    fun cancelFileUpload(remotePath: String, accountName: String) {
+        try {
+            val user = accountManager.getUser(accountName).get()
+
+            // need to update now table in mUploadsStorageManager,
+            // since the operation will not get to be run by FileUploader#uploadFile
+            uploadsStorageManager.removeUpload(accountName, remotePath)
+
+            cancelAndRestartUploadJob(user)
+        } catch (e: NoSuchElementException) {
+            Log_OC.e(TAG, "Error cancelling current upload because user does not exist!")
+        }
+    }
+
+    private fun cancelAndRestartUploadJob(user: User) {
+        backgroundJobManager.run {
+            cancelFilesUploadJob(user)
+            startFilesUploadJob(user)
+        }
+    }
+
+    @Suppress("ReturnCount")
+    fun isUploading(user: User?, file: OCFile?): Boolean {
+        if (user == null || file == null || !backgroundJobManager.isStartFileUploadJobScheduled(user)) {
+            return false
+        }
+
+        val upload: OCUpload = uploadsStorageManager.getUploadByRemotePath(file.remotePath) ?: return false
+        return upload.uploadStatus == UploadStatus.UPLOAD_IN_PROGRESS
+    }
+
+    private fun canUploadBeRetried(upload: OCUpload, gotWifi: Boolean, isCharging: Boolean): Boolean {
+        val file = File(upload.localPath)
+        val needsWifi = upload.isUseWifiOnly
+        val needsCharging = upload.isWhileChargingOnly
+        return file.exists() && (!needsWifi || gotWifi) && (!needsCharging || isCharging)
+    }
+
+    @Suppress("ReturnCount")
+    fun isUploadingNow(upload: OCUpload?): Boolean {
+        val currentUploadFileOperation = currentUploadFileOperation
+        if (currentUploadFileOperation == null || currentUploadFileOperation.user == null) return false
+        if (upload == null || upload.accountName != currentUploadFileOperation.user.accountName) return false
+
+        return if (currentUploadFileOperation.oldFile != null) {
+            // For file conflicts check old file remote path
+            upload.remotePath == currentUploadFileOperation.remotePath ||
+                upload.remotePath == currentUploadFileOperation.oldFile!!
+                    .remotePath
+        } else {
+            upload.remotePath == currentUploadFileOperation.remotePath
+        }
+    }
+
+    fun uploadUpdatedFile(
+        user: User,
+        existingFiles: Array<OCFile?>?,
+        behaviour: Int,
+        nameCollisionPolicy: NameCollisionPolicy
+    ) {
+        if (existingFiles == null) {
+            return
+        }
+
+        Log_OC.d(this, "upload updated file")
+
+        val uploads = existingFiles.map { file ->
+            file?.let {
+                OCUpload(file, user).apply {
+                    fileSize = file.fileLength
+                    this.nameCollisionPolicy = nameCollisionPolicy
+                    isCreateRemoteFolder = true
+                    this.localAction = behaviour
+                    isUseWifiOnly = false
+                    isWhileChargingOnly = false
+                    uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS
+                }
+            }
+        }
+        uploadsStorageManager.storeUploads(uploads)
+        backgroundJobManager.startFilesUploadJob(user)
+    }
+
+    fun retryUpload(upload: OCUpload, user: User) {
+        Log_OC.d(this, "retry upload")
+
+        upload.uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS
+        uploadsStorageManager.updateUpload(upload)
+
+        backgroundJobManager.startFilesUploadJob(user)
+    }
+
+    fun cancel(accountName: String) {
+        uploadsStorageManager.removeUploads(accountName)
+        cancelAndRestartUploadJob(accountManager.getUser(accountName).get())
+    }
+
+    fun addUploadTransferProgressListener(
+        listener: OnDatatransferProgressListener,
+        targetKey: String
+    ) {
+        mBoundListeners[targetKey] = listener
+    }
+
+    fun removeUploadTransferProgressListener(
+        listener: OnDatatransferProgressListener,
+        targetKey: String
+    ) {
+        if (mBoundListeners[targetKey] === listener) {
+            mBoundListeners.remove(targetKey)
+        }
+    }
+
+    class UploadNotificationActionReceiver : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            val accountName = intent.getStringExtra(FileUploadWorker.EXTRA_ACCOUNT_NAME)
+            val remotePath = intent.getStringExtra(FileUploadWorker.EXTRA_REMOTE_PATH)
+            val action = intent.action
+
+            if (FileUploadWorker.ACTION_CANCEL_BROADCAST == action) {
+                Log_OC.d(
+                    FileUploadWorker.TAG,
+                    "Cancel broadcast received for file " + remotePath + " at " + System.currentTimeMillis()
+                )
+                if (accountName == null || remotePath == null) {
+                    return
+                }
+
+                instance().cancelFileUpload(remotePath, accountName)
+            }
+        }
+    }
+}

+ 334 - 0
app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt

@@ -0,0 +1,334 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client.jobs.upload
+
+import android.app.PendingIntent
+import android.content.Context
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import androidx.work.Worker
+import androidx.work.WorkerParameters
+import com.nextcloud.client.account.User
+import com.nextcloud.client.account.UserAccountManager
+import com.nextcloud.client.device.PowerManagementService
+import com.nextcloud.client.jobs.BackgroundJobManager
+import com.nextcloud.client.jobs.BackgroundJobManagerImpl
+import com.nextcloud.client.network.ConnectivityService
+import com.nextcloud.model.WorkerState
+import com.nextcloud.model.WorkerStateLiveData
+import com.owncloud.android.datamodel.FileDataStorageManager
+import com.owncloud.android.datamodel.ThumbnailsCacheManager
+import com.owncloud.android.datamodel.UploadsStorageManager
+import com.owncloud.android.db.OCUpload
+import com.owncloud.android.lib.common.OwnCloudAccount
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
+import com.owncloud.android.lib.common.operations.RemoteOperationResult
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
+import com.owncloud.android.lib.common.utils.Log_OC
+import com.owncloud.android.operations.UploadFileOperation
+import com.owncloud.android.utils.ErrorMessageAdapter
+import com.owncloud.android.utils.theme.ViewThemeUtils
+import java.io.File
+
+@Suppress("LongParameterList")
+class FileUploadWorker(
+    val uploadsStorageManager: UploadsStorageManager,
+    val connectivityService: ConnectivityService,
+    val powerManagementService: PowerManagementService,
+    val userAccountManager: UserAccountManager,
+    val viewThemeUtils: ViewThemeUtils,
+    val localBroadcastManager: LocalBroadcastManager,
+    private val backgroundJobManager: BackgroundJobManager,
+    val context: Context,
+    params: WorkerParameters
+) : Worker(context, params), OnDatatransferProgressListener {
+
+    companion object {
+        val TAG: String = FileUploadWorker::class.java.simpleName
+
+        const val NOTIFICATION_ERROR_ID: Int = 413
+        private const val MAX_PROGRESS: Int = 100
+        const val ACCOUNT = "data_account"
+        var currentUploadFileOperation: UploadFileOperation? = null
+
+        private const val UPLOADS_ADDED_MESSAGE = "UPLOADS_ADDED"
+        private const val UPLOAD_START_MESSAGE = "UPLOAD_START"
+        private const val UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"
+
+        const val EXTRA_UPLOAD_RESULT = "RESULT"
+        const val EXTRA_REMOTE_PATH = "REMOTE_PATH"
+        const val EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH"
+        const val EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH"
+        const val EXTRA_LINKED_TO_PATH = "LINKED_TO"
+        const val ACCOUNT_NAME = "ACCOUNT_NAME"
+        const val EXTRA_ACCOUNT_NAME = "ACCOUNT_NAME"
+        const val ACTION_CANCEL_BROADCAST = "CANCEL"
+        const val LOCAL_BEHAVIOUR_COPY = 0
+        const val LOCAL_BEHAVIOUR_MOVE = 1
+        const val LOCAL_BEHAVIOUR_FORGET = 2
+        const val LOCAL_BEHAVIOUR_DELETE = 3
+
+        fun getUploadsAddedMessage(): String {
+            return FileUploadWorker::class.java.name + UPLOADS_ADDED_MESSAGE
+        }
+
+        fun getUploadStartMessage(): String {
+            return FileUploadWorker::class.java.name + UPLOAD_START_MESSAGE
+        }
+
+        fun getUploadFinishMessage(): String {
+            return FileUploadWorker::class.java.name + UPLOAD_FINISH_MESSAGE
+        }
+    }
+
+    private var lastPercent = 0
+    private val notificationManager = UploadNotificationManager(context, viewThemeUtils)
+    private val intents = FileUploaderIntents(context)
+    private val fileUploaderDelegate = FileUploaderDelegate()
+
+    @Suppress("TooGenericExceptionCaught")
+    override fun doWork(): Result {
+        return try {
+            backgroundJobManager.logStartOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class))
+            val result = retrievePagesBySortingUploadsByID()
+            backgroundJobManager.logEndOfWorker(BackgroundJobManagerImpl.formatClassTag(this::class), result)
+            result
+        } catch (t: Throwable) {
+            Log_OC.e(TAG, "Error caught at FileUploadWorker " + t.localizedMessage)
+            Result.failure()
+        }
+    }
+
+    override fun onStopped() {
+        Log_OC.e(TAG, "FileUploadWorker stopped")
+
+        setIdleWorkerState()
+        currentUploadFileOperation?.cancel(null)
+        notificationManager.dismissWorkerNotifications()
+
+        super.onStopped()
+    }
+
+    private fun setWorkerState(user: User?, uploads: List<OCUpload>) {
+        WorkerStateLiveData.instance().setWorkState(WorkerState.Upload(user, uploads))
+    }
+
+    private fun setIdleWorkerState() {
+        WorkerStateLiveData.instance().setWorkState(WorkerState.Idle)
+    }
+
+    private fun retrievePagesBySortingUploadsByID(): Result {
+        val accountName = inputData.getString(ACCOUNT) ?: return Result.failure()
+        var currentPage = uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(-1, accountName)
+
+        while (currentPage.isNotEmpty() && !isStopped) {
+            Log_OC.d(TAG, "Handling ${currentPage.size} uploads for account $accountName")
+            val lastId = currentPage.last().uploadId
+            uploadFiles(currentPage, accountName)
+            currentPage =
+                uploadsStorageManager.getCurrentAndPendingUploadsForAccountPageAscById(lastId, accountName)
+        }
+
+        if (isStopped) {
+            Log_OC.d(TAG, "FileUploadWorker for account $accountName was stopped")
+        } else {
+            Log_OC.d(TAG, "No more pending uploads for account $accountName, stopping work")
+        }
+        return Result.success()
+    }
+
+    private fun uploadFiles(uploads: List<OCUpload>, accountName: String) {
+        val user = userAccountManager.getUser(accountName)
+        setWorkerState(user.get(), uploads)
+
+        for (upload in uploads) {
+            if (isStopped) {
+                break
+            }
+
+            if (user.isPresent) {
+                val operation = createUploadFileOperation(upload, user.get())
+
+                currentUploadFileOperation = operation
+                val result = upload(operation, user.get())
+                currentUploadFileOperation = null
+
+                fileUploaderDelegate.sendBroadcastUploadFinished(
+                    operation,
+                    result,
+                    operation.oldFile?.storagePath,
+                    context,
+                    localBroadcastManager
+                )
+            } else {
+                uploadsStorageManager.removeUpload(upload.uploadId)
+            }
+        }
+    }
+
+    private fun createUploadFileOperation(upload: OCUpload, user: User): UploadFileOperation {
+        return UploadFileOperation(
+            uploadsStorageManager,
+            connectivityService,
+            powerManagementService,
+            user,
+            null,
+            upload,
+            upload.nameCollisionPolicy,
+            upload.localAction,
+            context,
+            upload.isUseWifiOnly,
+            upload.isWhileChargingOnly,
+            true,
+            FileDataStorageManager(user, context.contentResolver)
+        ).apply {
+            addDataTransferProgressListener(this@FileUploadWorker)
+        }
+    }
+
+    @Suppress("TooGenericExceptionCaught", "DEPRECATION")
+    private fun upload(operation: UploadFileOperation, user: User): RemoteOperationResult<Any?> {
+        lateinit var result: RemoteOperationResult<Any?>
+
+        notificationManager.prepareForStart(
+            operation,
+            intents.startIntent(operation),
+            intents.notificationStartIntent(operation)
+        )
+
+        try {
+            val storageManager = operation.storageManager
+            val ocAccount = OwnCloudAccount(user.toPlatformAccount(), context)
+            val uploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, context)
+            result = operation.execute(uploadClient)
+
+            val task = ThumbnailsCacheManager.ThumbnailGenerationTask(storageManager, user)
+            val file = File(operation.originalStoragePath)
+            val remoteId: String? = operation.file.remoteId
+            task.execute(ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, remoteId))
+        } catch (e: Exception) {
+            Log_OC.e(TAG, "Error uploading", e)
+            result = RemoteOperationResult<Any?>(e)
+        } finally {
+            cleanupUploadProcess(result, operation)
+        }
+
+        return result
+    }
+
+    private fun cleanupUploadProcess(result: RemoteOperationResult<Any?>, operation: UploadFileOperation) {
+        if (!(isStopped && result.isCancelled)) {
+            uploadsStorageManager.updateDatabaseUploadResult(result, operation)
+            notifyUploadResult(operation, result)
+            notificationManager.dismissWorkerNotifications()
+        }
+    }
+
+    @Suppress("ReturnCount")
+    private fun notifyUploadResult(
+        uploadFileOperation: UploadFileOperation,
+        uploadResult: RemoteOperationResult<Any?>
+    ) {
+        Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.code)
+
+        if (uploadResult.isSuccess) {
+            notificationManager.dismissOldErrorNotification(uploadFileOperation)
+            return
+        }
+
+        if (uploadResult.isCancelled) {
+            return
+        }
+
+        val notDelayed = uploadResult.code !in setOf(
+            ResultCode.DELAYED_FOR_WIFI,
+            ResultCode.DELAYED_FOR_CHARGING,
+            ResultCode.DELAYED_IN_POWER_SAVE_MODE
+        )
+
+        val isValidFile = uploadResult.code !in setOf(
+            ResultCode.LOCAL_FILE_NOT_FOUND,
+            ResultCode.LOCK_FAILED
+        )
+
+        if (!notDelayed || !isValidFile) {
+            return
+        }
+
+        notificationManager.run {
+            val errorMessage = ErrorMessageAdapter.getErrorCauseMessage(
+                uploadResult,
+                uploadFileOperation,
+                context.resources
+            )
+
+            // FIXME SYNC_CONFLICT passes wrong OCFile, check ConflictsResolveActivity.createIntent usage
+            val conflictResolveIntent = if (uploadResult.code == ResultCode.SYNC_CONFLICT) {
+                intents.conflictResolveActionIntents(context, uploadFileOperation)
+            } else {
+                null
+            }
+            val credentialIntent: PendingIntent? = if (uploadResult.code == ResultCode.UNAUTHORIZED) {
+                intents.credentialIntent(uploadFileOperation)
+            } else {
+                null
+            }
+            notifyForFailedResult(uploadResult.code, conflictResolveIntent, credentialIntent, errorMessage)
+            showNewNotification(uploadFileOperation)
+        }
+    }
+
+    override fun onTransferProgress(
+        progressRate: Long,
+        totalTransferredSoFar: Long,
+        totalToTransfer: Long,
+        fileAbsoluteName: String
+    ) {
+        val percent = (MAX_PROGRESS * totalTransferredSoFar.toDouble() / totalToTransfer.toDouble()).toInt()
+
+        if (percent != lastPercent) {
+            notificationManager.run {
+                updateUploadProgress(fileAbsoluteName, percent, currentUploadFileOperation)
+
+                val accountName = currentUploadFileOperation?.user?.accountName
+                val remotePath = currentUploadFileOperation?.remotePath
+
+                if (accountName != null && remotePath != null) {
+                    val key: String =
+                        FileUploadHelper.buildRemoteName(accountName, remotePath)
+                    val boundListener = FileUploadHelper.mBoundListeners[key]
+
+                    boundListener?.onTransferProgress(
+                        progressRate,
+                        totalTransferredSoFar,
+                        totalToTransfer,
+                        fileAbsoluteName
+                    )
+                }
+
+                dismissOldErrorNotification(currentUploadFileOperation)
+            }
+        }
+
+        lastPercent = percent
+    }
+}

+ 16 - 18
app/src/main/java/com/nextcloud/client/utils/FileUploaderDelegate.kt → app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderDelegate.kt

@@ -1,10 +1,9 @@
 /*
- *
  * Nextcloud Android client application
  *
- * @author Tobias Kaminsky
- * Copyright (C) 2022 Tobias Kaminsky
- * Copyright (C) 2022 Nextcloud GmbH
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 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
@@ -20,12 +19,11 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
-package com.nextcloud.client.utils
+package com.nextcloud.client.jobs.upload
 
 import android.content.Context
 import android.content.Intent
 import androidx.localbroadcastmanager.content.LocalBroadcastManager
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.lib.common.operations.RemoteOperationResult
 import com.owncloud.android.operations.UploadFileOperation
 
@@ -36,7 +34,7 @@ class FileUploaderDelegate {
      * TODO - no more broadcasts, replace with a callback to subscribed listeners once we drop FileUploader
      */
     fun sendBroadcastUploadsAdded(context: Context, localBroadcastManager: LocalBroadcastManager) {
-        val start = Intent(FileUploader.getUploadsAddedMessage())
+        val start = Intent(FileUploadWorker.getUploadsAddedMessage())
         // nothing else needed right now
         start.setPackage(context.packageName)
         localBroadcastManager.sendBroadcast(start)
@@ -54,10 +52,10 @@ class FileUploaderDelegate {
         context: Context,
         localBroadcastManager: LocalBroadcastManager
     ) {
-        val start = Intent(FileUploader.getUploadStartMessage())
-        start.putExtra(FileUploader.EXTRA_REMOTE_PATH, upload.remotePath) // real remote
-        start.putExtra(FileUploader.EXTRA_OLD_FILE_PATH, upload.originalStoragePath)
-        start.putExtra(FileUploader.ACCOUNT_NAME, upload.user.accountName)
+        val start = Intent(FileUploadWorker.getUploadStartMessage())
+        start.putExtra(FileUploadWorker.EXTRA_REMOTE_PATH, upload.remotePath) // real remote
+        start.putExtra(FileUploadWorker.EXTRA_OLD_FILE_PATH, upload.originalStoragePath)
+        start.putExtra(FileUploadWorker.ACCOUNT_NAME, upload.user.accountName)
         start.setPackage(context.packageName)
         localBroadcastManager.sendBroadcast(start)
     }
@@ -78,17 +76,17 @@ class FileUploaderDelegate {
         context: Context,
         localBroadcastManager: LocalBroadcastManager
     ) {
-        val end = Intent(FileUploader.getUploadFinishMessage())
+        val end = Intent(FileUploadWorker.getUploadFinishMessage())
         // real remote path, after possible automatic renaming
-        end.putExtra(FileUploader.EXTRA_REMOTE_PATH, upload.remotePath)
+        end.putExtra(FileUploadWorker.EXTRA_REMOTE_PATH, upload.remotePath)
         if (upload.wasRenamed()) {
-            end.putExtra(FileUploader.EXTRA_OLD_REMOTE_PATH, upload.oldFile!!.remotePath)
+            end.putExtra(FileUploadWorker.EXTRA_OLD_REMOTE_PATH, upload.oldFile!!.remotePath)
         }
-        end.putExtra(FileUploader.EXTRA_OLD_FILE_PATH, upload.originalStoragePath)
-        end.putExtra(FileUploader.ACCOUNT_NAME, upload.user.accountName)
-        end.putExtra(FileUploader.EXTRA_UPLOAD_RESULT, uploadResult.isSuccess)
+        end.putExtra(FileUploadWorker.EXTRA_OLD_FILE_PATH, upload.originalStoragePath)
+        end.putExtra(FileUploadWorker.ACCOUNT_NAME, upload.user.accountName)
+        end.putExtra(FileUploadWorker.EXTRA_UPLOAD_RESULT, uploadResult.isSuccess)
         if (unlinkedFromRemotePath != null) {
-            end.putExtra(FileUploader.EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath)
+            end.putExtra(FileUploadWorker.EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath)
         }
         end.setPackage(context.packageName)
         localBroadcastManager.sendBroadcast(end)

+ 136 - 0
app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt

@@ -0,0 +1,136 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client.jobs.upload
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import com.owncloud.android.authentication.AuthenticatorActivity
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
+import com.owncloud.android.operations.UploadFileOperation
+import com.owncloud.android.ui.activity.ConflictsResolveActivity.Companion.createIntent
+import com.owncloud.android.ui.activity.UploadListActivity
+import java.security.SecureRandom
+
+class FileUploaderIntents(private val context: Context) {
+
+    private val secureRandomGenerator = SecureRandom()
+
+    fun startIntent(operation: UploadFileOperation): PendingIntent {
+        val intent = Intent(
+            context,
+            FileUploadHelper.UploadNotificationActionReceiver::class.java
+        ).apply {
+            putExtra(FileUploadWorker.EXTRA_ACCOUNT_NAME, operation.user.accountName)
+            putExtra(FileUploadWorker.EXTRA_REMOTE_PATH, operation.remotePath)
+            action = FileUploadWorker.ACTION_CANCEL_BROADCAST
+        }
+
+        return PendingIntent.getBroadcast(
+            context,
+            secureRandomGenerator.nextInt(),
+            intent,
+            PendingIntent.FLAG_IMMUTABLE
+        )
+    }
+
+    fun credentialIntent(operation: UploadFileOperation): PendingIntent {
+        val intent = Intent(context, AuthenticatorActivity::class.java).apply {
+            putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, operation.user.toPlatformAccount())
+            putExtra(AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN)
+            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+            addFlags(Intent.FLAG_FROM_BACKGROUND)
+        }
+
+        return PendingIntent.getActivity(
+            context,
+            System.currentTimeMillis().toInt(),
+            intent,
+            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+        )
+    }
+
+    fun resultIntent(resultCode: ResultCode, operation: UploadFileOperation): PendingIntent {
+        val intent = if (resultCode == ResultCode.SYNC_CONFLICT) {
+            createIntent(
+                operation.file,
+                operation.user,
+                operation.ocUploadId,
+                Intent.FLAG_ACTIVITY_CLEAR_TOP,
+                context
+            )
+        } else {
+            UploadListActivity.createIntent(
+                operation.file,
+                operation.user,
+                Intent.FLAG_ACTIVITY_CLEAR_TOP,
+                context
+            )
+        }
+
+        return PendingIntent.getActivity(
+            context,
+            System.currentTimeMillis().toInt(),
+            intent,
+            PendingIntent.FLAG_IMMUTABLE
+        )
+    }
+
+    fun notificationStartIntent(operation: UploadFileOperation): PendingIntent {
+        val intent = UploadListActivity.createIntent(
+            operation.file,
+            operation.user,
+            Intent.FLAG_ACTIVITY_CLEAR_TOP,
+            context
+        )
+
+        return PendingIntent.getActivity(
+            context,
+            System.currentTimeMillis().toInt(),
+            intent,
+            PendingIntent.FLAG_IMMUTABLE
+        )
+    }
+
+    fun conflictResolveActionIntents(context: Context, uploadFileOperation: UploadFileOperation): PendingIntent {
+        val intent = createIntent(
+            uploadFileOperation.file,
+            uploadFileOperation.user,
+            uploadFileOperation.ocUploadId,
+            Intent.FLAG_ACTIVITY_CLEAR_TOP,
+            context
+        )
+
+        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+            PendingIntent.getActivity(context, SecureRandom().nextInt(), intent, PendingIntent.FLAG_MUTABLE)
+        } else {
+            PendingIntent.getActivity(
+                context,
+                SecureRandom().nextInt(),
+                intent,
+                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+            )
+        }
+    }
+}

+ 5 - 7
app/src/main/java/com/nextcloud/client/files/upload/PostUploadAction.kt → app/src/main/java/com/nextcloud/client/jobs/upload/PostUploadAction.kt

@@ -17,13 +17,11 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.upload
-
-import com.owncloud.android.files.services.FileUploader
+package com.nextcloud.client.jobs.upload
 
 enum class PostUploadAction(val value: Int) {
-    NONE(FileUploader.LOCAL_BEHAVIOUR_FORGET),
-    COPY_TO_APP(FileUploader.LOCAL_BEHAVIOUR_COPY),
-    MOVE_TO_APP(FileUploader.LOCAL_BEHAVIOUR_MOVE),
-    DELETE_SOURCE(FileUploader.LOCAL_BEHAVIOUR_DELETE)
+    NONE(FileUploadWorker.LOCAL_BEHAVIOUR_FORGET),
+    COPY_TO_APP(FileUploadWorker.LOCAL_BEHAVIOUR_COPY),
+    MOVE_TO_APP(FileUploadWorker.LOCAL_BEHAVIOUR_MOVE),
+    DELETE_SOURCE(FileUploadWorker.LOCAL_BEHAVIOUR_DELETE)
 }

+ 195 - 0
app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt

@@ -0,0 +1,195 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Alper Ozturk
+ * Copyright (C) 2023 Alper Ozturk
+ * Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
+ */
+
+package com.nextcloud.client.jobs.upload
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.graphics.BitmapFactory
+import android.os.Build
+import androidx.core.app.NotificationCompat
+import com.owncloud.android.R
+import com.owncloud.android.lib.common.operations.RemoteOperationResult
+import com.owncloud.android.lib.resources.files.FileUtils
+import com.owncloud.android.operations.UploadFileOperation
+import com.owncloud.android.ui.notifications.NotificationUtils
+import com.owncloud.android.utils.theme.ViewThemeUtils
+
+class UploadNotificationManager(private val context: Context, private val viewThemeUtils: ViewThemeUtils) {
+    companion object {
+        private const val ID = 411
+    }
+
+    private var notification: Notification? = null
+    private var notificationBuilder: NotificationCompat.Builder
+    private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
+    init {
+        notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply {
+            setContentTitle(context.getString(R.string.foreground_service_upload))
+            setSmallIcon(R.drawable.notification_icon)
+            setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon))
+
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD)
+            }
+        }
+
+        notification = notificationBuilder.build()
+    }
+
+    @Suppress("MagicNumber")
+    fun prepareForStart(upload: UploadFileOperation, pendingIntent: PendingIntent, startIntent: PendingIntent) {
+        notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply {
+            setSmallIcon(R.drawable.notification_icon)
+            setOngoing(true)
+            setTicker(context.getString(R.string.foreground_service_upload))
+            setContentTitle(context.getString(R.string.uploader_upload_in_progress_ticker))
+            setProgress(100, 0, false)
+            setContentText(
+                String.format(
+                    context.getString(R.string.uploader_upload_in_progress),
+                    0,
+                    upload.fileName
+                )
+            )
+            clearActions()
+
+            addAction(
+                R.drawable.ic_action_cancel_grey,
+                context.getString(R.string.common_cancel),
+                pendingIntent
+            )
+
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD)
+            }
+
+            setContentIntent(startIntent)
+        }
+
+        if (!upload.isInstantPicture && !upload.isInstantVideo) {
+            showNotification()
+        }
+    }
+
+    fun notifyForFailedResult(
+        resultCode: RemoteOperationResult.ResultCode,
+        conflictsResolveIntent: PendingIntent?,
+        credentialIntent: PendingIntent?,
+        errorMessage: String
+    ) {
+        val textId = resultTitle(resultCode)
+
+        notificationBuilder.run {
+            setTicker(context.getString(textId))
+            setContentTitle(context.getString(textId))
+            setAutoCancel(false)
+            setOngoing(false)
+            setProgress(0, 0, false)
+            clearActions()
+
+            conflictsResolveIntent?.let {
+                addAction(
+                    R.drawable.ic_cloud_upload,
+                    R.string.upload_list_resolve_conflict,
+                    it
+                )
+            }
+
+            credentialIntent?.let {
+                setContentIntent(it)
+            }
+
+            setContentText(errorMessage)
+        }
+    }
+
+    private fun resultTitle(resultCode: RemoteOperationResult.ResultCode): Int {
+        val needsToUpdateCredentials = (resultCode == RemoteOperationResult.ResultCode.UNAUTHORIZED)
+
+        return if (needsToUpdateCredentials) {
+            R.string.uploader_upload_failed_credentials_error
+        } else if (resultCode == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
+            R.string.uploader_upload_failed_sync_conflict_error
+        } else {
+            R.string.uploader_upload_failed_ticker
+        }
+    }
+
+    fun addAction(icon: Int, textId: Int, intent: PendingIntent) {
+        notificationBuilder.addAction(
+            icon,
+            context.getString(textId),
+            intent
+        )
+    }
+
+    fun showNewNotification(operation: UploadFileOperation) {
+        notificationManager.notify(
+            NotificationUtils.createUploadNotificationTag(operation.file),
+            FileUploadWorker.NOTIFICATION_ERROR_ID,
+            notificationBuilder.build()
+        )
+    }
+
+    private fun showNotification() {
+        notificationManager.notify(ID, notificationBuilder.build())
+    }
+
+    @Suppress("MagicNumber")
+    fun updateUploadProgress(filePath: String, percent: Int, currentOperation: UploadFileOperation?) {
+        notificationBuilder.run {
+            setProgress(100, percent, false)
+            val fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1)
+            val text = String.format(context.getString(R.string.uploader_upload_in_progress), percent, fileName)
+            setContentText(text)
+
+            showNotification()
+            dismissOldErrorNotification(currentOperation)
+        }
+    }
+
+    fun dismissOldErrorNotification(operation: UploadFileOperation?) {
+        if (operation == null) {
+            return
+        }
+
+        dismissOldErrorNotification(operation.file.remotePath, operation.file.storagePath)
+
+        operation.oldFile?.let {
+            dismissOldErrorNotification(it.remotePath, it.storagePath)
+        }
+    }
+
+    fun dismissOldErrorNotification(remotePath: String, localPath: String) {
+        notificationManager.cancel(
+            NotificationUtils.createUploadNotificationTag(remotePath, localPath),
+            FileUploadWorker.NOTIFICATION_ERROR_ID
+        )
+    }
+
+    fun dismissWorkerNotifications() {
+        notificationManager.cancel(ID)
+    }
+}

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/upload/UploadTask.kt → app/src/main/java/com/nextcloud/client/jobs/upload/UploadTask.kt

@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.upload
+package com.nextcloud.client.jobs.upload
 
 import android.content.Context
 import com.nextcloud.client.account.User

+ 1 - 1
app/src/main/java/com/nextcloud/client/files/upload/UploadTrigger.kt → app/src/main/java/com/nextcloud/client/jobs/upload/UploadTrigger.kt

@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
-package com.nextcloud.client.files.upload
+package com.nextcloud.client.jobs.upload
 
 import com.owncloud.android.operations.UploadFileOperation
 

+ 2 - 0
app/src/main/java/com/nextcloud/model/WorkerState.kt

@@ -22,9 +22,11 @@
 package com.nextcloud.model
 
 import com.nextcloud.client.account.User
+import com.owncloud.android.db.OCUpload
 import com.owncloud.android.operations.DownloadFileOperation
 
 sealed class WorkerState {
     object Idle : WorkerState()
     class Download(var user: User?, var currentDownload: DownloadFileOperation?) : WorkerState()
+    class Upload(var user: User?, var uploads: List<OCUpload>) : WorkerState()
 }

+ 3 - 3
app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.java

@@ -34,11 +34,11 @@ import android.os.RemoteException;
 
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.account.User;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.db.UploadResult;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -726,7 +726,7 @@ public class UploadsStorageManager extends Observable {
                 upload.getRemotePath()
                         );
         } else {
-            String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
+            String localPath = (FileUploadWorker.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
                 ? upload.getStoragePath() : null;
 
             if (uploadResult.isSuccess()) {
@@ -753,7 +753,7 @@ public class UploadsStorageManager extends Observable {
      * Updates the persistent upload database with an upload now in progress.
      */
     public void updateDatabaseUploadStart(UploadFileOperation upload) {
-        String localPath = (FileUploader.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
+        String localPath = (FileUploadWorker.LOCAL_BEHAVIOUR_MOVE == upload.getLocalBehaviour())
             ? upload.getStoragePath() : null;
 
         updateUploadStatus(

+ 5 - 4
app/src/main/java/com/owncloud/android/db/OCUpload.java

@@ -27,10 +27,11 @@ import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.nextcloud.client.account.User;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.UploadFileOperation;
@@ -171,7 +172,7 @@ public class OCUpload implements Parcelable {
         accountName = "";
         fileSize = -1;
         uploadId = -1;
-        localAction = FileUploader.LOCAL_BEHAVIOUR_COPY;
+        localAction = FileUploadWorker.LOCAL_BEHAVIOUR_COPY;
         nameCollisionPolicy = NameCollisionPolicy.DEFAULT;
         createRemoteFolder = false;
         uploadStatus = UploadStatus.UPLOAD_IN_PROGRESS;
@@ -182,9 +183,9 @@ public class OCUpload implements Parcelable {
         folderUnlockToken = "";
     }
 
-    public void setDataFixed(FileUploader.FileUploaderBinder binder) {
+    public void setDataFixed(FileUploadHelper uploadHelper) {
         fixedUploadStatus = uploadStatus;
-        fixedUploadingNow = binder != null && binder.isUploadingNow(this);
+        fixedUploadingNow = uploadHelper != null && uploadHelper.isUploadingNow(this);
         fixedUploadEndTimeStamp = uploadEndTimestamp;
         fixedUploadId = uploadId;
     }

+ 12 - 14
app/src/main/java/com/owncloud/android/files/FileMenuFilter.java

@@ -31,12 +31,12 @@ import android.view.Menu;
 import com.nextcloud.android.files.FileLockingHelper;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.editimage.EditImageActivity;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.utils.EditorUtils;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.activity.ComponentsGetter;
@@ -383,10 +383,9 @@ public class FileMenuFilter {
         boolean synchronizing = false;
         if (componentsGetter != null && !files.isEmpty() && user != null) {
             OperationsServiceBinder opsBinder = componentsGetter.getOperationsServiceBinder();
-            FileUploaderBinder uploaderBinder = componentsGetter.getFileUploaderBinder();
             synchronizing = anyFileSynchronizing(opsBinder) ||      // comparing local and remote
-                            anyFileDownloading() ||
-                            anyFileUploading(uploaderBinder);
+                anyFileDownloading() ||
+                anyFileUploading();
         }
         return synchronizing;
     }
@@ -411,21 +410,20 @@ public class FileMenuFilter {
         return false;
     }
 
-    private boolean anyFileUploading(FileUploaderBinder uploaderBinder) {
-        boolean uploading = false;
-        if (uploaderBinder != null) {
-            for (Iterator<OCFile> iterator = files.iterator(); !uploading && iterator.hasNext(); ) {
-                uploading = uploaderBinder.isUploading(user, iterator.next());
+    private boolean anyFileUploading() {
+        for (OCFile file : files) {
+            if (FileUploadHelper.Companion.instance().isUploading(user, file)) {
+                return true;
             }
         }
-        return uploading;
+        return false;
     }
 
     private boolean isShareApiEnabled(OCCapability capability) {
         return capability != null &&
-                (capability.getFilesSharingApiEnabled().isTrue() ||
-                        capability.getFilesSharingApiEnabled().isUnknown()
-                );
+            (capability.getFilesSharingApiEnabled().isTrue() ||
+                capability.getFilesSharingApiEnabled().isUnknown()
+            );
     }
 
     private boolean isShareWithUsersAllowed() {

+ 0 - 1409
app/src/main/java/com/owncloud/android/files/services/FileUploader.java

@@ -1,1409 +0,0 @@
-/**
- *  ownCloud Android client application
- *
- *  @author Bartek Przybylski
- *  @author masensio
- *  @author LukeOwnCloud
- *  @author David A. Velasco
- *  @author Chris Narkiewicz
- *
- *  Copyright (C) 2012 Bartek Przybylski
- *  Copyright (C) 2012-2016 ownCloud Inc.
- *  Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License version 2,
- *  as published by the Free Software Foundation.
- *
- *  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 General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.files.services;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.accounts.OnAccountsUpdateListener;
-import android.annotation.SuppressLint;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.BitmapFactory;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Parcelable;
-import android.os.Process;
-import android.util.Pair;
-
-import com.nextcloud.client.account.User;
-import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.device.BatteryStatus;
-import com.nextcloud.client.device.PowerManagementService;
-import com.nextcloud.client.jobs.FilesUploadWorker;
-import com.nextcloud.client.network.Connectivity;
-import com.nextcloud.client.network.ConnectivityService;
-import com.nextcloud.client.utils.FileUploaderDelegate;
-import com.nextcloud.java.util.Optional;
-import com.nextcloud.utils.ForegroundServiceHelper;
-import com.nextcloud.utils.extensions.IntentExtensionsKt;
-import com.owncloud.android.MainApp;
-import com.owncloud.android.R;
-import com.owncloud.android.authentication.AuthenticatorActivity;
-import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.datamodel.ForegroundServiceType;
-import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.datamodel.ThumbnailsCacheManager;
-import com.owncloud.android.datamodel.UploadsStorageManager;
-import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
-import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.db.UploadResult;
-import com.owncloud.android.lib.common.OwnCloudAccount;
-import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
-import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
-import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.files.FileUtils;
-import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
-import com.owncloud.android.operations.UploadFileOperation;
-import com.owncloud.android.ui.activity.ConflictsResolveActivity;
-import com.owncloud.android.ui.activity.UploadListActivity;
-import com.owncloud.android.ui.notifications.NotificationUtils;
-import com.owncloud.android.utils.ErrorMessageAdapter;
-import com.owncloud.android.utils.FilesUploadHelper;
-import com.owncloud.android.utils.theme.ViewThemeUtils;
-
-import java.io.File;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-import androidx.core.app.NotificationCompat;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
-import dagger.android.AndroidInjection;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Service for uploading files. Invoke using context.startService(...).
- *
- * Files to be uploaded are stored persistently using {@link UploadsStorageManager}.
- *
- * On next invocation of {@link FileUploader} uploaded files which previously failed will be uploaded again until either
- * upload succeeded or a fatal error occurred.
- *
- * Every file passed to this service is uploaded. No filtering is performed. However, Intent keys (e.g., KEY_WIFI_ONLY)
- * are obeyed.
- */
-public class FileUploader extends Service
-    implements OnDatatransferProgressListener, OnAccountsUpdateListener, UploadFileOperation.OnRenameListener {
-
-    private static final String TAG = FileUploader.class.getSimpleName();
-
-    private static final String UPLOADS_ADDED_MESSAGE = "UPLOADS_ADDED";
-    private static final String UPLOAD_START_MESSAGE = "UPLOAD_START";
-    private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
-    public static final String EXTRA_UPLOAD_RESULT = "RESULT";
-    public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
-    public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH";
-    public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH";
-    public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
-    public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
-
-    public static final String EXTRA_ACCOUNT_NAME = "ACCOUNT_NAME";
-    public static final String ACTION_CANCEL_BROADCAST = "CANCEL";
-    public static final String ACTION_PAUSE_BROADCAST = "PAUSE";
-
-    private static final int FOREGROUND_SERVICE_ID = 411;
-    private static final int NOTIFICATION_ERROR_ID = FilesUploadWorker.NOTIFICATION_ERROR_ID;
-
-    public static final String KEY_FILE = "FILE";
-    public static final String KEY_LOCAL_FILE = "LOCAL_FILE";
-    public static final String KEY_REMOTE_FILE = "REMOTE_FILE";
-    public static final String KEY_MIME_TYPE = "MIME_TYPE";
-
-    /**
-     * Call this Service with only this Intent key if all pending uploads are to be retried.
-     */
-    private static final String KEY_RETRY = "KEY_RETRY";
-//    /**
-//     * Call this Service with KEY_RETRY and KEY_RETRY_REMOTE_PATH to retry
-//     * upload of file identified by KEY_RETRY_REMOTE_PATH.
-//     */
-//    private static final String KEY_RETRY_REMOTE_PATH = "KEY_RETRY_REMOTE_PATH";
-    /**
-     * Call this Service with KEY_RETRY and KEY_RETRY_UPLOAD to retry upload of file identified by KEY_RETRY_UPLOAD.
-     */
-    private static final String KEY_RETRY_UPLOAD = "KEY_RETRY_UPLOAD";
-
-    /**
-     * {@link Account} to which file is to be uploaded.
-     */
-    public static final String KEY_ACCOUNT = "ACCOUNT";
-
-    /**
-     * {@link User} for which file is to be uploaded.
-     */
-    public static final String KEY_USER = "USER";
-
-
-    /**
-     * What {@link NameCollisionPolicy} to do when the file already exists on the remote.
-     */
-    public static final String KEY_NAME_COLLISION_POLICY = "KEY_NAME_COLLISION_POLICY";
-
-    /**
-     * Set to true if remote folder is to be created if it does not exist.
-     */
-    public static final String KEY_CREATE_REMOTE_FOLDER = "CREATE_REMOTE_FOLDER";
-    /**
-     * Key to signal what is the origin of the upload request
-     */
-    public static final String KEY_CREATED_BY = "CREATED_BY";
-
-    public static final String KEY_WHILE_ON_WIFI_ONLY = "KEY_ON_WIFI_ONLY";
-
-    /**
-     * Set to true if upload is to performed only when phone is being charged.
-     */
-    public static final String KEY_WHILE_CHARGING_ONLY = "KEY_WHILE_CHARGING_ONLY";
-
-    public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR";
-
-    /**
-     * Set to true if the HTTP library should disable automatic retries of uploads.
-     */
-    public static final String KEY_DISABLE_RETRIES = "DISABLE_RETRIES";
-
-    public static final int LOCAL_BEHAVIOUR_COPY = 0;
-    public static final int LOCAL_BEHAVIOUR_MOVE = 1;
-    public static final int LOCAL_BEHAVIOUR_FORGET = 2;
-    public static final int LOCAL_BEHAVIOUR_DELETE = 3;
-
-    private static boolean forceNewUploadWorker = false;
-
-    private Notification mNotification;
-    private Looper mServiceLooper;
-    private ServiceHandler mServiceHandler;
-    private static IBinder mBinder;
-    private OwnCloudClient mUploadClient;
-    private Account mCurrentAccount;
-    private FileDataStorageManager mStorageManager;
-
-    private SecureRandom secureRandomGenerator = new SecureRandom();
-
-    @Inject UserAccountManager accountManager;
-    @Inject UploadsStorageManager mUploadsStorageManager;
-    @Inject ConnectivityService connectivityService;
-    @Inject PowerManagementService powerManagementService;
-    @Inject LocalBroadcastManager localBroadcastManager;
-    @Inject ViewThemeUtils viewThemeUtils;
-
-    private IndexedForest<UploadFileOperation> mPendingUploads = new IndexedForest<>();
-
-    /**
-     * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent
-     * upload!
-     */
-    private UploadFileOperation mCurrentUpload;
-
-    private NotificationManager mNotificationManager;
-    private NotificationCompat.Builder mNotificationBuilder;
-    private int mLastPercent;
-    private FileUploaderDelegate fileUploaderDelegate;
-
-
-    @Override
-    public void onRenameUpload() {
-        mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload);
-        fileUploaderDelegate.sendBroadcastUploadStarted(mCurrentUpload, this, localBroadcastManager);
-    }
-
-    /**
-     * Service initialization
-     */
-    @SuppressFBWarnings("ST")
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        AndroidInjection.inject(this);
-        Log_OC.d(TAG, "Creating service");
-        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-        HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND);
-        thread.start();
-        mServiceLooper = thread.getLooper();
-        mServiceHandler = new ServiceHandler(mServiceLooper, this);
-        mBinder = new FileUploaderBinder();
-        fileUploaderDelegate = new FileUploaderDelegate();
-
-        NotificationCompat.Builder builder = NotificationUtils.newNotificationBuilder(this, viewThemeUtils).setContentTitle(
-                getApplicationContext().getResources().getString(R.string.app_name))
-            .setContentText(getApplicationContext().getResources().getString(R.string.foreground_service_upload))
-            .setSmallIcon(R.drawable.notification_icon)
-            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.notification_icon));
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            builder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD);
-        }
-
-        mNotification = builder.build();
-
-        // TODO Add UploadResult.KILLED?
-        int failedCounter = mUploadsStorageManager.failInProgressUploads(UploadResult.SERVICE_INTERRUPTED);
-        if (failedCounter > 0) {
-            resurrection();
-        }
-
-        // add AccountsUpdatedListener
-        AccountManager am = AccountManager.get(getApplicationContext());
-        am.addOnAccountsUpdatedListener(this, null, false);
-    }
-
-    /**
-     * Service clean-up when restarted after being killed
-     */
-    private void resurrection() {
-        // remove stucked notification
-        mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
-    }
-
-    /**
-     * Service clean up
-     */
-    @SuppressFBWarnings("ST")
-    @Override
-    public void onDestroy() {
-        Log_OC.v(TAG, "Destroying service");
-        mBinder = null;
-        mServiceHandler = null;
-        mServiceLooper.quit();
-        mServiceLooper = null;
-        if (mNotificationManager != null) {
-            mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
-        }
-        // remove AccountsUpdatedListener
-        AccountManager am = AccountManager.get(getApplicationContext());
-        am.removeOnAccountsUpdatedListener(this);
-        super.onDestroy();
-    }
-
-    /**
-     * Entry point to add one or several files to the queue of uploads.
-     *
-     * New uploads are added calling to startService(), resulting in a call to this method. This ensures the service
-     * will keep on working although the caller activity goes away.
-     */
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        Log_OC.d(TAG, "Starting command with id " + startId);
-
-        ForegroundServiceHelper.INSTANCE.startService(this, FOREGROUND_SERVICE_ID, mNotification, ForegroundServiceType.DataSync);
-
-        if (intent == null) {
-            Log_OC.e(TAG, "Intent is null");
-            return Service.START_NOT_STICKY;
-        }
-
-        if (!intent.hasExtra(KEY_ACCOUNT)) {
-            Log_OC.e(TAG, "Not enough information provided in intent");
-            return Service.START_NOT_STICKY;
-        }
-
-        final Account account = IntentExtensionsKt.getParcelableArgument(intent, KEY_ACCOUNT, Account.class);
-        if (account == null) {
-            return Service.START_NOT_STICKY;
-        }
-        Optional<User> optionalUser = accountManager.getUser(account.name);
-        if (!optionalUser.isPresent()) {
-            return Service.START_NOT_STICKY;
-        }
-        final User user = optionalUser.get();
-
-        boolean retry = intent.getBooleanExtra(KEY_RETRY, false);
-        List<String> requestedUploads = new ArrayList<>();
-
-        boolean onWifiOnly = intent.getBooleanExtra(KEY_WHILE_ON_WIFI_ONLY, false);
-        boolean whileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, false);
-
-        if (retry) {
-            // Retry uploads
-            if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_RETRY_UPLOAD)) {
-                Log_OC.e(TAG, "Not enough information provided in intent: no KEY_RETRY_UPLOAD_KEY");
-                return START_NOT_STICKY;
-            }
-            retryUploads(intent, user, requestedUploads);
-        } else {
-            // Start new uploads
-            if (!(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
-                Log_OC.e(TAG, "Not enough information provided in intent");
-                return Service.START_NOT_STICKY;
-            }
-
-            Integer error = gatherAndStartNewUploads(intent, user, requestedUploads, onWifiOnly, whileChargingOnly);
-            if (error != null) {
-                return error;
-            }
-        }
-
-        if (requestedUploads.size() > 0) {
-            Message msg = mServiceHandler.obtainMessage();
-            msg.arg1 = startId;
-            msg.obj = requestedUploads;
-            mServiceHandler.sendMessage(msg);
-            fileUploaderDelegate.sendBroadcastUploadsAdded(this, localBroadcastManager);
-        }
-        return Service.START_NOT_STICKY;
-    }
-
-    /**
-     * Gather and start new uploads.
-     *
-     * @return A {@link Service} constant in case of error, {@code null} otherwise.
-     */
-    @Nullable
-    private Integer gatherAndStartNewUploads(
-        Intent intent,
-        User user,
-        List<String> requestedUploads,
-        boolean onWifiOnly,
-        boolean whileChargingOnly
-                                            ) {
-        String[] localPaths = null;
-        String[] remotePaths = null;
-        String[] mimeTypes = null;
-        OCFile[] files = null;
-
-        if (intent.hasExtra(KEY_FILE)) {
-            Parcelable[] files_temp = intent.getParcelableArrayExtra(KEY_FILE);
-            files = new OCFile[files_temp.length];
-            System.arraycopy(files_temp, 0, files, 0, files_temp.length);
-        } else {
-            localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
-            remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE);
-            mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE);
-        }
-
-        if (intent.hasExtra(KEY_FILE) && files == null) {
-            Log_OC.e(TAG, "Incorrect array for OCFiles provided in upload intent");
-            return Service.START_NOT_STICKY;
-        } else if (!intent.hasExtra(KEY_FILE)) {
-            if (localPaths == null) {
-                Log_OC.e(TAG, "Incorrect array for local paths provided in upload intent");
-                return Service.START_NOT_STICKY;
-            }
-            if (remotePaths == null) {
-                Log_OC.e(TAG, "Incorrect array for remote paths provided in upload intent");
-                return Service.START_NOT_STICKY;
-            }
-            if (localPaths.length != remotePaths.length) {
-                Log_OC.e(TAG, "Different number of remote paths and local paths!");
-                return Service.START_NOT_STICKY;
-            }
-
-            files = new OCFile[localPaths.length];
-            for (int i = 0; i < localPaths.length; i++) {
-                files[i] = UploadFileOperation.obtainNewOCFileToUpload(
-                    remotePaths[i],
-                    localPaths[i],
-                    mimeTypes != null ? mimeTypes[i] : null
-                                                                      );
-                if (files[i] == null) {
-                    Log_OC.e(TAG, "obtainNewOCFileToUpload() returned null for remotePaths[i]:" + remotePaths[i]
-                        + " and localPaths[i]:" + localPaths[i]);
-                    return Service.START_NOT_STICKY;
-                }
-            }
-        }
-        // at this point variable "OCFile[] files" is loaded correctly.
-
-        NameCollisionPolicy nameCollisionPolicy = IntentExtensionsKt.getSerializableArgument(intent, KEY_NAME_COLLISION_POLICY, NameCollisionPolicy.class);
-        if (nameCollisionPolicy == null) {
-            nameCollisionPolicy = NameCollisionPolicy.DEFAULT;
-        }
-        int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_FORGET);
-        boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false);
-        int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER);
-        boolean disableRetries = intent.getBooleanExtra(KEY_DISABLE_RETRIES, true);
-        try {
-            for (OCFile file : files) {
-                startNewUpload(
-                    user,
-                    requestedUploads,
-                    onWifiOnly,
-                    whileChargingOnly,
-                    nameCollisionPolicy,
-                    localAction,
-                    isCreateRemoteFolder,
-                    createdBy,
-                    file,
-                    disableRetries
-                              );
-            }
-        } catch (IllegalArgumentException e) {
-            Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
-            return START_NOT_STICKY;
-        } catch (IllegalStateException e) {
-            Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
-            return START_NOT_STICKY;
-        } catch (Exception e) {
-            Log_OC.e(TAG, "Unexpected exception while processing upload intent", e);
-            return START_NOT_STICKY;
-        }
-        return null;
-    }
-
-    /**
-     * Start a new {@link UploadFileOperation}.
-     */
-    @SuppressLint("SdCardPath")
-    private void startNewUpload(
-        User user,
-        List<String> requestedUploads,
-        boolean onWifiOnly,
-        boolean whileChargingOnly,
-        NameCollisionPolicy nameCollisionPolicy,
-        int localAction,
-        boolean isCreateRemoteFolder,
-        int createdBy,
-        OCFile file,
-        boolean disableRetries
-                               ) {
-        OCUpload ocUpload = new OCUpload(file, user);
-        ocUpload.setFileSize(file.getFileLength());
-        ocUpload.setNameCollisionPolicy(nameCollisionPolicy);
-        ocUpload.setCreateRemoteFolder(isCreateRemoteFolder);
-        ocUpload.setCreatedBy(createdBy);
-        ocUpload.setLocalAction(localAction);
-        ocUpload.setUseWifiOnly(onWifiOnly);
-        ocUpload.setWhileChargingOnly(whileChargingOnly);
-        ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
-
-        UploadFileOperation newUpload = new UploadFileOperation(
-            mUploadsStorageManager,
-            connectivityService,
-            powerManagementService,
-            user,
-            file,
-            ocUpload,
-            nameCollisionPolicy,
-            localAction,
-            this,
-            onWifiOnly,
-            whileChargingOnly,
-            disableRetries,
-            new FileDataStorageManager(user, getContentResolver())
-        );
-        newUpload.setCreatedBy(createdBy);
-        if (isCreateRemoteFolder) {
-            newUpload.setRemoteFolderToBeCreated();
-        }
-        newUpload.addDataTransferProgressListener(this);
-        newUpload.addDataTransferProgressListener((FileUploaderBinder) mBinder);
-
-        newUpload.addRenameUploadListener(this);
-
-        Pair<String, String> putResult = mPendingUploads.putIfAbsent(
-            user.getAccountName(),
-            file.getRemotePath(),
-            newUpload
-                                                                    );
-
-        if (putResult != null) {
-            requestedUploads.add(putResult.first);
-
-            // Save upload in database
-            long id = mUploadsStorageManager.storeUpload(ocUpload);
-            newUpload.setOCUploadId(id);
-        }
-    }
-
-    /**
-     * Retries a list of uploads.
-     */
-    private void retryUploads(Intent intent, User user, List<String> requestedUploads) {
-        boolean onWifiOnly;
-        boolean whileChargingOnly;
-
-        OCUpload upload = IntentExtensionsKt.getParcelableArgument(intent, KEY_RETRY_UPLOAD, OCUpload.class);
-
-        onWifiOnly = upload.isUseWifiOnly();
-        whileChargingOnly = upload.isWhileChargingOnly();
-
-        UploadFileOperation newUpload = new UploadFileOperation(
-            mUploadsStorageManager,
-            connectivityService,
-            powerManagementService,
-            user,
-            null,
-            upload,
-            upload.getNameCollisionPolicy(),
-            upload.getLocalAction(),
-            this,
-            onWifiOnly,
-            whileChargingOnly,
-            true,
-            new FileDataStorageManager(user, getContentResolver())
-        );
-
-        newUpload.addDataTransferProgressListener(this);
-        newUpload.addDataTransferProgressListener((FileUploaderBinder) mBinder);
-
-        newUpload.addRenameUploadListener(this);
-
-        Pair<String, String> putResult = mPendingUploads.putIfAbsent(
-            user.getAccountName(),
-            upload.getRemotePath(),
-            newUpload
-                                                                    );
-        if (putResult != null) {
-            String uploadKey = putResult.first;
-            requestedUploads.add(uploadKey);
-
-            // Update upload in database
-            upload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS);
-            mUploadsStorageManager.updateUpload(upload);
-        }
-    }
-
-    /**
-     * Provides a binder object that clients can use to perform operations on the queue of uploads, excepting the
-     * addition of new files.
-     *
-     * Implemented to perform cancellation, pause and resume of existing uploads.
-     */
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mBinder;
-    }
-
-    /**
-     * Called when ALL the bound clients were onbound.
-     */
-    @Override
-    public boolean onUnbind(Intent intent) {
-        ((FileUploaderBinder) mBinder).clearListeners();
-        return false;   // not accepting rebinding (default behaviour)
-    }
-
-    @Override
-    public void onAccountsUpdated(Account[] accounts) {
-        // Review current upload, and cancel it if its account doesn't exist
-        if (mCurrentUpload != null && !accountManager.exists(mCurrentUpload.getUser().toPlatformAccount())) {
-            mCurrentUpload.cancel(ResultCode.ACCOUNT_NOT_FOUND);
-        }
-        // The rest of uploads are cancelled when they try to start
-    }
-
-    /**
-     * Core upload method: sends the file(s) to upload WARNING: legacy code, must be in sync with @{{@link
-     * FilesUploadWorker upload(UploadFileOperation, User)}
-     *
-     * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
-     */
-    public void uploadFile(String uploadKey) {
-        mCurrentUpload = mPendingUploads.get(uploadKey);
-
-        if (mCurrentUpload != null) {
-            /// Check account existence
-            if (!accountManager.exists(mCurrentUpload.getUser().toPlatformAccount())) {
-                Log_OC.w(TAG, "Account " + mCurrentUpload.getUser().getAccountName() +
-                    " does not exist anymore -> cancelling all its uploads");
-                cancelPendingUploads(mCurrentUpload.getUser().getAccountName());
-                return;
-            }
-
-            /// OK, let's upload
-            mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload);
-
-            notifyUploadStart(mCurrentUpload);
-
-            fileUploaderDelegate.sendBroadcastUploadStarted(mCurrentUpload, this, localBroadcastManager);
-
-            RemoteOperationResult uploadResult = null;
-
-            try {
-                /// prepare client object to send the request to the ownCloud server
-                if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentUpload.getUser().toPlatformAccount())) {
-                    mCurrentAccount = mCurrentUpload.getUser().toPlatformAccount();
-                    mStorageManager = new FileDataStorageManager(getCurrentUser().get(), getContentResolver());
-                }   // else, reuse storage manager from previous operation
-                // always get client from client manager, to get fresh credentials in case of update
-                OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this);
-                mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this);
-                uploadResult = mCurrentUpload.execute(mUploadClient);
-            } catch (Exception e) {
-                Log_OC.e(TAG, "Error uploading", e);
-                uploadResult = new RemoteOperationResult(e);
-            } finally {
-                Pair<UploadFileOperation, String> removeResult;
-                if (mCurrentUpload.wasRenamed()) {
-                    OCFile oldFile = mCurrentUpload.getOldFile();
-                    String oldRemotePath = "";
-                    if (oldFile != null) {
-                        oldRemotePath = oldFile.getRemotePath();
-                    }
-                    removeResult = mPendingUploads.removePayload(mCurrentAccount.name, oldRemotePath);
-                    // TODO: grant that name is also updated for mCurrentUpload.getOCUploadId
-                } else {
-                    removeResult = mPendingUploads.removePayload(mCurrentAccount.name,
-                                                                 mCurrentUpload.getDecryptedRemotePath());
-                }
-
-                mUploadsStorageManager.updateDatabaseUploadResult(uploadResult, mCurrentUpload);
-
-                /// notify result
-                notifyUploadResult(mCurrentUpload, uploadResult);
-
-                fileUploaderDelegate.sendBroadcastUploadFinished(mCurrentUpload,
-                                                                 uploadResult,
-                                                                 removeResult.second,
-                                                                 this,
-                                                                 localBroadcastManager);
-            }
-
-            // generate new Thumbnail
-            Optional<User> user = getCurrentUser();
-            if (user.isPresent()) {
-                final ThumbnailsCacheManager.ThumbnailGenerationTask task =
-                    new ThumbnailsCacheManager.ThumbnailGenerationTask(mStorageManager, user.get());
-
-                File file = new File(mCurrentUpload.getOriginalStoragePath());
-                String remoteId = mCurrentUpload.getFile().getRemoteId();
-
-                task.execute(new ThumbnailsCacheManager.ThumbnailGenerationTaskObject(file, remoteId));
-            }
-        }
-    }
-
-    /**
-     * Convert current account to user. This is a temporary workaround until
-     * service is migrated to new user model.
-     *
-     * @return Optional {@link User}
-     */
-    private Optional<User> getCurrentUser() {
-        if (mCurrentAccount == null) {
-            return Optional.empty();
-        } else {
-            return accountManager.getUser(mCurrentAccount.name);
-        }
-    }
-
-    /**
-     * Creates a status notification to show the upload progress
-     *
-     * @param upload Upload operation starting.
-     */
-    private void notifyUploadStart(UploadFileOperation upload) {
-        // / create status notification with a progress bar
-        Intent notificationActionIntent = new Intent(getApplicationContext(), UploadNotificationActionReceiver.class);
-        notificationActionIntent.putExtra(EXTRA_ACCOUNT_NAME, upload.getUser().getAccountName());
-        notificationActionIntent.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath());
-        notificationActionIntent.setAction(ACTION_CANCEL_BROADCAST);
-
-        PendingIntent pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), secureRandomGenerator.nextInt(), notificationActionIntent, PendingIntent.FLAG_IMMUTABLE);
-        mLastPercent = 0;
-        mNotificationBuilder = NotificationUtils.newNotificationBuilder(this, viewThemeUtils);
-        mNotificationBuilder
-            .setOngoing(true)
-            .setSmallIcon(R.drawable.notification_icon)
-            .setTicker(getString(R.string.uploader_upload_in_progress_ticker))
-            .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker))
-            .setProgress(100, 0, false)
-            .setContentText(
-                String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())
-                           )
-            .clearActions() // to make sure there is only one action
-            .addAction(R.drawable.ic_action_cancel_grey, getApplicationContext().getString(R.string.common_cancel), pendingIntent);
-
-
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
-            mNotificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD);
-        }
-
-        /// includes a pending intent in the notification showing the details
-        Intent intent = UploadListActivity.createIntent(upload.getFile(),
-                                                        upload.getUser(),
-                                                        Intent.FLAG_ACTIVITY_CLEAR_TOP,
-                                                        this);
-        mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this,
-                                                                        (int) System.currentTimeMillis(),
-                                                                        intent,
-                                                                        PendingIntent.FLAG_IMMUTABLE)
-                                             );
-
-        if (!upload.isInstantPicture() && !upload.isInstantVideo()) {
-            if (mNotificationManager == null) {
-                mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-            }
-
-            mNotificationManager.notify(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
-        }   // else wait until the upload really start (onTransferProgress is called), so that if it's discarded
-        // due to lack of Wifi, no notification is shown
-        // TODO generalize for automated uploads
-    }
-
-    /**
-     * Callback method to update the progress bar in the status notification
-     */
-    @Override
-    public void onTransferProgress(
-        long progressRate,
-        long totalTransferredSoFar,
-        long totalToTransfer,
-        String filePath
-                                  ) {
-        int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
-        if (percent != mLastPercent) {
-            mNotificationBuilder.setProgress(100, percent, false);
-            String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
-            String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
-            mNotificationBuilder.setContentText(text);
-            if (mNotificationManager == null) {
-                mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-            }
-            mNotificationManager.notify(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
-            cancelOldErrorNotification(mCurrentUpload);
-        }
-        mLastPercent = percent;
-    }
-
-    /**
-     * Updates the status notification with the result of an upload operation.
-     *
-     * @param uploadResult Result of the upload operation.
-     * @param upload       Finished upload operation
-     */
-    @SuppressFBWarnings("DMI")
-    private void notifyUploadResult(UploadFileOperation upload, RemoteOperationResult uploadResult) {
-        Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode());
-        // cancelled operation or success -> silent removal of progress notification
-        if (mNotificationManager == null) {
-            mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-        }
-
-        if (uploadResult.isSuccess()){
-            cancelOldErrorNotification(upload);
-        }
-
-        // Only notify if the upload fails
-        if (!uploadResult.isCancelled() &&
-            !uploadResult.isSuccess() &&
-            ResultCode.LOCAL_FILE_NOT_FOUND != uploadResult.getCode() &&
-            uploadResult.getCode() != ResultCode.DELAYED_FOR_WIFI &&
-            uploadResult.getCode() != ResultCode.DELAYED_FOR_CHARGING &&
-            uploadResult.getCode() != ResultCode.DELAYED_IN_POWER_SAVE_MODE &&
-            uploadResult.getCode() != ResultCode.LOCK_FAILED) {
-
-            int tickerId = R.string.uploader_upload_failed_ticker;
-
-            String content;
-
-            // check credentials error
-            boolean needsToUpdateCredentials = uploadResult.getCode() == ResultCode.UNAUTHORIZED;
-            if (needsToUpdateCredentials) {
-                tickerId = R.string.uploader_upload_failed_credentials_error;
-            } else if (uploadResult.getCode() == ResultCode.SYNC_CONFLICT) {
-                // check file conflict
-                tickerId = R.string.uploader_upload_failed_sync_conflict_error;
-            }
-
-            mNotificationBuilder
-                .setTicker(getString(tickerId))
-                .setContentTitle(getString(tickerId))
-                .setAutoCancel(true)
-                .setOngoing(false)
-                .setProgress(0, 0, false)
-                .clearActions();
-
-            content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, upload, getResources());
-
-            if (needsToUpdateCredentials) {
-                // let the user update credentials with one click
-                Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
-                updateAccountCredentials.putExtra(
-                    AuthenticatorActivity.EXTRA_ACCOUNT, upload.getUser().toPlatformAccount()
-                                                 );
-                updateAccountCredentials.putExtra(
-                    AuthenticatorActivity.EXTRA_ACTION,
-                    AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
-                                                 );
-
-                updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
-                mNotificationBuilder.setContentIntent(PendingIntent.getActivity(
-                    this,
-                    (int) System.currentTimeMillis(),
-                    updateAccountCredentials,
-                    PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE
-                                                                               ));
-            } else {
-                Intent intent;
-                if (uploadResult.getCode() == ResultCode.SYNC_CONFLICT) {
-                    intent = ConflictsResolveActivity.createIntent(upload.getFile(),
-                                                                   upload.getUser(),
-                                                                   upload.getOCUploadId(),
-                                                                   Intent.FLAG_ACTIVITY_CLEAR_TOP,
-                                                                   this);
-                } else {
-                    intent = UploadListActivity.createIntent(upload.getFile(),
-                                                             upload.getUser(),
-                                                             Intent.FLAG_ACTIVITY_CLEAR_TOP,
-                                                             this);
-                }
-
-                mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this,
-                                                                                (int) System.currentTimeMillis(),
-                                                                                intent,
-                                                                                PendingIntent.FLAG_IMMUTABLE)
-                                                     );
-            }
-
-            mNotificationBuilder.setContentText(content);
-            if (!uploadResult.isSuccess()) {
-                mNotificationManager.notify(secureRandomGenerator.nextInt(), mNotificationBuilder.build());
-            }
-        }
-    }
-
-    /**
-     * Remove and 'forgets' pending uploads of a user.
-     *
-     * @param accountName User which uploads will be cancelled
-     */
-    private void cancelPendingUploads(String accountName) {
-        mPendingUploads.remove(accountName);
-        mUploadsStorageManager.removeUploads(accountName);
-    }
-
-    /**
-     * Upload a new file
-     */
-    public static void uploadNewFile(
-        User user,
-        String localPath,
-        String remotePath,
-        int behaviour,
-        boolean createRemoteFile,
-        int createdBy,
-        boolean requiresWifi,
-        boolean requiresCharging,
-        NameCollisionPolicy nameCollisionPolicy
-                                    ) {
-        uploadNewFile(
-            user,
-            new String[]{localPath},
-            new String[]{remotePath},
-            behaviour,
-            createRemoteFile,
-            createdBy,
-            requiresWifi,
-            requiresCharging,
-            nameCollisionPolicy
-                     );
-    }
-
-    /**
-     * Upload multiple new files
-     */
-    public static void uploadNewFile(
-        User user,
-        String[] localPaths,
-        String[] remotePaths,
-        Integer behaviour,
-        Boolean createRemoteFolder,
-        int createdBy,
-        boolean requiresWifi,
-        boolean requiresCharging,
-        NameCollisionPolicy nameCollisionPolicy
-                                    ) {
-        new FilesUploadHelper().uploadNewFiles(user,
-                                               localPaths,
-                                               remotePaths,
-                                               createRemoteFolder,
-                                               createdBy,
-                                               requiresWifi,
-                                               requiresCharging,
-                                               nameCollisionPolicy,
-                                               behaviour);
-    }
-
-    /**
-     * Upload and overwrite an already uploaded file with disabled retries
-     */
-    public static void uploadUpdateFile(
-        Context context,
-        User user,
-        OCFile existingFile,
-        Integer behaviour,
-        NameCollisionPolicy nameCollisionPolicy) {
-        uploadUpdateFile(context,
-                         user,
-                         new OCFile[]{existingFile},
-                         behaviour,
-                         nameCollisionPolicy,
-                         true);
-    }
-
-    /**
-     * Upload and overwrite an already uploaded file
-     */
-    public static void uploadUpdateFile(
-        Context context,
-        User user,
-        OCFile existingFile,
-        Integer behaviour,
-        NameCollisionPolicy nameCollisionPolicy,
-        boolean disableRetries) {
-        uploadUpdateFile(context,
-                         user,
-                         new OCFile[]{existingFile},
-                         behaviour,
-                         nameCollisionPolicy,
-                         disableRetries);
-    }
-
-    /**
-     * Upload and overwrite already uploaded files
-     */
-    public static void uploadUpdateFile(
-        Context context,
-        User user,
-        OCFile[] existingFiles,
-        Integer behaviour,
-        NameCollisionPolicy nameCollisionPolicy,
-        boolean disableRetries
-                                       ) {
-        Intent intent = new Intent(context, FileUploader.class);
-
-        intent.putExtra(FileUploader.KEY_USER, user);
-        intent.putExtra(FileUploader.KEY_ACCOUNT, user.toPlatformAccount());
-        intent.putExtra(FileUploader.KEY_FILE, existingFiles);
-        intent.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, behaviour);
-        intent.putExtra(FileUploader.KEY_NAME_COLLISION_POLICY, nameCollisionPolicy);
-        intent.putExtra(FileUploader.KEY_DISABLE_RETRIES, disableRetries);
-
-        new FilesUploadHelper().uploadUpdatedFile(user, existingFiles, behaviour, nameCollisionPolicy);
-    }
-
-    /**
-     * Retry a failed {@link OCUpload} identified by {@link OCUpload#getRemotePath()}
-     */
-    public static void retryUpload(@NonNull Context context,
-                                   @NonNull User user,
-                                   @NonNull OCUpload upload) {
-        Intent i = new Intent(context, FileUploader.class);
-        i.putExtra(FileUploader.KEY_RETRY, true);
-        i.putExtra(FileUploader.KEY_USER, user);
-        i.putExtra(FileUploader.KEY_ACCOUNT, user.toPlatformAccount());
-        i.putExtra(FileUploader.KEY_RETRY_UPLOAD, upload);
-
-        new FilesUploadHelper().retryUpload(upload, user);
-    }
-
-    /**
-     * Retry a subset of all the stored failed uploads.
-     */
-    public static void retryFailedUploads(
-        @NonNull final Context context,
-        @NonNull final UploadsStorageManager uploadsStorageManager,
-        @NonNull final ConnectivityService connectivityService,
-        @NonNull final UserAccountManager accountManager,
-        @NonNull final PowerManagementService powerManagementService
-                                         ) {
-        OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
-        if (failedUploads == null || failedUploads.length == 0) {
-            return; //nothing to do
-        }
-
-        final Connectivity connectivity = connectivityService.getConnectivity();
-        final boolean gotNetwork = connectivity.isConnected();
-        final boolean gotWifi = connectivity.isWifi();
-        final BatteryStatus batteryStatus = powerManagementService.getBattery();
-        final boolean charging = batteryStatus.isCharging() || batteryStatus.isFull();
-        final boolean isPowerSaving = powerManagementService.isPowerSavingEnabled();
-
-        Optional<User> uploadUser = Optional.empty();
-        for (OCUpload failedUpload : failedUploads) {
-            // 1. extract failed upload owner account and cache it between loops (expensive query)
-            if (!uploadUser.isPresent() || !uploadUser.get().nameEquals(failedUpload.getAccountName())) {
-                uploadUser = accountManager.getUser(failedUpload.getAccountName());
-            }
-            final boolean isDeleted = !new File(failedUpload.getLocalPath()).exists();
-            if (isDeleted) {
-                // 2A. for deleted files, mark as permanently failed
-                if (failedUpload.getLastResult() != UploadResult.FILE_NOT_FOUND) {
-                    failedUpload.setLastResult(UploadResult.FILE_NOT_FOUND);
-                    uploadsStorageManager.updateUpload(failedUpload);
-                }
-            } else if (!isPowerSaving && gotNetwork &&
-                canUploadBeRetried(failedUpload, gotWifi, charging) && !connectivityService.isInternetWalled()) {
-                // 2B. for existing local files, try restarting it if possible
-                retryUpload(context, uploadUser.get(), failedUpload);
-            }
-        }
-    }
-
-    private static boolean canUploadBeRetried(OCUpload upload, boolean gotWifi, boolean isCharging) {
-        File file = new File(upload.getLocalPath());
-        boolean needsWifi = upload.isUseWifiOnly();
-        boolean needsCharging = upload.isWhileChargingOnly();
-
-        return file.exists() && (!needsWifi || gotWifi) && (!needsCharging || isCharging);
-    }
-
-    public static String getUploadsAddedMessage() {
-        return FileUploader.class.getName() + UPLOADS_ADDED_MESSAGE;
-    }
-
-    public static String getUploadStartMessage() {
-        return FileUploader.class.getName() + UPLOAD_START_MESSAGE;
-    }
-
-    public static String getUploadFinishMessage() {
-        return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
-    }
-
-    @VisibleForTesting
-    public static void setForceNewUploadWorker(final Boolean value) {
-        forceNewUploadWorker = value;
-    }
-
-    /**
-     * Binder to let client components to perform operations on the queue of uploads.
-     * <p>
-     * It provides by itself the available operations.
-     */
-    public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
-        /**
-         * Map of listeners that will be reported about progress of uploads from a {@link FileUploaderBinder} instance
-         */
-        private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<>();
-
-        /**
-         * Cancels a pending or current upload of a remote file.
-         *
-         * @param account ownCloud account where the remote file will be stored.
-         * @param file    A file in the queue of pending uploads
-         */
-        public void cancel(Account account, ServerFileInterface file) {
-            cancel(account.name, file.getRemotePath(), null);
-        }
-
-        /**
-         * Cancels a pending or current upload that was persisted.
-         *
-         * @param storedUpload Upload operation persisted
-         */
-        public void cancel(OCUpload storedUpload) {
-            cancel(storedUpload.getAccountName(), storedUpload.getRemotePath(), null);
-        }
-
-        /**
-         * Cancels a pending or current upload of a remote file.
-         *
-         * @param accountName Local name of an ownCloud account where the remote file will be stored.
-         * @param remotePath  Remote target of the upload
-         * @param resultCode  Setting result code will pause rather than cancel the job
-         */
-        public void cancel(String accountName, String remotePath, @Nullable ResultCode resultCode) {
-            try {
-                new FilesUploadHelper().cancelFileUpload(remotePath, accountManager.getUser(accountName).get());
-            } catch (NoSuchElementException e) {
-                Log_OC.e(TAG, "Error cancelling current upload because user does not exist!");
-            }
-        }
-
-        /**
-         * Cancels all the uploads for a user, both running and pending.
-         *
-         * @param user Nextcloud user
-         */
-        public void cancel(User user) {
-            cancel(user.getAccountName());
-        }
-
-        public void cancel(String accountName) {
-            cancelPendingUploads(accountName);
-            new FilesUploadHelper().restartUploadJob(accountManager.getUser(accountName).get());
-        }
-
-        public void clearListeners() {
-            FilesUploadHelper.Progress.getMBoundListeners().clear();
-            mBoundListeners.clear();
-        }
-
-        /**
-         * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting
-         * for it
-         *
-         * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload.
-         *
-         * Warning: If remote file exists and target was renamed the original file is being returned here. That is, it
-         * seems as if the original file is being updated when actually a new file is being uploaded.
-         *
-         * @param user    user where the remote file will be stored.
-         * @param file    A file that could be in the queue of pending uploads
-         */
-        public boolean isUploading(User user, OCFile file) {
-            if (user == null || file == null) {
-                return false;
-            }
-
-            OCUpload upload = mUploadsStorageManager.getUploadByRemotePath(file.getRemotePath());
-
-            if (upload == null){
-                return false;
-            }
-
-            return upload.getUploadStatus() == UploadStatus.UPLOAD_IN_PROGRESS;
-        }
-
-        @SuppressFBWarnings("NP")
-        public boolean isUploadingNow(OCUpload upload) {
-            UploadFileOperation currentUploadFileOperation = FilesUploadWorker.Companion.getCurrentUploadFileOperation();
-            if (currentUploadFileOperation == null || currentUploadFileOperation.getUser() == null) return false;
-            if (upload == null || (!upload.getAccountName().equals(currentUploadFileOperation.getUser().getAccountName()))) return false;
-            if (currentUploadFileOperation.getOldFile() != null){
-                // For file conflicts check old file remote path
-                return upload.getRemotePath().equals(currentUploadFileOperation.getRemotePath()) ||
-                    upload.getRemotePath().equals(currentUploadFileOperation.getOldFile().getRemotePath());
-            }
-            return upload.getRemotePath().equals(currentUploadFileOperation.getRemotePath());
-        }
-
-        /**
-         * Adds a listener interested in the progress of the upload for a concrete file.
-         *
-         * @param listener Object to notify about progress of transfer.
-         * @param user  user owning the file of interest.
-         * @param file     {@link OCFile} of interest for listener.
-         */
-        public void addDatatransferProgressListener(
-            OnDatatransferProgressListener listener,
-            User user,
-            ServerFileInterface file
-                                                   ) {
-            if (user == null || file == null || listener == null) {
-                return;
-            }
-
-            String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
-            new FilesUploadHelper().addDatatransferProgressListener(listener,targetKey);
-        }
-
-        /**
-         * Adds a listener interested in the progress of the upload for a concrete file.
-         *
-         * @param listener Object to notify about progress of transfer.
-         * @param ocUpload {@link OCUpload} of interest for listener.
-         */
-        public void addDatatransferProgressListener(
-            OnDatatransferProgressListener listener,
-            OCUpload ocUpload
-                                                   ) {
-            if (ocUpload == null || listener == null) {
-                return;
-            }
-
-            String targetKey = buildRemoteName(ocUpload.getAccountName(), ocUpload.getRemotePath());
-            new FilesUploadHelper().addDatatransferProgressListener(listener,targetKey);
-        }
-
-        /**
-         * Removes a listener interested in the progress of the upload for a concrete file.
-         *
-         * @param listener Object to notify about progress of transfer.
-         * @param user user owning the file of interest.
-         * @param file {@link OCFile} of interest for listener.
-         */
-        public void removeDatatransferProgressListener(
-            OnDatatransferProgressListener listener,
-            User user,
-            ServerFileInterface file
-                                                      ) {
-            if (user == null || file == null || listener == null) {
-                return;
-            }
-
-            String targetKey = buildRemoteName(user.getAccountName(), file.getRemotePath());
-            new FilesUploadHelper().removeDatatransferProgressListener(listener,targetKey);
-        }
-
-        /**
-         * Removes a listener interested in the progress of the upload for a concrete file.
-         *
-         * @param listener Object to notify about progress of transfer.
-         * @param ocUpload Stored upload of interest
-         */
-        public void removeDatatransferProgressListener(
-            OnDatatransferProgressListener listener,
-            OCUpload ocUpload
-                                                      ) {
-            if (ocUpload == null || listener == null) {
-                return;
-            }
-
-            String targetKey = buildRemoteName(ocUpload.getAccountName(), ocUpload.getRemotePath());
-            new FilesUploadHelper().removeDatatransferProgressListener(listener,targetKey);
-        }
-
-        @Override
-        public void onTransferProgress(
-            long progressRate,
-            long totalTransferredSoFar,
-            long totalToTransfer,
-            String fileName
-                                      ) {
-            String key = buildRemoteName(mCurrentUpload.getUser().getAccountName(), mCurrentUpload.getFile().getRemotePath());
-            OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
-
-            if (boundListener != null) {
-                boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
-            }
-
-            Context context = MainApp.getAppContext();
-            if (context != null) {
-                ResultCode cancelReason = null;
-                Connectivity connectivity = connectivityService.getConnectivity();
-                if (mCurrentUpload.isWifiRequired() && !connectivity.isWifi()) {
-                    cancelReason = ResultCode.DELAYED_FOR_WIFI;
-                } else if (mCurrentUpload.isChargingRequired() && !powerManagementService.getBattery().isCharging()) {
-                    cancelReason = ResultCode.DELAYED_FOR_CHARGING;
-                } else if (!mCurrentUpload.isIgnoringPowerSaveMode() && powerManagementService.isPowerSavingEnabled()) {
-                    cancelReason = ResultCode.DELAYED_IN_POWER_SAVE_MODE;
-                }
-
-                if (cancelReason != null) {
-                    cancel(
-                        mCurrentUpload.getUser().getAccountName(),
-                        mCurrentUpload.getFile().getRemotePath(),
-                        cancelReason
-                          );
-                }
-            }
-        }
-
-        /**
-         * Builds a key for the map of listeners.
-         *
-         * TODO use method in IndexedForest, or refactor both to a common place add to local database) to better policy
-         * (add to local database, then upload)
-         *
-         * @param accountName Local name of the ownCloud account where the file to upload belongs.
-         * @param remotePath  Remote path to upload the file to.
-         * @return Key
-         */
-        public static String buildRemoteName(String accountName, String remotePath) {
-            return accountName + remotePath;
-        }
-    }
-
-    private void cancelOldErrorNotification(UploadFileOperation uploadFileOperation){
-        if (mNotificationManager == null) {
-            mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-        }
-
-        if (uploadFileOperation == null) return;
-
-        mNotificationManager.cancel(NotificationUtils.createUploadNotificationTag(uploadFileOperation.getFile()),
-                                    NOTIFICATION_ERROR_ID);
-
-        //cancel for old file because of file conflicts
-        OCFile oldFile = uploadFileOperation.getOldFile();
-        if ( oldFile != null) {
-            mNotificationManager.cancel(NotificationUtils.createUploadNotificationTag(oldFile),
-                                        NOTIFICATION_ERROR_ID);
-        }
-
-
-    }
-
-
-    /**
-     * Upload worker. Performs the pending uploads in the order they were requested.
-     *
-     * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
-     */
-    private static class ServiceHandler extends Handler {
-        // don't make it a final class, and don't remove the static ; lint will
-        // warn about a possible memory leak
-        private FileUploader mService;
-
-        public ServiceHandler(Looper looper, FileUploader service) {
-            super(looper);
-            if (service == null) {
-                throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
-            }
-            mService = service;
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            @SuppressWarnings("unchecked")
-            List<String> requestedUploads = (List<String>) msg.obj;
-            if (msg.obj != null) {
-                for (String requestedUpload : requestedUploads) {
-                    mService.uploadFile(requestedUpload);
-                }
-            }
-            Log_OC.d(TAG, "Stopping command after id " + msg.arg1);
-            mService.mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
-            mService.stopForeground(true);
-            mService.stopSelf(msg.arg1);
-        }
-    }
-
-
-    /**
-     * When cancel action in upload notification is pressed, cancel upload of item
-     */
-    public static class UploadNotificationActionReceiver extends BroadcastReceiver {
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-
-            String accountName = intent.getStringExtra(EXTRA_ACCOUNT_NAME);
-            String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
-            String action = intent.getAction();
-
-            if (ACTION_CANCEL_BROADCAST.equals(action)) {
-                Log_OC.d(TAG, "Cancel broadcast received for file " + remotePath + " at " + System.currentTimeMillis());
-
-                if (accountName == null || remotePath == null) {
-                    return;
-                }
-
-                FileUploaderBinder uploadBinder = (FileUploaderBinder) mBinder;
-                uploadBinder.cancel(accountName, remotePath, null);
-            } else if (ACTION_PAUSE_BROADCAST.equals(action)) {
-
-            } else {
-                Log_OC.d(TAG, "Unknown action to perform as UploadNotificationActionReceiver.");
-            }
-        }
-    }
-}

+ 0 - 1
app/src/main/java/com/owncloud/android/operations/DownloadFileOperation.java

@@ -23,7 +23,6 @@ package com.owncloud.android.operations;
 
 import android.content.Context;
 import android.text.TextUtils;
-import android.util.Log;
 import android.webkit.MimeTypeMap;
 
 import com.nextcloud.client.account.User;

+ 39 - 46
app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java

@@ -25,10 +25,11 @@ import android.content.Context;
 import android.text.TextUtils;
 
 import com.nextcloud.client.account.User;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -55,8 +56,8 @@ public class SynchronizeFileOperation extends SyncOperation {
     private boolean mTransferWasRequested;
 
     /**
-     * When 'false', uploads to the server are not done; only downloads or conflict detection.
-     * This is a temporal field.
+     * When 'false', uploads to the server are not done; only downloads or conflict detection. This is a temporal
+     * field.
      * TODO Remove when 'folder synchronization' replaces 'folder download'.
      */
     private boolean mAllowUploads;
@@ -65,15 +66,15 @@ public class SynchronizeFileOperation extends SyncOperation {
     /**
      * Constructor for "full synchronization mode".
      * <p/>
-     * Uses remotePath to retrieve all the data both in local cache and in the remote OC server
-     * when the operation is executed, instead of reusing {@link OCFile} instances.
+     * Uses remotePath to retrieve all the data both in local cache and in the remote OC server when the operation is
+     * executed, instead of reusing {@link OCFile} instances.
      * <p/>
      * Useful for direct synchronization of a single file.
      *
      * @param remotePath       remote path of the file
      * @param user             Nextcloud user owning the file.
-     * @param syncFileContents When 'true', transference of data will be started by the
-     *                         operation if needed and no conflict is detected.
+     * @param syncFileContents When 'true', transference of data will be started by the operation if needed and no
+     *                         conflict is detected.
      * @param context          Android context; needed to start transfers.
      */
     public SynchronizeFileOperation(
@@ -95,22 +96,20 @@ public class SynchronizeFileOperation extends SyncOperation {
 
 
     /**
-     * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or
-     * from remote OC server.
-     *
-     * Useful to include this operation as part of the synchronization of a folder
-     * (or a full account), avoiding the repetition of fetch operations (both in local database
-     * or remote server).
-     *
-     * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them,
-     * use the other constructor.
+     * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or from remote OC server.
+     * <p>
+     * Useful to include this operation as part of the synchronization of a folder (or a full account), avoiding the
+     * repetition of fetch operations (both in local database or remote server).
+     * <p>
+     * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them, use the other
+     * constructor.
      *
      * @param localFile        Data of file (just) retrieved from local cache/database.
-     * @param serverFile       Data of file (just) retrieved from a remote server. If null,
-     *                         will be retrieved from network by the operation when executed.
+     * @param serverFile       Data of file (just) retrieved from a remote server. If null, will be retrieved from
+     *                         network by the operation when executed.
      * @param user             Nextcloud user owning the file.
-     * @param syncFileContents When 'true', transference of data will be started by the
-     *                         operation if needed and no conflict is detected.
+     * @param syncFileContents When 'true', transference of data will be started by the operation if needed and no
+     *                         conflict is detected.
      * @param context          Android context; needed to start transfers.
      */
     public SynchronizeFileOperation(
@@ -144,25 +143,21 @@ public class SynchronizeFileOperation extends SyncOperation {
 
     /**
      * Temporal constructor.
-     *
-     * Extends the previous one to allow constrained synchronizations where uploads are never
-     * performed - only downloads or conflict detection.
-     *
-     * Do not use unless you are involved in 'folder synchronization' or 'folder download' work
-     * in progress.
-     *
+     * <p>
+     * Extends the previous one to allow constrained synchronizations where uploads are never performed - only downloads
+     * or conflict detection.
+     * <p>
+     * Do not use unless you are involved in 'folder synchronization' or 'folder download' work in progress.
+     * <p>
      * TODO Remove when 'folder synchronization' replaces 'folder download'.
      *
-     * @param localFile        Data of file (just) retrieved from local cache/database.
-     *                         MUSTN't be null.
-     * @param serverFile       Data of file (just) retrieved from a remote server.
-     *                         If null, will be retrieved from network by the operation
-     *                         when executed.
+     * @param localFile        Data of file (just) retrieved from local cache/database. MUSTN't be null.
+     * @param serverFile       Data of file (just) retrieved from a remote server. If null, will be retrieved from
+     *                         network by the operation when executed.
      * @param user             Nextcloud user owning the file.
-     * @param syncFileContents When 'true', transference of data will be started by the
-     *                         operation if needed and no conflict is detected.
-     * @param allowUploads     When 'false', uploads to the server are not done;
-     *                         only downloads or conflict detection.
+     * @param syncFileContents When 'true', transference of data will be started by the operation if needed and no
+     *                         conflict is detected.
+     * @param allowUploads     When 'false', uploads to the server are not done; only downloads or conflict detection.
      * @param context          Android context; needed to start transfers.
      */
     public SynchronizeFileOperation(
@@ -214,12 +209,12 @@ public class SynchronizeFileOperation extends SyncOperation {
                 if (TextUtils.isEmpty(mLocalFile.getEtag())) {
                     // file uploaded (null) or downloaded ("") before upgrade to version 1.8.0; check the old condition
                     serverChanged = mServerFile.getModificationTimestamp() !=
-                            mLocalFile.getModificationTimestampAtLastSyncForData();
+                        mLocalFile.getModificationTimestampAtLastSyncForData();
                 } else {
                     serverChanged = !mServerFile.getEtag().equals(mLocalFile.getEtag());
                 }
                 boolean localChanged =
-                        mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData();
+                    mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData();
 
                 /// decide action to perform depending upon changes
                 //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
@@ -286,7 +281,7 @@ public class SynchronizeFileOperation extends SyncOperation {
         }
 
         Log_OC.i(TAG, "Synchronizing " + mUser.getAccountName() + ", file " + mLocalFile.getRemotePath() +
-                ": " + result.getLogMessage());
+            ": " + result.getLogMessage());
 
         return result;
     }
@@ -298,13 +293,11 @@ public class SynchronizeFileOperation extends SyncOperation {
      * @param file OCFile object representing the file to upload
      */
     private void requestForUpload(OCFile file) {
-        FileUploader.uploadUpdateFile(
-            mContext,
+        FileUploadHelper.Companion.instance().uploadUpdatedFile(
             mUser,
-            file,
-            FileUploader.LOCAL_BEHAVIOUR_MOVE,
-            NameCollisionPolicy.OVERWRITE
-                                     );
+            new OCFile[]{ file },
+            FileUploadWorker.LOCAL_BEHAVIOUR_MOVE,
+            NameCollisionPolicy.OVERWRITE);
 
         mTransferWasRequested = true;
     }

+ 3 - 4
app/src/main/java/com/owncloud/android/operations/SynchronizeFolderOperation.java

@@ -25,7 +25,7 @@ import android.content.Intent;
 import android.text.TextUtils;
 
 import com.nextcloud.client.account.User;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.e2e.v1.decrypted.DecryptedFolderMetadataFileV1;
@@ -294,7 +294,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
         if (mLocalFolder.isEncrypted() && object == null) {
             throw new IllegalStateException("metadata is null!");
         }
-        
+
         // get current data about local contents of the folder to synchronize
         Map<String, OCFile> localFilesMap;
         E2EVersion e2EVersion;
@@ -308,7 +308,7 @@ public class SynchronizeFolderOperation extends SyncOperation {
             localFilesMap = RefreshFolderOperation.prefillLocalFilesMap((DecryptedFolderMetadataFile) object,
                                                                         storageManager.getFolderContent(mLocalFolder, false));
         }
-        
+
         // loop to synchronize every child
         List<OCFile> updatedFiles = new ArrayList<>(folderAndFiles.size() - 1);
         OCFile remoteFile;
@@ -421,7 +421,6 @@ public class SynchronizeFolderOperation extends SyncOperation {
     }
 
 
-    @SuppressFBWarnings("JLM")
     private void prepareOpsFromLocalKnowledge() throws OperationCancelledException {
         List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder, false);
         for (OCFile child : children) {

+ 6 - 6
app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java

@@ -29,6 +29,7 @@ import android.text.TextUtils;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.network.Connectivity;
 import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
@@ -45,7 +46,6 @@ import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFile;
 import com.owncloud.android.datamodel.e2e.v1.encrypted.EncryptedFolderMetadataFileV1;
 import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
@@ -1019,7 +1019,7 @@ public class UploadFileOperation extends SyncOperation {
 
     private RemoteOperationResult copyFile(File originalFile, String expectedPath) throws OperationCancelledException,
         IOException {
-        if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY && !mOriginalStoragePath.equals(expectedPath)) {
+        if (mLocalBehaviour == FileUploadWorker.LOCAL_BEHAVIOUR_COPY && !mOriginalStoragePath.equals(expectedPath)) {
             String temporalPath = FileStorageUtils.getInternalTemporalPath(user.getAccountName(), mContext) +
                 mFile.getRemotePath();
             mFile.setStoragePath(temporalPath);
@@ -1077,20 +1077,20 @@ public class UploadFileOperation extends SyncOperation {
                                         File originalFile,
                                         OwnCloudClient client) {
         switch (mLocalBehaviour) {
-            case FileUploader.LOCAL_BEHAVIOUR_FORGET:
+            case FileUploadWorker.LOCAL_BEHAVIOUR_FORGET:
             default:
                 mFile.setStoragePath("");
                 saveUploadedFile(client);
                 break;
 
-            case FileUploader.LOCAL_BEHAVIOUR_DELETE:
+            case FileUploadWorker.LOCAL_BEHAVIOUR_DELETE:
                 originalFile.delete();
                 mFile.setStoragePath("");
                 getStorageManager().deleteFileInMediaScan(originalFile.getAbsolutePath());
                 saveUploadedFile(client);
                 break;
 
-            case FileUploader.LOCAL_BEHAVIOUR_COPY:
+            case FileUploadWorker.LOCAL_BEHAVIOUR_COPY:
                 if (temporalFile != null) {
                     try {
                         move(temporalFile, expectedFile);
@@ -1111,7 +1111,7 @@ public class UploadFileOperation extends SyncOperation {
                 }
                 break;
 
-            case FileUploader.LOCAL_BEHAVIOUR_MOVE:
+            case FileUploadWorker.LOCAL_BEHAVIOUR_MOVE:
                 String expectedPath = FileStorageUtils.getDefaultSavePathFor(user.getAccountName(), mFile);
                 File newFile = new File(expectedPath);
 

+ 7 - 11
app/src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java

@@ -43,7 +43,8 @@ import android.widget.Toast;
 
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.files.downloader.DownloadTask;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.client.preferences.AppPreferencesImpl;
 import com.nextcloud.client.utils.HashUtil;
@@ -52,7 +53,6 @@ import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
@@ -100,7 +100,6 @@ import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_WRITE_ONLY;
 import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
 import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
-import static com.owncloud.android.files.services.FileUploader.LOCAL_BEHAVIOUR_DELETE;
 
 public class DocumentsStorageProvider extends DocumentsProvider {
 
@@ -267,13 +266,11 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
                         // TODO disable upload notifications as DocumentsProvider users already show them
                         // upload file with FileUploader service (off main thread)
-                        FileUploader.uploadUpdateFile(
-                            context,
+                        FileUploadHelper.Companion.instance().uploadUpdatedFile(
                             user,
-                            ocFile,
-                            LOCAL_BEHAVIOUR_DELETE,
-                            NameCollisionPolicy.OVERWRITE,
-                            false);
+                            new OCFile[]{ ocFile },
+                            FileUploadWorker.LOCAL_BEHAVIOUR_DELETE,
+                            NameCollisionPolicy.OVERWRITE);
                     } else {
                         // error, no upload needed
                         Log_OC.e(TAG, "File was closed with an error: " + ocFile.getFileName(), error);
@@ -307,7 +304,6 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     /**
      * Updates the OC File after a successful download.
      *
-     * TODO unify with code from {@link com.nextcloud.client.files.downloader.FileDownloadWorker} and {@link DownloadTask}.
      */
     private void saveDownloadedFile(FileDataStorageManager storageManager, DownloadFileOperation dfo, OCFile file) {
         long syncDate = System.currentTimeMillis();
@@ -344,7 +340,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     public AssetFileDescriptor openDocumentThumbnail(String documentId,
                                                      Point sizeHint,
                                                      CancellationSignal signal)
-            throws FileNotFoundException {
+        throws FileNotFoundException {
         Log_OC.d(TAG, "openDocumentThumbnail(), id=" + documentId);
 
         Document document = toDocument(documentId);

+ 1 - 1
app/src/main/java/com/owncloud/android/services/SyncFolderHandler.java

@@ -28,7 +28,7 @@ import android.os.Message;
 import android.util.Pair;
 
 import com.nextcloud.client.account.User;
-import com.nextcloud.client.files.downloader.FileDownloadWorker;
+import com.nextcloud.client.jobs.download.FileDownloadWorker;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.IndexedForest;
 import com.owncloud.android.lib.common.OwnCloudAccount;

+ 7 - 7
app/src/main/java/com/owncloud/android/ui/activity/ComponentsGetter.java

@@ -20,9 +20,9 @@
 
 package com.owncloud.android.ui.activity;
 
-import com.nextcloud.client.files.downloader.FileDownloadWorker;
+import com.nextcloud.client.jobs.download.FileDownloadWorker;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.helpers.FileOperationsHelper;
 
@@ -39,18 +39,18 @@ public interface ComponentsGetter {
      * To be invoked when the parent activity is fully created to get a reference
      * to the FileUploader service API.
      */
-    public FileUploaderBinder getFileUploaderBinder();
+    public FileUploadHelper getFileUploaderHelper();
+
 
-    
     /**
      * To be invoked when the parent activity is fully created to get a reference
-     * to the OperationsSerivce service API.
+     * to the OperationsService service API.
      */
     public OperationsServiceBinder getOperationsServiceBinder();
 
-    
+
     public FileDataStorageManager getStorageManager();
-    
+
     public FileOperationsHelper getFileOperationsHelper();
 
 

+ 36 - 23
app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt

@@ -21,14 +21,16 @@ import android.content.Intent
 import android.os.Bundle
 import android.widget.Toast
 import com.nextcloud.client.account.User
-import com.nextcloud.client.files.downloader.FileDownloadHelper
+import com.nextcloud.client.jobs.download.FileDownloadHelper
+import com.nextcloud.client.jobs.upload.FileUploadHelper
+import com.nextcloud.client.jobs.upload.FileUploadWorker
+import com.nextcloud.client.jobs.upload.UploadNotificationManager
 import com.nextcloud.model.HTTPStatusCodes
 import com.nextcloud.utils.extensions.getParcelableArgument
 import com.owncloud.android.R
 import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.datamodel.UploadsStorageManager
 import com.owncloud.android.db.OCUpload
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.files.services.NameCollisionPolicy
 import com.owncloud.android.lib.common.utils.Log_OC
 import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation
@@ -51,7 +53,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
     private var conflictUploadId: Long = 0
     private var existingFile: OCFile? = null
     private var newFile: OCFile? = null
-    private var localBehaviour = FileUploader.LOCAL_BEHAVIOUR_FORGET
+    private var localBehaviour = FileUploadWorker.LOCAL_BEHAVIOUR_FORGET
 
     @JvmField
     var listener: OnConflictDecisionMadeListener? = null
@@ -91,38 +93,49 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
             when (decision) {
                 Decision.CANCEL -> {}
                 Decision.KEEP_LOCAL -> {
-                    FileUploader.uploadUpdateFile(
-                        baseContext,
+                    upload?.let {
+                        FileUploadHelper.instance().cancelFileUpload(it.remotePath, it.accountName)
+                    }
+                    FileUploadHelper.instance().uploadUpdatedFile(
                         user,
-                        file,
+                        arrayOf(file),
                         localBehaviour,
                         NameCollisionPolicy.OVERWRITE
                     )
-                    uploadsStorageManager!!.removeUpload(upload)
                 }
 
                 Decision.KEEP_BOTH -> {
-                    FileUploader.uploadUpdateFile(
-                        baseContext,
+                    upload?.let {
+                        FileUploadHelper.instance().cancelFileUpload(it.remotePath, it.accountName)
+                    }
+                    FileUploadHelper.instance().uploadUpdatedFile(
                         user,
-                        file,
+                        arrayOf(file),
                         localBehaviour,
                         NameCollisionPolicy.RENAME
                     )
-                    uploadsStorageManager!!.removeUpload(upload)
                 }
 
-                Decision.KEEP_SERVER -> if (!shouldDeleteLocal()) {
-                    // Overwrite local file
-                    file?.let {
-                        FileDownloadHelper.instance().downloadFile(
-                            getUser().orElseThrow { RuntimeException() },
-                            file,
-                            conflictUploadId = conflictUploadId
-                        )
+                Decision.KEEP_SERVER -> {
+                    if (!shouldDeleteLocal()) {
+                        // Overwrite local file
+                        file?.let {
+                            FileDownloadHelper.instance().downloadFile(
+                                getUser().orElseThrow { RuntimeException() },
+                                file,
+                                conflictUploadId = conflictUploadId
+                            )
+                        }
+                    }
+
+                    upload?.let {
+                        FileUploadHelper.instance().cancelFileUpload(it.remotePath, it.accountName)
+
+                        UploadNotificationManager(
+                            applicationContext,
+                            viewThemeUtils
+                        ).dismissOldErrorNotification(it.remotePath, it.localPath)
                     }
-                } else {
-                    uploadsStorageManager!!.removeUpload(upload)
                 }
 
                 else -> {}
@@ -192,7 +205,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
         if (prev != null) {
             fragmentTransaction.remove(prev)
         }
-        if (existingFile != null && storageManager.fileExists(newFile!!.remotePath)) {
+        if (existingFile != null && storageManager.fileExists(newFile?.remotePath)) {
             val dialog = ConflictsResolveDialog.newInstance(
                 existingFile,
                 newFile,
@@ -226,7 +239,7 @@ class ConflictsResolveActivity : FileActivity(), OnConflictDecisionMadeListener
      * @return whether the local version of the files is to be deleted.
      */
     private fun shouldDeleteLocal(): Boolean {
-        return localBehaviour == FileUploader.LOCAL_BEHAVIOUR_DELETE
+        return localBehaviour == FileUploadWorker.LOCAL_BEHAVIOUR_DELETE
     }
 
     companion object {

+ 19 - 32
app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java

@@ -43,9 +43,9 @@ import android.text.TextUtils;
 import com.google.android.material.snackbar.Snackbar;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
-import com.nextcloud.client.files.downloader.FileDownloadWorker;
 import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.download.FileDownloadWorker;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.utils.EditorUtils;
 import com.nextcloud.utils.extensions.BundleExtensionsKt;
@@ -56,8 +56,6 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
@@ -123,8 +121,8 @@ import static com.owncloud.android.ui.activity.FileDisplayActivity.TAG_PUBLIC_LI
  * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud {@link Account}s .
  */
 public abstract class FileActivity extends DrawerActivity
-        implements OnRemoteOperationListener, ComponentsGetter, SslUntrustedCertDialog.OnSslUntrustedCertListener,
-        LoadingVersionNumberTask.VersionDevInterface, FileDetailSharingFragment.OnEditShareListener {
+    implements OnRemoteOperationListener, ComponentsGetter, SslUntrustedCertDialog.OnSslUntrustedCertListener,
+    LoadingVersionNumberTask.VersionDevInterface, FileDetailSharingFragment.OnEditShareListener {
 
     public static final String EXTRA_FILE = "com.owncloud.android.ui.activity.FILE";
     public static final String EXTRA_LIVE_PHOTO_FILE = "com.owncloud.android.ui.activity.LIVE.PHOTO.FILE";
@@ -150,7 +148,7 @@ public abstract class FileActivity extends DrawerActivity
     private static final String DIALOG_UNTRUSTED_CERT = "DIALOG_UNTRUSTED_CERT";
     private static final String DIALOG_CERT_NOT_SAVED = "DIALOG_CERT_NOT_SAVED";
 
-     /** Main {@link OCFile} handled by the activity.*/
+    /** Main {@link OCFile} handled by the activity.*/
     private OCFile mFile;
 
     /** Flag to signal if the activity is launched by a notification */
@@ -168,8 +166,7 @@ public abstract class FileActivity extends DrawerActivity
     private boolean mResumed;
 
     protected FileDownloadWorker.FileDownloadProgressListener fileDownloadProgressListener;
-    protected FileUploaderBinder mUploaderBinder;
-    private ServiceConnection mUploadServiceConnection;
+    protected FileUploadHelper fileUploadHelper = FileUploadHelper.Companion.instance();
 
     @Inject
     UserAccountManager accountManager;
@@ -229,7 +226,7 @@ public abstract class FileActivity extends DrawerActivity
             user = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_USER, User.class);
             mFile = IntentExtensionsKt.getParcelableArgument(getIntent(), FileActivity.EXTRA_FILE, OCFile.class);
             mFromNotification = getIntent().getBooleanExtra(FileActivity.EXTRA_FROM_NOTIFICATION,
-                    false);
+                                                            false);
 
             if (user != null) {
                 setUser(user);
@@ -238,13 +235,7 @@ public abstract class FileActivity extends DrawerActivity
 
         mOperationsServiceConnection = new OperationsServiceConnection();
         bindService(new Intent(this, OperationsService.class), mOperationsServiceConnection,
-                Context.BIND_AUTO_CREATE);
-
-        mUploadServiceConnection = newTransferenceServiceConnection();
-        if (mUploadServiceConnection != null) {
-            bindService(new Intent(this, FileUploader.class), mUploadServiceConnection,
                     Context.BIND_AUTO_CREATE);
-        }
     }
 
     public void checkInternetConnection() {
@@ -283,10 +274,6 @@ public abstract class FileActivity extends DrawerActivity
             unbindService(mOperationsServiceConnection);
             mOperationsServiceBinder = null;
         }
-        if (mUploadServiceConnection != null) {
-            unbindService(mUploadServiceConnection);
-            mUploadServiceConnection = null;
-        }
 
         super.onDestroy();
     }
@@ -365,7 +352,7 @@ public abstract class FileActivity extends DrawerActivity
         dismissLoadingDialog();
 
         if (!result.isSuccess() && (
-                result.getCode() == ResultCode.UNAUTHORIZED ||
+            result.getCode() == ResultCode.UNAUTHORIZED ||
                 (result.isException() && result.getException() instanceof AuthenticatorException)
         )) {
 
@@ -393,8 +380,8 @@ public abstract class FileActivity extends DrawerActivity
 
             } else if (result.getCode() != ResultCode.CANCELLED) {
                 DisplayUtils.showSnackMessage(
-                        this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources())
-                );
+                    this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources())
+                                             );
             }
 
         } else if (operation instanceof SynchronizeFileOperation) {
@@ -525,7 +512,7 @@ public abstract class FileActivity extends DrawerActivity
         } else {
             if (!operation.transferWasRequested()) {
                 DisplayUtils.showSnackMessage(this, ErrorMessageAdapter.getErrorCauseMessage(result,
-                        operation, getResources()));
+                                                                                             operation, getResources()));
             }
             supportInvalidateOptionsMenu();
         }
@@ -575,7 +562,7 @@ public abstract class FileActivity extends DrawerActivity
         long waitingForOpId = mFileOperationsHelper.getOpIdWaitingFor();
         if (waitingForOpId <= Integer.MAX_VALUE) {
             boolean wait = mOperationsServiceBinder.dispatchResultIfFinished((int)waitingForOpId,
-                    this);
+                                                                             this);
             if (!wait ) {
                 dismissLoadingDialog();
             }
@@ -620,8 +607,8 @@ public abstract class FileActivity extends DrawerActivity
     }
 
     @Override
-    public FileUploaderBinder getFileUploaderBinder() {
-        return mUploaderBinder;
+    public FileUploadHelper getFileUploaderHelper() {
+        return fileUploadHelper;
     }
 
     public OCFile getCurrentDir() {
@@ -648,7 +635,7 @@ public abstract class FileActivity extends DrawerActivity
     public void onFailedSavingCertificate() {
         ConfirmationDialogFragment dialog = ConfirmationDialogFragment.newInstance(
             R.string.ssl_validator_not_saved, new String[]{}, 0, R.string.common_ok, -1, -1
-        );
+                                                                                  );
         dialog.show(getSupportFragmentManager(), DIALOG_CERT_NOT_SAVED);
     }
 
@@ -694,10 +681,10 @@ public abstract class FileActivity extends DrawerActivity
                 DisplayUtils.startLinkIntent(activity, devApkLink);
             } else {
                 Snackbar.make(activity.findViewById(android.R.id.content), R.string.dev_version_new_version_available,
-                        Snackbar.LENGTH_LONG)
-                        .setAction(activity.getString(R.string.version_dev_download), v -> {
-                            DisplayUtils.startLinkIntent(activity, devApkLink);
-                        }).show();
+                              Snackbar.LENGTH_LONG)
+                    .setAction(activity.getString(R.string.version_dev_download), v -> {
+                        DisplayUtils.startLinkIntent(activity, devApkLink);
+                    }).show();
             }
         } else {
             if (!inBackground) {

+ 28 - 48
app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -65,8 +65,10 @@ import com.nextcloud.client.core.AsyncRunner;
 import com.nextcloud.client.di.Injectable;
 import com.nextcloud.client.editimage.EditImageActivity;
 import com.nextcloud.client.files.DeepLinkHandler;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
-import com.nextcloud.client.files.downloader.FileDownloadWorker;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
+import com.nextcloud.client.jobs.download.FileDownloadWorker;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.media.PlayerServiceConnection;
 import com.nextcloud.client.network.ClientFactory;
 import com.nextcloud.client.network.ConnectivityService;
@@ -84,8 +86,6 @@ import com.owncloud.android.databinding.FilesBinding;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.VirtualFolderType;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
@@ -869,7 +869,7 @@ public class FileDisplayActivity extends FileActivity
                             return;
                         }
 
-                        requestUploadOfFilesFromFileSystem(renamedFile.getParentFile().getAbsolutePath(), new String[]{renamedFile.getAbsolutePath()}, FileUploader.LOCAL_BEHAVIOUR_DELETE);
+                        requestUploadOfFilesFromFileSystem(renamedFile.getParentFile().getAbsolutePath(), new String[]{renamedFile.getAbsolutePath()}, FileUploadWorker.LOCAL_BEHAVIOUR_DELETE);
                     }
                 }
             }, new String[]{FileOperationsHelper.createImageFile(getActivity()).getAbsolutePath()}).execute();
@@ -909,20 +909,20 @@ public class FileDisplayActivity extends FileActivity
             }
 
             int behaviour = switch (resultCode) {
-                case UploadFilesActivity.RESULT_OK_AND_MOVE -> FileUploader.LOCAL_BEHAVIOUR_MOVE;
-                case UploadFilesActivity.RESULT_OK_AND_DELETE -> FileUploader.LOCAL_BEHAVIOUR_DELETE;
-                default -> FileUploader.LOCAL_BEHAVIOUR_FORGET;
+                case UploadFilesActivity.RESULT_OK_AND_MOVE -> FileUploadWorker.LOCAL_BEHAVIOUR_MOVE;
+                case UploadFilesActivity.RESULT_OK_AND_DELETE -> FileUploadWorker.LOCAL_BEHAVIOUR_DELETE;
+                default -> FileUploadWorker.LOCAL_BEHAVIOUR_FORGET;
             };
 
-            FileUploader.uploadNewFile(getUser().orElseThrow(RuntimeException::new),
-                                       filePaths,
-                                       remotePaths,
-                                       behaviour,
-                                       true,
-                                       UploadFileOperation.CREATED_BY_USER,
-                                       false,
-                                       false,
-                                       NameCollisionPolicy.ASK_USER);
+            FileUploadHelper.Companion.instance().uploadNewFiles(getUser().orElseThrow(RuntimeException::new),
+                                                                 filePaths,
+                                                                 remotePaths,
+                                                                 behaviour,
+                                                                 true,
+                                                                 UploadFileOperation.CREATED_BY_USER,
+                                                                 false,
+                                                                 false,
+                                                                 NameCollisionPolicy.ASK_USER);
 
         } else {
             Log_OC.d(TAG, "User clicked on 'Update' with no selection");
@@ -944,7 +944,7 @@ public class FileDisplayActivity extends FileActivity
             streamsToUpload.add(contentIntent.getData());
         }
 
-        int behaviour = (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) ? FileUploader.LOCAL_BEHAVIOUR_MOVE : FileUploader.LOCAL_BEHAVIOUR_COPY;
+        int behaviour = (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) ? FileUploadWorker.LOCAL_BEHAVIOUR_MOVE : FileUploadWorker.LOCAL_BEHAVIOUR_COPY;
 
         OCFile currentDir = getCurrentDir();
         String remotePath = (currentDir != null) ? currentDir.getRemotePath() : OCFile.ROOT_PATH;
@@ -1114,7 +1114,7 @@ public class FileDisplayActivity extends FileActivity
         }
 
         // Listen for upload messages
-        IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.getUploadFinishMessage());
+        IntentFilter uploadIntentFilter = new IntentFilter(FileUploadWorker.Companion.getUploadFinishMessage());
         mUploadFinishReceiver = new UploadFinishReceiver();
         localBroadcastManager.registerReceiver(mUploadFinishReceiver, uploadIntentFilter);
 
@@ -1359,21 +1359,21 @@ public class FileDisplayActivity extends FileActivity
          */
         @Override
         public void onReceive(Context context, Intent intent) {
-            String uploadedRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH);
-            String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);
+            String uploadedRemotePath = intent.getStringExtra(FileUploadWorker.EXTRA_REMOTE_PATH);
+            String accountName = intent.getStringExtra(FileUploadWorker.ACCOUNT_NAME);
             boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name);
             OCFile currentDir = getCurrentDir();
             boolean isDescendant = currentDir != null && uploadedRemotePath != null && uploadedRemotePath.startsWith(currentDir.getRemotePath());
 
             if (sameAccount && isDescendant) {
-                String linkedToRemotePath = intent.getStringExtra(FileUploader.EXTRA_LINKED_TO_PATH);
+                String linkedToRemotePath = intent.getStringExtra(FileUploadWorker.EXTRA_LINKED_TO_PATH);
                 if (linkedToRemotePath == null || isAscendant(linkedToRemotePath)) {
                     updateListOfFilesFragment(false);
                 }
             }
 
-            boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false);
-            boolean renamedInUpload = getFile().getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH));
+            boolean uploadWasFine = intent.getBooleanExtra(FileUploadWorker.EXTRA_UPLOAD_RESULT, false);
+            boolean renamedInUpload = getFile().getRemotePath().equals(intent.getStringExtra(FileUploadWorker.EXTRA_OLD_REMOTE_PATH));
 
             boolean sameFile = getFile().getRemotePath().equals(uploadedRemotePath) || renamedInUpload;
             Fragment details = getLeftFragment();
@@ -1567,6 +1567,8 @@ public class FileDisplayActivity extends FileActivity
             if (state instanceof WorkerState.Download) {
                 Log_OC.d(TAG, "Download worker started");
                 handleDownloadWorkerState();
+            } else if (state instanceof WorkerState.Idle) {
+                fileDownloadProgressListener = null;
             }
         });
     }
@@ -1587,31 +1589,12 @@ public class FileDisplayActivity extends FileActivity
 
     /**
      * Defines callbacks for service binding, passed to bindService()
+     * TODO: Check if this can be removed since download and uploads uses work manager now.
      */
     private class ListServiceConnection implements ServiceConnection {
 
         @Override
-        public void onServiceConnected(ComponentName component, IBinder service) {
-            if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
-                Log_OC.d(TAG, "Upload service connected");
-                mUploaderBinder = (FileUploaderBinder) service;
-            } else {
-                return;
-            }
-
-            // a new chance to get the mDownloadBinder through
-            // getFileDownloadBinder() - THIS IS A MESS
-            OCFileListFragment listOfFiles = getListOfFilesFragment();
-            if (listOfFiles != null && (getIntent() == null || (getIntent() != null &&
-                IntentExtensionsKt.getParcelableArgument(getIntent(), EXTRA_FILE, OCFile.class) == null))) {
-                listOfFiles.listDirectory(MainApp.isOnlyOnDevice(), false);
-            }
-
-            Fragment leftFragment = getLeftFragment();
-            if (leftFragment instanceof FileDetailFragment detailFragment) {
-                detailFragment.listenForTransferProgress();
-                detailFragment.updateFileDetails(false, false);
-            }
+        public void onServiceConnected(ComponentName name, IBinder service) {
         }
 
         @Override
@@ -1619,9 +1602,6 @@ public class FileDisplayActivity extends FileActivity
             if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloadWorker.class))) {
                 Log_OC.d(TAG, "Download service disconnected");
                 fileDownloadProgressListener = null;
-            } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) {
-                Log_OC.d(TAG, "Upload service disconnected");
-                mUploaderBinder = null;
             }
         }
     }

+ 3 - 72
app/src/main/java/com/owncloud/android/ui/activity/ManageAccountsActivity.java

@@ -28,21 +28,18 @@ import android.accounts.AccountManager;
 import android.accounts.AccountManagerCallback;
 import android.accounts.AccountManagerFuture;
 import android.accounts.OperationCanceledException;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.view.MenuItem;
 import android.view.View;
 
 import com.google.common.collect.Sets;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
 import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
 import com.nextcloud.client.onboarding.FirstRunActivity;
 import com.nextcloud.java.util.Optional;
 import com.nextcloud.model.WorkerState;
@@ -54,7 +51,6 @@ import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.datamodel.ArbitraryDataProvider;
 import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
 import com.owncloud.android.datamodel.FileDataStorageManager;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.UserInfo;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -108,7 +104,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
     private final Handler handler = new Handler();
     private String accountName;
     private UserListAdapter userListAdapter;
-    private ServiceConnection uploadServiceConnection;
     private Set<String> originalUsers;
     private String originalCurrentUser;
 
@@ -164,11 +159,9 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
 
         recyclerView.setAdapter(userListAdapter);
         recyclerView.setLayoutManager(new LinearLayoutManager(this));
-        initializeComponentGetters();
         observeWorkerState();
     }
 
-
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
@@ -243,17 +236,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
         }
     }
 
-    /**
-     * Initialize ComponentsGetters.
-     */
-    private void initializeComponentGetters() {
-        uploadServiceConnection = newTransferenceServiceConnection();
-        if (uploadServiceConnection != null) {
-            bindService(new Intent(this, FileUploader.class), uploadServiceConnection,
-                        Context.BIND_AUTO_CREATE);
-        }
-    }
-
     private List<UserListItem> getUserListItems() {
         List<User> users = accountManager.getAllUsers();
         List<UserListItem> userListItems = new ArrayList<>(users.size());
@@ -337,11 +319,7 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
             // after remove account
             Optional<User> user = accountManager.getUser(accountName);
             if (!user.isPresent()) {
-                // Cancel transfers of the removed account
-                if (mUploaderBinder != null) {
-                    mUploaderBinder.cancel(accountName);
-                }
-
+                fileUploadHelper.cancel(accountName);
                 FileDownloadHelper.Companion.instance().cancelAllDownloadsForAccount(workerAccountName, workerCurrentDownload);
             }
 
@@ -372,25 +350,10 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
         }
     }
 
-    @Override
-    protected void onDestroy() {
-        if (uploadServiceConnection != null) {
-            unbindService(uploadServiceConnection);
-            uploadServiceConnection = null;
-        }
-
-        super.onDestroy();
-    }
-
     public Handler getHandler() {
         return handler;
     }
 
-    @Override
-    public FileUploader.FileUploaderBinder getFileUploaderBinder() {
-        return mUploaderBinder;
-    }
-
     @Override
     public OperationsService.OperationsServiceBinder getOperationsServiceBinder() {
         return null;
@@ -406,10 +369,6 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
         return null;
     }
 
-    protected ServiceConnection newTransferenceServiceConnection() {
-        return new ManageAccountsServiceConnection();
-    }
-
     private void performAccountRemoval(User user) {
         // disable account in recycler view
         for (int i = 0; i < userListAdapter.getItemCount(); i++) {
@@ -427,13 +386,8 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
         ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(this);
         arbitraryDataProvider.storeOrUpdateKeyValue(user.getAccountName(), PENDING_FOR_REMOVAL, String.valueOf(true));
 
-        // Cancel transfers
-        if (mUploaderBinder != null) {
-            mUploaderBinder.cancel(user);
-        }
-
         FileDownloadHelper.Companion.instance().cancelAllDownloadsForAccount(workerAccountName, workerCurrentDownload);
-
+        fileUploadHelper.cancel(user.getAccountName());
         backgroundJobManager.startAccountRemovalJob(user.getAccountName(), false);
 
         // immediately select a new account
@@ -531,27 +485,4 @@ public class ManageAccountsActivity extends FileActivity implements UserListAdap
     public void onAccountClicked(User user) {
         openAccount(user);
     }
-
-    /**
-     * Defines callbacks for service binding, passed to bindService()
-     */
-    private class ManageAccountsServiceConnection implements ServiceConnection {
-
-        @Override
-        public void onServiceConnected(ComponentName component, IBinder service) {
-            if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
-                Log_OC.d(TAG, "Upload service connected");
-                mUploaderBinder = (FileUploader.FileUploaderBinder) service;
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName component) {
-            if (component.equals(new ComponentName(ManageAccountsActivity.this, FileUploader.class))) {
-                Log_OC.d(TAG, "Upload service suddenly disconnected");
-                mUploaderBinder = null;
-            }
-        }
-    }
-
 }

+ 8 - 8
app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java

@@ -59,6 +59,8 @@ import android.widget.Toast;
 import com.google.android.material.button.MaterialButton;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.di.Injectable;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.utils.extensions.BundleExtensionsKt;
 import com.nextcloud.utils.extensions.IntentExtensionsKt;
@@ -68,7 +70,6 @@ import com.owncloud.android.databinding.ReceiveExternalFilesBinding;
 import com.owncloud.android.databinding.UploadFileDialogBinding;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -884,17 +885,16 @@ public class ReceiveExternalFilesActivity extends FileActivity
     }
 
     public void uploadFile(String tmpName, String filename) {
-        FileUploader.uploadNewFile(
+        FileUploadHelper.Companion.instance().uploadNewFiles(
             getUser().orElseThrow(RuntimeException::new),
-            tmpName,
-            mFile.getRemotePath() + filename,
-            FileUploader.LOCAL_BEHAVIOUR_COPY,
+            new String[]{ tmpName },
+            new String[]{ mFile.getRemotePath() + filename},
+            FileUploadWorker.LOCAL_BEHAVIOUR_COPY,
             true,
             UploadFileOperation.CREATED_BY_USER,
             false,
             false,
-            NameCollisionPolicy.ASK_USER
-                                  );
+            NameCollisionPolicy.ASK_USER);
         finish();
     }
 
@@ -905,7 +905,7 @@ public class ReceiveExternalFilesActivity extends FileActivity
             mStreamsToUpload,
             mUploadPath,
             getUser().orElseThrow(RuntimeException::new),
-            FileUploader.LOCAL_BEHAVIOUR_DELETE,
+            FileUploadWorker.LOCAL_BEHAVIOUR_DELETE,
             true, // Show waiting dialog while file is being copied from private storage
             this  // Copy temp task listener
         );

+ 3 - 3
app/src/main/java/com/owncloud/android/ui/activity/SyncedFoldersActivity.kt

@@ -43,6 +43,7 @@ import com.nextcloud.client.di.Injectable
 import com.nextcloud.client.jobs.BackgroundJobManager
 import com.nextcloud.client.jobs.MediaFoldersDetectionWork
 import com.nextcloud.client.jobs.NotificationWork
+import com.nextcloud.client.jobs.upload.FileUploadWorker
 import com.nextcloud.client.preferences.AppPreferences
 import com.nextcloud.client.preferences.SubFolderRule
 import com.nextcloud.utils.extensions.getParcelableArgument
@@ -58,7 +59,6 @@ import com.owncloud.android.datamodel.OCFile
 import com.owncloud.android.datamodel.SyncedFolder
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem
 import com.owncloud.android.datamodel.SyncedFolderProvider
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.files.services.NameCollisionPolicy
 import com.owncloud.android.lib.common.utils.Log_OC
 import com.owncloud.android.ui.adapter.SyncedFolderAdapter
@@ -453,7 +453,7 @@ class SyncedFoldersActivity :
             true,
             false,
             account.name,
-            FileUploader.LOCAL_BEHAVIOUR_FORGET,
+            FileUploadWorker.LOCAL_BEHAVIOUR_FORGET,
             NameCollisionPolicy.ASK_USER.serialize(),
             false,
             clock.currentTime,
@@ -547,7 +547,7 @@ class SyncedFoldersActivity :
                         true,
                         false,
                         account.name,
-                        FileUploader.LOCAL_BEHAVIOUR_FORGET,
+                        FileUploadWorker.LOCAL_BEHAVIOUR_FORGET,
                         NameCollisionPolicy.ASK_USER.serialize(),
                         false,
                         clock.currentTime,

+ 2 - 2
app/src/main/java/com/owncloud/android/ui/activity/UploadFilesActivity.java

@@ -37,10 +37,10 @@ import android.widget.TextView;
 
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.di.Injectable;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.owncloud.android.R;
 import com.owncloud.android.databinding.UploadFilesLayoutBinding;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.ui.adapter.StoragePathAdapter;
 import com.owncloud.android.ui.asynctasks.CheckAvailableSpaceTask;
@@ -499,7 +499,7 @@ public class UploadFilesActivity extends DrawerActivity implements LocalFileList
                 data.putExtra(EXTRA_CHOSEN_FILES, new String[]{filesToUpload[0]});
                 setResult(RESULT_OK_AND_DELETE, data);
 
-                preferences.setUploaderBehaviour(FileUploader.LOCAL_BEHAVIOUR_DELETE);
+                preferences.setUploaderBehaviour(FileUploadWorker.LOCAL_BEHAVIOUR_DELETE);
             } else {
                 data.putExtra(EXTRA_CHOSEN_FILES, mFileListFragment.getCheckedFilePaths());
                 data.putExtra(LOCAL_BASE_PATH, mCurrentDir.getAbsolutePath());

+ 23 - 49
app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java

@@ -25,13 +25,10 @@ package com.owncloud.android.ui.activity;
 
 import android.accounts.Account;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.ServiceConnection;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -42,15 +39,17 @@ import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.utils.Throttler;
+import com.nextcloud.model.WorkerState;
+import com.nextcloud.model.WorkerStateLiveData;
 import com.owncloud.android.R;
 import com.owncloud.android.databinding.UploadListLayoutBinding;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -146,6 +145,20 @@ public class UploadListActivity extends FileActivity {
         setupDrawer(R.id.nav_uploads);
 
         setupContent();
+        observeWorkerState();
+    }
+
+    private void observeWorkerState() {
+        WorkerStateLiveData.Companion.instance().observe(this, state -> {
+            if (state instanceof WorkerState.Upload) {
+                Log_OC.d(TAG, "Upload worker started");
+                handleUploadWorkerState();
+            }
+        });
+    }
+
+    private void handleUploadWorkerState() {
+        uploadListAdapter.loadUploadItemsFromDb();
     }
 
     private void setupContent() {
@@ -180,6 +193,7 @@ public class UploadListActivity extends FileActivity {
         swipeListRefreshLayout.setOnRefreshListener(this::refresh);
 
         loadItems();
+        uploadListAdapter.loadUploadItemsFromDb();
     }
 
     private void loadItems() {
@@ -197,9 +211,7 @@ public class UploadListActivity extends FileActivity {
         backgroundJobManager.startImmediateFilesSyncJob(false, true);
 
         if(uploadsStorageManager.getFailedUploads().length > 0){
-            // retry failed uploads
-            new Thread(() -> FileUploader.retryFailedUploads(
-                this,
+            new Thread(() -> FileUploadHelper.Companion.instance().retryFailedUploads(
                 uploadsStorageManager,
                 connectivityService,
                 userAccountManager,
@@ -229,9 +241,9 @@ public class UploadListActivity extends FileActivity {
         // Listen for upload messages
         uploadMessagesReceiver = new UploadMessagesReceiver();
         IntentFilter uploadIntentFilter = new IntentFilter();
-        uploadIntentFilter.addAction(FileUploader.getUploadsAddedMessage());
-        uploadIntentFilter.addAction(FileUploader.getUploadStartMessage());
-        uploadIntentFilter.addAction(FileUploader.getUploadFinishMessage());
+        uploadIntentFilter.addAction(FileUploadWorker.Companion.getUploadsAddedMessage());
+        uploadIntentFilter.addAction(FileUploadWorker.Companion.getUploadStartMessage());
+        uploadIntentFilter.addAction(FileUploadWorker.Companion.getUploadFinishMessage());
         localBroadcastManager.registerReceiver(uploadMessagesReceiver, uploadIntentFilter);
 
         Log_OC.v(TAG, "onResume() end");
@@ -319,44 +331,6 @@ public class UploadListActivity extends FileActivity {
         }
     }
 
-
-    @Override
-    protected ServiceConnection newTransferenceServiceConnection() {
-        return new UploadListServiceConnection();
-    }
-
-    /**
-     * Defines callbacks for service binding, passed to bindService()
-     */
-    private class UploadListServiceConnection implements ServiceConnection {
-
-        @Override
-        public void onServiceConnected(ComponentName component, IBinder service) {
-            if (service instanceof FileUploaderBinder) {
-                if (mUploaderBinder == null) {
-                    mUploaderBinder = (FileUploaderBinder) service;
-                    Log_OC.d(TAG, "UploadListActivity connected to Upload service. component: " +
-                            component + " service: " + service);
-                    uploadListAdapter.loadUploadItemsFromDb();
-                } else {
-                    Log_OC.d(TAG, "mUploaderBinder already set. mUploaderBinder: " +
-                        mUploaderBinder + " service:" + service);
-                }
-            } else {
-                Log_OC.d(TAG, "UploadListActivity not connected to Upload service. component: " +
-                    component + " service: " + service);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName component) {
-            if (component.equals(new ComponentName(UploadListActivity.this, FileUploader.class))) {
-                Log_OC.d(TAG, "UploadListActivity suddenly disconnected from Upload service");
-                mUploaderBinder = null;
-            }
-        }
-    }
-
     /**
      * Once the file upload has changed its status -> update uploads list view
      */

+ 2 - 1
app/src/main/java/com/owncloud/android/ui/adapter/GalleryAdapter.kt

@@ -63,7 +63,7 @@ class GalleryAdapter(
     transferServiceGetter: ComponentsGetter,
     viewThemeUtils: ViewThemeUtils,
     var columns: Int,
-    val defaultThumbnailSize: Int
+    private val defaultThumbnailSize: Int
 ) : SectionedRecyclerViewAdapter<SectionedViewHolder>(), CommonOCFileListAdapterInterface, PopupTextProvider {
     var files: List<GalleryItems> = mutableListOf()
     private val ocFileListDelegate: OCFileListDelegate
@@ -73,6 +73,7 @@ class GalleryAdapter(
         storageManager = transferServiceGetter.storageManager
 
         ocFileListDelegate = OCFileListDelegate(
+            transferServiceGetter.fileUploaderHelper,
             context,
             ocFileListFragmentInterface,
             user,

+ 3 - 1
app/src/main/java/com/owncloud/android/ui/adapter/OCFileListAdapter.java

@@ -46,6 +46,7 @@ import android.widget.LinearLayout;
 import com.elyeproj.loaderviewlibrary.LoaderImageView;
 import com.nextcloud.android.common.ui.theme.utils.ColorRole;
 import com.nextcloud.client.account.User;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
@@ -170,7 +171,8 @@ public class OCFileListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
 
         this.viewThemeUtils = viewThemeUtils;
 
-        ocFileListDelegate = new OCFileListDelegate(activity,
+        ocFileListDelegate = new OCFileListDelegate(FileUploadHelper.Companion.instance(),
+                                                    activity,
                                                     ocFileListFragmentInterface,
                                                     user,
                                                     mStorageManager,

+ 4 - 3
app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt

@@ -32,7 +32,8 @@ import androidx.core.content.res.ResourcesCompat
 import com.elyeproj.loaderviewlibrary.LoaderImageView
 import com.nextcloud.android.common.ui.theme.utils.ColorRole
 import com.nextcloud.client.account.User
-import com.nextcloud.client.files.downloader.FileDownloadHelper
+import com.nextcloud.client.jobs.download.FileDownloadHelper
+import com.nextcloud.client.jobs.upload.FileUploadHelper
 import com.nextcloud.client.preferences.AppPreferences
 import com.nextcloud.utils.extensions.createRoundedOutline
 import com.owncloud.android.R
@@ -53,6 +54,7 @@ import com.owncloud.android.utils.theme.ViewThemeUtils
 
 @Suppress("LongParameterList", "TooManyFunctions")
 class OCFileListDelegate(
+    private val fileUploadHelper: FileUploadHelper,
     private val context: Context,
     private val ocFileListFragmentInterface: OCFileListFragmentInterface,
     private val user: User,
@@ -343,12 +345,11 @@ class OCFileListDelegate(
 
     private fun showLocalFileIndicator(file: OCFile, gridViewHolder: ListGridImageViewHolder) {
         val operationsServiceBinder = transferServiceGetter.operationsServiceBinder
-        val fileUploaderBinder = transferServiceGetter.fileUploaderBinder
 
         val icon: Int? = when {
             operationsServiceBinder?.isSynchronizing(user, file) == true ||
                 FileDownloadHelper.instance().isDownloading(user, file) ||
-                fileUploaderBinder?.isUploading(user, file) == true -> {
+                fileUploadHelper.isUploading(user, file) -> {
                 // synchronizing, downloading or uploading
                 R.drawable.ic_synchronizing
             }

+ 37 - 49
app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java

@@ -43,7 +43,8 @@ import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.core.Clock;
 import com.nextcloud.client.device.PowerManagementService;
-import com.nextcloud.client.jobs.FilesUploadWorker;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.java.util.Optional;
 import com.owncloud.android.MainApp;
@@ -58,7 +59,6 @@ import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus;
 import com.owncloud.android.db.OCUpload;
 import com.owncloud.android.db.OCUploadComparator;
 import com.owncloud.android.db.UploadResult;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.RefreshFolderOperation;
@@ -95,6 +95,8 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
     private final ViewThemeUtils viewThemeUtils;
     private NotificationManager mNotificationManager;
 
+    private final FileUploadHelper uploadHelper = FileUploadHelper.Companion.instance();
+
     @Override
     public int getSectionCount() {
         return uploadGroups.length;
@@ -126,21 +128,16 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         headerViewHolder.binding.uploadListAction.setOnClickListener(v -> {
             switch (group.type) {
                 case CURRENT -> {
-                    FileUploader.FileUploaderBinder uploaderBinder = parentActivity.getFileUploaderBinder();
-                    if (uploaderBinder != null) {
-                        for (OCUpload upload : group.getItems()) {
-                            uploaderBinder.cancel(upload);
-                        }
+                    for (OCUpload upload : group.getItems()) {
+                        uploadHelper.cancelFileUpload(upload.getRemotePath(), upload.getAccountName());
                     }
                 }
                 case FINISHED -> uploadsStorageManager.clearSuccessfulUploads();
-                case FAILED -> new Thread(() -> FileUploader.retryFailedUploads(
-                    parentActivity,
+                case FAILED -> new Thread(() -> FileUploadHelper.Companion.instance().retryFailedUploads(
                     uploadsStorageManager,
                     connectivityService,
                     accountManager,
-                    powerManagementService
-                                                                               )).start();
+                    powerManagementService)).start();
                 default -> {
                 }
                 // do nothing
@@ -164,6 +161,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                              final Clock clock,
                              final ViewThemeUtils viewThemeUtils) {
         Log_OC.d(TAG, "UploadListAdapter");
+
         this.parentActivity = fileActivity;
         this.uploadsStorageManager = uploadsStorageManager;
         this.storageManager = storageManager;
@@ -270,33 +268,31 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                 viewThemeUtils.platform.themeHorizontalProgressBar(itemViewHolder.binding.uploadProgressBar);
                 itemViewHolder.binding.uploadProgressBar.setProgress(0);
                 itemViewHolder.binding.uploadProgressBar.setVisibility(View.VISIBLE);
-                FileUploader.FileUploaderBinder binder = parentActivity.getFileUploaderBinder();
-                if (binder != null) {
-                    if (binder.isUploadingNow(item)) {
-                        // really uploading, so...
-                        // ... unbind the old progress bar, if any; ...
-                        if (progressListener != null) {
-                            binder.removeDatatransferProgressListener(
-                                progressListener,
-                                progressListener.getUpload()   // the one that was added
-                                                                     );
-                        }
-                        // ... then, bind the current progress bar to listen for updates
-                        progressListener = new ProgressListener(item, itemViewHolder.binding.uploadProgressBar);
-                        binder.addDatatransferProgressListener(progressListener, item);
-                    } else {
-                        // not really uploading; stop listening progress if view is reused!
-                        if (progressListener != null &&
-                            progressListener.isWrapping(itemViewHolder.binding.uploadProgressBar)) {
-                            binder.removeDatatransferProgressListener(progressListener,
-                                                                      progressListener.getUpload() // the one that was added
-                                                                     );
-                            progressListener = null;
-                        }
+
+                if (uploadHelper.isUploadingNow(item)) {
+                    // really uploading, so...
+                    // ... unbind the old progress bar, if any; ...
+                    if (progressListener != null) {
+                        String targetKey = FileUploadHelper.Companion.buildRemoteName(progressListener.getUpload().getAccountName(), progressListener.getUpload().getRemotePath());
+                        uploadHelper.removeUploadTransferProgressListener(progressListener, targetKey);
                     }
+                    // ... then, bind the current progress bar to listen for updates
+                    progressListener = new ProgressListener(item, itemViewHolder.binding.uploadProgressBar);
+                    String targetKey = FileUploadHelper.Companion.buildRemoteName(item.getAccountName(), item.getRemotePath());
+                    uploadHelper.addUploadTransferProgressListener(progressListener, targetKey);
+
                 } else {
-                    Log_OC.w(TAG, "FileUploaderBinder not ready yet for upload " + item.getRemotePath());
+                    // not really uploading; stop listening progress if view is reused!
+                    if (progressListener != null &&
+                        progressListener.isWrapping(itemViewHolder.binding.uploadProgressBar)) {
+
+                        String targetKey = FileUploadHelper.Companion.buildRemoteName(progressListener.getUpload().getAccountName(), progressListener.getUpload().getRemotePath());
+
+                        uploadHelper.removeUploadTransferProgressListener(progressListener, targetKey);
+                        progressListener = null;
+                    }
                 }
+
                 itemViewHolder.binding.uploadDate.setVisibility(View.GONE);
                 itemViewHolder.binding.uploadFileSize.setVisibility(View.GONE);
                 itemViewHolder.binding.uploadProgressBar.invalidate();
@@ -312,11 +308,8 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
             itemViewHolder.binding.uploadRightButton.setImageResource(R.drawable.ic_action_cancel_grey);
             itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE);
             itemViewHolder.binding.uploadRightButton.setOnClickListener(v -> {
-                FileUploader.FileUploaderBinder uploaderBinder = parentActivity.getFileUploaderBinder();
-                if (uploaderBinder != null) {
-                    uploaderBinder.cancel(item);
-                    loadUploadItemsFromDb();
-                }
+                uploadHelper.cancelFileUpload(item.getRemotePath(), item.getAccountName());
+                loadUploadItemsFromDb();
             });
 
         } else if (item.getUploadStatus() == UploadStatus.UPLOAD_FAILED) {
@@ -363,7 +356,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
                 File file = new File(item.getLocalPath());
                 Optional<User> user = accountManager.getUser(item.getAccountName());
                 if (file.exists() && user.isPresent()) {
-                    FileUploader.retryUpload(parentActivity, user.get(), item);
+                    uploadHelper.retryUpload(item, user.get());
                     loadUploadItemsFromDb();
                 } else {
                     DisplayUtils.showSnackMessage(
@@ -608,14 +601,11 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
      * @return Text describing the status of the given upload.
      */
     private String getStatusText(OCUpload upload) {
-
         String status;
         switch (upload.getUploadStatus()) {
-
             case UPLOAD_IN_PROGRESS:
                 status = parentActivity.getString(R.string.uploads_view_later_waiting_to_upload);
-                FileUploader.FileUploaderBinder binder = parentActivity.getFileUploaderBinder();
-                if (binder != null && binder.isUploadingNow(upload)) {
+                if (uploadHelper.isUploadingNow(upload)) {
                     // really uploading, bind the progress bar to listen for progress updates
                     status = parentActivity.getString(R.string.uploader_upload_in_progress_ticker);
                 }
@@ -862,10 +852,8 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
         }
 
         void fixAndSortItems(OCUpload... array) {
-            FileUploader.FileUploaderBinder binder = parentActivity.getFileUploaderBinder();
-
             for (OCUpload upload : array) {
-                upload.setDataFixed(binder);
+                upload.setDataFixed(uploadHelper);
             }
             Arrays.sort(array, new OCUploadComparator());
 
@@ -885,7 +873,7 @@ public class UploadListAdapter extends SectionedRecyclerViewAdapter<SectionedVie
 
         if (upload == null) return;
         mNotificationManager.cancel(NotificationUtils.createUploadNotificationTag(upload.getRemotePath(),upload.getLocalPath()),
-                                    FilesUploadWorker.NOTIFICATION_ERROR_ID);
+                                    FileUploadWorker.NOTIFICATION_ERROR_ID);
 
     }
 

+ 5 - 6
app/src/main/java/com/owncloud/android/ui/asynctasks/CopyAndUploadContentUrisTask.java

@@ -30,8 +30,8 @@ import android.provider.DocumentsContract;
 import android.widget.Toast;
 
 import com.nextcloud.client.account.User;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.owncloud.android.R;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
@@ -247,17 +247,16 @@ public class CopyAndUploadContentUrisTask extends AsyncTask<Object, Void, Result
     }
 
     private void requestUpload(User user, String localPath, String remotePath, int behaviour) {
-        FileUploader.uploadNewFile(
+        FileUploadHelper.Companion.instance().uploadNewFiles(
             user,
-            localPath,
-            remotePath,
+            new String[]{ localPath },
+            new String[]{ remotePath },
             behaviour,
             false,      // do not create parent folder if not existent
             UploadFileOperation.CREATED_BY_USER,
             false,
             false,
-            NameCollisionPolicy.ASK_USER
-                                  );
+            NameCollisionPolicy.ASK_USER);
     }
 
     @Override

+ 1 - 1
app/src/main/java/com/owncloud/android/ui/dialog/SetupEncryptionDialogFragment.kt

@@ -287,7 +287,7 @@ class SetupEncryptionDialogFragment : DialogFragment(), Injectable {
             mWeakContext = WeakReference(context)
         }
 
-        @Suppress("ReturnCount")
+        @Suppress("ReturnCount", "LongMethod")
         @Deprecated("Deprecated in Java")
         override fun doInBackground(vararg params: Void?): String? {
             // fetch private/public key

+ 7 - 7
app/src/main/java/com/owncloud/android/ui/dialog/parcel/SyncedFolderParcelable.java

@@ -23,10 +23,10 @@ package com.owncloud.android.ui.dialog.parcel;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.preferences.SubFolderRule;
 import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 
 /**
@@ -134,11 +134,11 @@ public class SyncedFolderParcelable implements Parcelable {
 
     public Integer getUploadActionInteger() {
         switch (uploadAction) {
-            case FileUploader.LOCAL_BEHAVIOUR_FORGET:
+            case FileUploadWorker.LOCAL_BEHAVIOUR_FORGET:
                 return 0;
-            case FileUploader.LOCAL_BEHAVIOUR_MOVE:
+            case FileUploadWorker.LOCAL_BEHAVIOUR_MOVE:
                 return 1;
-            case FileUploader.LOCAL_BEHAVIOUR_DELETE:
+            case FileUploadWorker.LOCAL_BEHAVIOUR_DELETE:
                 return 2;
         }
         return 0;
@@ -147,13 +147,13 @@ public class SyncedFolderParcelable implements Parcelable {
     public void setUploadAction(String uploadAction) {
         switch (uploadAction) {
             case "LOCAL_BEHAVIOUR_FORGET":
-                this.uploadAction = FileUploader.LOCAL_BEHAVIOUR_FORGET;
+                this.uploadAction = FileUploadWorker.LOCAL_BEHAVIOUR_FORGET;
                 break;
             case "LOCAL_BEHAVIOUR_MOVE":
-                this.uploadAction = FileUploader.LOCAL_BEHAVIOUR_MOVE;
+                this.uploadAction = FileUploadWorker.LOCAL_BEHAVIOUR_MOVE;
                 break;
             case "LOCAL_BEHAVIOUR_DELETE":
-                this.uploadAction = FileUploader.LOCAL_BEHAVIOUR_DELETE;
+                this.uploadAction = FileUploadWorker.LOCAL_BEHAVIOUR_DELETE;
                 break;
             default:
                 // do nothing

+ 24 - 14
app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -44,8 +44,9 @@ import com.google.android.material.tabs.TabLayout;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.di.Injectable;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
 import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.client.network.ClientFactory;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.client.preferences.AppPreferences;
@@ -58,7 +59,6 @@ import com.owncloud.android.databinding.FileDetailsFragmentBinding;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@@ -233,7 +233,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
         } else {
             binding.emptyList.emptyListView.setVisibility(View.GONE);
         }
-        
+
         Context context = getContext();
         if (context == null) {
             return null;
@@ -506,7 +506,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
      *
      * @param transferring Flag signaling if the file should be considered as downloading or uploading, although
      *                     {@link FileDownloadHelper#isDownloading(User, OCFile)}  and
-     *                     {@link FileUploaderBinder#isUploading(User, OCFile)} return false.
+     *                     {@link FileUploadHelper#isUploading(User, OCFile)} return false.
      * @param refresh      If 'true', try to refresh the whole file from the database
      */
     public void updateFileDetails(boolean transferring, boolean refresh) {
@@ -537,10 +537,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             setFavoriteIconStatus(file.isFavorite());
 
             // configure UI for depending upon local state of the file
-            FileUploaderBinder uploaderBinder = containerActivity.getFileUploaderBinder();
             if (transferring
                 || (FileDownloadHelper.Companion.instance().isDownloading(user, file))
-                || (uploaderBinder != null && uploaderBinder.isUploading(user, file))) {
+                || (FileUploadHelper.Companion.instance().isUploading(user, file))) {
                 setButtonsForTransferring();
 
             } else if (file.isDown()) {
@@ -661,11 +660,10 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
             // show the progress bar for the transfer
             binding.progressBlock.setVisibility(View.VISIBLE);
             binding.progressText.setVisibility(View.VISIBLE);
-            FileUploaderBinder uploaderBinder = containerActivity.getFileUploaderBinder();
             if (FileDownloadHelper.Companion.instance().isDownloading(user, getFile())) {
                 binding.progressText.setText(R.string.downloader_download_in_progress_ticker);
             } else {
-                if (uploaderBinder != null && uploaderBinder.isUploading(user, getFile())) {
+                if (FileUploadHelper.Companion.instance().isUploading(user, getFile())) {
                     binding.progressText.setText(R.string.uploader_upload_in_progress_ticker);
                 }
             }
@@ -698,9 +696,15 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
                 containerActivity.getFileDownloadProgressListener().
                     addDataTransferProgressListener(progressListener, getFile());
             }
-            if (containerActivity.getFileUploaderBinder() != null) {
-                containerActivity.getFileUploaderBinder().
-                    addDatatransferProgressListener(progressListener, user, getFile());
+
+            if (containerActivity.getFileUploaderHelper() != null) {
+                OCFile file = getFile();
+                if (user == null || file == null) {
+                    return;
+                }
+
+                String targetKey = FileUploadHelper.Companion.buildRemoteName(user.getAccountName(), file.getRemotePath());
+                containerActivity.getFileUploaderHelper().addUploadTransferProgressListener(progressListener, targetKey);
             }
         } else {
             Log_OC.d(TAG, "progressListener == null");
@@ -713,9 +717,15 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
                 containerActivity.getFileDownloadProgressListener().
                     removeDataTransferProgressListener(progressListener, getFile());
             }
-            if (containerActivity.getFileUploaderBinder() != null) {
-                containerActivity.getFileUploaderBinder().
-                    removeDatatransferProgressListener(progressListener, user, getFile());
+            if (containerActivity.getFileUploaderHelper() != null) {
+                OCFile file = getFile();
+
+                if (user == null || file == null) {
+                    return;
+                }
+
+                String targetKey = FileUploadHelper.Companion.buildRemoteName(user.getAccountName(), file.getRemotePath());
+                containerActivity.getFileUploaderHelper().removeUploadTransferProgressListener(progressListener, targetKey);
             }
         }
     }

+ 3 - 4
app/src/main/java/com/owncloud/android/ui/fragment/FileFragment.java

@@ -26,7 +26,6 @@ import android.os.Bundle;
 
 import com.nextcloud.utils.extensions.BundleExtensionsKt;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.ui.activity.ComponentsGetter;
 
 import androidx.annotation.Nullable;
@@ -106,7 +105,7 @@ public class FileFragment extends Fragment {
 
         } catch (ClassCastException e) {
             throw new IllegalArgumentException(activity.toString() + " must implement " +
-                    ContainerActivity.class.getSimpleName(), e);
+                                                   ContainerActivity.class.getSimpleName(), e);
         }
     }
 
@@ -159,8 +158,8 @@ public class FileFragment extends Fragment {
          * This happens when a download or upload is started or ended for a file.
          *
          * This method is necessary by now to update the user interface of the double-pane layout
-         * in tablets because methods {@link //FileDownloaderBinder # isDownloading(Account, OCFile)}
-         * and {@link FileUploaderBinder# isUploading(Account, OCFile)}
+         * in tablets because methods FileDownloaderBinder.isDownloading(Account, OCFile)
+         * and FilesUploadHelper.isUploading(Account, OCFile)
          * won't provide the needed response before the method where this is called finishes.
          *
          * TODO Remove this when the transfer state of a file is kept in the database

+ 3 - 3
app/src/main/java/com/owncloud/android/ui/fragment/contactsbackup/BackupListFragment.java

@@ -40,10 +40,10 @@ import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.di.Injectable;
 import com.nextcloud.client.files.DownloadRequest;
 import com.nextcloud.client.files.Request;
-import com.nextcloud.client.files.transfer.Transfer;
-import com.nextcloud.client.files.transfer.TransferManagerConnection;
-import com.nextcloud.client.files.transfer.TransferState;
 import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.transfer.Transfer;
+import com.nextcloud.client.jobs.transfer.TransferManagerConnection;
+import com.nextcloud.client.jobs.transfer.TransferState;
 import com.nextcloud.client.network.ClientFactory;
 import com.nextcloud.utils.extensions.BundleExtensionsKt;
 import com.owncloud.android.R;

+ 9 - 5
app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java

@@ -47,8 +47,9 @@ import android.webkit.MimeTypeMap;
 
 import com.nextcloud.client.account.CurrentAccountProvider;
 import com.nextcloud.client.account.User;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
 import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
 import com.nextcloud.client.network.ConnectivityService;
 import com.nextcloud.java.util.Optional;
 import com.nextcloud.utils.EditorUtils;
@@ -59,7 +60,6 @@ import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.StreamMediaFileOperation;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.files.CheckEtagRemoteOperation;
@@ -104,6 +104,7 @@ import java.util.Collection;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -1010,9 +1011,12 @@ public class FileOperationsHelper {
             FileDownloadHelper.Companion.instance().cancelPendingOrCurrentDownloads(currentUser, files);
         }
 
-        FileUploaderBinder uploaderBinder = fileActivity.getFileUploaderBinder();
-        if (uploaderBinder != null && uploaderBinder.isUploading(currentUser, file)) {
-            uploaderBinder.cancel(currentUser.toPlatformAccount(), file);
+        if (FileUploadHelper.Companion.instance().isUploading(currentUser, file)) {
+            try {
+                FileUploadHelper.Companion.instance().cancelFileUpload(file.getRemotePath(), currentUser.getAccountName());
+            } catch (NoSuchElementException e) {
+                Log_OC.e(TAG, "Error cancelling current upload because user does not exist!");
+            }
         }
     }
 

+ 8 - 8
app/src/main/java/com/owncloud/android/ui/helpers/UriUploader.kt

@@ -23,8 +23,8 @@ import android.content.ContentResolver
 import android.net.Uri
 import android.os.Parcelable
 import com.nextcloud.client.account.User
+import com.nextcloud.client.jobs.upload.FileUploadHelper
 import com.owncloud.android.R
-import com.owncloud.android.files.services.FileUploader
 import com.owncloud.android.files.services.NameCollisionPolicy
 import com.owncloud.android.lib.common.utils.Log_OC
 import com.owncloud.android.operations.UploadFileOperation
@@ -35,16 +35,16 @@ import com.owncloud.android.ui.fragment.TaskRetainerFragment
 import com.owncloud.android.utils.UriUtils.getDisplayNameForUri
 
 /**
- * This class examines URIs pointing to files to upload and then requests [FileUploader] to upload them.
+ * This class examines URIs pointing to files to upload and then requests [FileUploadHelper] to upload them.
  *
  *
- * URIs with scheme file:// do not require any previous processing, their path is sent to [FileUploader] to find
+ * URIs with scheme file:// do not require any previous processing, their path is sent to [FileUploadHelper] to find
  * the source file.
  *
  *
  * URIs with scheme content:// are handling assuming that file is in private storage owned by a different app, and that
  * persistence permission is not granted. Due to this, contents of the file are temporary copied by the OC app, and then
- * passed [FileUploader].
+ * passed [FileUploadHelper].
  */
 @Suppress(
     "Detekt.LongParameterList",
@@ -114,7 +114,7 @@ class UriUploader(
     private fun isSensitiveUri(uri: Uri): Boolean = uri.toString().contains(mActivity.packageName)
 
     /**
-     * Requests the upload of a file in the local file system to [FileUploader] service.
+     * Requests the upload of a file in the local file system to [FileUploadHelper] service.
      *
      * The original file will be left in its original location, and will not be duplicated.
      * As a side effect, the user will see the file as not uploaded when accesses to the OC app.
@@ -125,10 +125,10 @@ class UriUploader(
      * @param remotePath    Absolute path in the current OC account to set to the uploaded file.
      */
     private fun requestUpload(localPath: String?, remotePath: String) {
-        FileUploader.uploadNewFile(
+        FileUploadHelper.instance().uploadNewFiles(
             user,
-            localPath,
-            remotePath,
+            arrayOf(localPath ?: ""),
+            arrayOf(remotePath),
             mBehaviour,
             false, // do not create parent folder if not existent
             UploadFileOperation.CREATED_BY_USER,

+ 23 - 58
app/src/main/java/com/owncloud/android/ui/preview/PreviewImageActivity.java

@@ -24,21 +24,19 @@ package com.owncloud.android.ui.preview;
 
 import android.annotation.SuppressLint;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.ServiceConnection;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.view.MenuItem;
 import android.view.View;
 
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.di.Injectable;
 import com.nextcloud.client.editimage.EditImageActivity;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
-import com.nextcloud.client.files.downloader.FileDownloadWorker;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
+import com.nextcloud.client.jobs.download.FileDownloadWorker;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.java.util.Optional;
 import com.nextcloud.model.WorkerState;
@@ -49,13 +47,10 @@ import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.VirtualFolderType;
-import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.operations.DownloadType;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.ui.activity.FileActivity;
@@ -82,10 +77,10 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  */
 @SuppressWarnings("PMD.AvoidDuplicateLiterals")
 public class PreviewImageActivity extends FileActivity implements
-        FileFragment.ContainerActivity,
-        ViewPager.OnPageChangeListener,
-        OnRemoteOperationListener,
-        Injectable {
+    FileFragment.ContainerActivity,
+    ViewPager.OnPageChangeListener,
+    OnRemoteOperationListener,
+    Injectable {
 
     public static final String TAG = PreviewImageActivity.class.getSimpleName();
     public static final String EXTRA_VIRTUAL_TYPE = "EXTRA_VIRTUAL_TYPE";
@@ -307,7 +302,7 @@ public class PreviewImageActivity extends FileActivity implements
     }
 
     private void observeWorkerState() {
-       WorkerStateLiveData.Companion.instance().observe(this, state -> {
+        WorkerStateLiveData.Companion.instance().observe(this, state -> {
             if (state instanceof WorkerState.Download) {
                 Log_OC.d(TAG, "Download worker started");
                 isDownloadWorkStarted = true;
@@ -325,41 +320,11 @@ public class PreviewImageActivity extends FileActivity implements
         });
     }
 
-    @Override
-    protected ServiceConnection newTransferenceServiceConnection() {
-        return new PreviewImageServiceConnection();
-    }
-
-    /** Defines callbacks for service binding, passed to bindService() */
-    private class PreviewImageServiceConnection implements ServiceConnection {
-
-        @Override
-        public void onServiceConnected(ComponentName component, IBinder service) {
-            if (component.equals(new ComponentName(PreviewImageActivity.this,
-                    FileUploader.class))) {
-                Log_OC.d(TAG, "Upload service connected");
-                mUploaderBinder = (FileUploaderBinder) service;
-            }
-
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName component) {
-            if (component.equals(new ComponentName(PreviewImageActivity.this,
-                    FileUploader.class))) {
-                Log_OC.d(TAG, "Upload service suddenly disconnected");
-                mUploaderBinder = null;
-            }
-        }
-    }
-
-
     @Override
     public void onStop() {
         super.onStop();
     }
 
-
     @Override
     public void onDestroy() {
         super.onDestroy();
@@ -374,7 +339,7 @@ public class PreviewImageActivity extends FileActivity implements
         localBroadcastManager.registerReceiver(mDownloadFinishReceiver, downloadIntentFilter);
 
         mUploadFinishReceiver = new UploadFinishReceiver();
-        IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.getUploadFinishMessage());
+        IntentFilter uploadIntentFilter = new IntentFilter(FileUploadWorker.Companion.getUploadFinishMessage());
         localBroadcastManager.registerReceiver(mUploadFinishReceiver, uploadIntentFilter);
     }
 
@@ -445,7 +410,7 @@ public class PreviewImageActivity extends FileActivity implements
                 setDrawerIndicatorEnabled(false);
 
                 if (currentFile.isEncrypted() && !currentFile.isDown() &&
-                        !mPreviewImagePagerAdapter.pendingErrorAt(position)) {
+                    !mPreviewImagePagerAdapter.pendingErrorAt(position)) {
                     requestForDownload(currentFile);
                 }
 
@@ -540,7 +505,7 @@ public class PreviewImageActivity extends FileActivity implements
 
     public void toggleFullScreen() {
         boolean visible = (mFullScreenAnchorView.getSystemUiVisibility()
-                & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+            & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
 
         if (visible) {
             hideSystemUI(mFullScreenAnchorView);
@@ -581,23 +546,23 @@ public class PreviewImageActivity extends FileActivity implements
 
 
     @SuppressLint("InlinedApi")
-	private void hideSystemUI(View anchorView) {
+    private void hideSystemUI(View anchorView) {
         anchorView.setSystemUiVisibility(
-                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION         // hides NAVIGATION BAR; Android >= 4.0
-            |   View.SYSTEM_UI_FLAG_FULLSCREEN              // hides STATUS BAR;     Android >= 4.1
-            |   View.SYSTEM_UI_FLAG_IMMERSIVE               // stays interactive;    Android >= 4.4
-            |   View.SYSTEM_UI_FLAG_LAYOUT_STABLE           // draw full window;     Android >= 4.1
-            |   View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN       // draw full window;     Android >= 4.1
-            |   View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION  // draw full window;     Android >= 4.1
-        );
+            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION         // hides NAVIGATION BAR; Android >= 4.0
+                |   View.SYSTEM_UI_FLAG_FULLSCREEN              // hides STATUS BAR;     Android >= 4.1
+                |   View.SYSTEM_UI_FLAG_IMMERSIVE               // stays interactive;    Android >= 4.4
+                |   View.SYSTEM_UI_FLAG_LAYOUT_STABLE           // draw full window;     Android >= 4.1
+                |   View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN       // draw full window;     Android >= 4.1
+                |   View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION  // draw full window;     Android >= 4.1
+                                        );
     }
 
     @SuppressLint("InlinedApi")
     private void showSystemUI(View anchorView) {
         anchorView.setSystemUiVisibility(
-                View.SYSTEM_UI_FLAG_LAYOUT_STABLE           // draw full window;     Android >= 4.1
-            |   View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN       // draw full window;     Android >= 4.1
-            |   View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION  // draw full window;     Android >= 4.
-        );
+            View.SYSTEM_UI_FLAG_LAYOUT_STABLE           // draw full window;     Android >= 4.1
+                |   View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN       // draw full window;     Android >= 4.1
+                |   View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION  // draw full window;     Android >= 4.
+                                        );
     }
 }

+ 1 - 1
app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaActivity.kt

@@ -62,8 +62,8 @@ import androidx.media3.ui.PlayerView
 import com.nextcloud.client.account.User
 import com.nextcloud.client.account.UserAccountManager
 import com.nextcloud.client.di.Injectable
-import com.nextcloud.client.files.downloader.FileDownloadHelper
 import com.nextcloud.client.jobs.BackgroundJobManager
+import com.nextcloud.client.jobs.download.FileDownloadHelper
 import com.nextcloud.client.media.ExoplayerListener
 import com.nextcloud.client.media.NextcloudExoPlayer.createNextcloudExoplayer
 import com.nextcloud.client.media.PlayerServiceConnection

+ 1 - 1
app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -53,8 +53,8 @@ import android.view.ViewGroup;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.di.Injectable;
-import com.nextcloud.client.files.downloader.FileDownloadHelper;
 import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.download.FileDownloadHelper;
 import com.nextcloud.client.media.ExoplayerListener;
 import com.nextcloud.client.media.NextcloudExoPlayer;
 import com.nextcloud.client.media.PlayerServiceConnection;

+ 8 - 9
app/src/main/java/com/owncloud/android/utils/FilesSyncHelper.java

@@ -34,6 +34,8 @@ import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.device.BatteryStatus;
 import com.nextcloud.client.device.PowerManagementService;
 import com.nextcloud.client.jobs.BackgroundJobManager;
+import com.nextcloud.client.jobs.upload.FileUploadHelper;
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.network.ConnectivityService;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.datamodel.FilesystemDataProvider;
@@ -42,7 +44,7 @@ import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.UploadsStorageManager;
 import com.owncloud.android.db.OCUpload;
-import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.db.UploadResult;
 import com.owncloud.android.lib.common.utils.Log_OC;
 
 import org.lukhnos.nnio.file.FileVisitResult;
@@ -65,8 +67,6 @@ public final class FilesSyncHelper {
 
     public static final String GLOBAL = "global";
 
-    public static final int ContentSyncJobId = 315;
-
     private FilesSyncHelper() {
         // utility class -> private constructor
     }
@@ -174,13 +174,14 @@ public final class FilesSyncHelper {
                                            final UserAccountManager accountManager,
                                            final ConnectivityService connectivityService,
                                            final PowerManagementService powerManagementService) {
-        final Context context = MainApp.getAppContext();
-
         boolean accountExists;
 
         boolean whileChargingOnly = true;
         boolean useWifiOnly = true;
 
+        // Make all in progress downloads failed to restart upload worker
+        uploadsStorageManager.failInProgressUploads(UploadResult.SERVICE_INTERRUPTED);
+
         OCUpload[] failedUploads = uploadsStorageManager.getFailedUploads();
 
         for (OCUpload failedUpload : failedUploads) {
@@ -229,13 +230,11 @@ public final class FilesSyncHelper {
 
         new Thread(() -> {
             if (connectivityService.getConnectivity().isConnected()) {
-                FileUploader.retryFailedUploads(
-                    context,
+                FileUploadHelper.Companion.instance().retryFailedUploads(
                     uploadsStorageManager,
                     connectivityService,
                     accountManager,
-                    powerManagementService
-                                               );
+                    powerManagementService);
             }
         }).start();
     }

+ 0 - 156
app/src/main/java/com/owncloud/android/utils/FilesUploadHelper.kt

@@ -1,156 +0,0 @@
-/*
- *
- * Nextcloud Android client application
- *
- * @author Tobias Kaminsky
- * Copyright (C) 2022 Tobias Kaminsky
- * Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
- */
-
-package com.owncloud.android.utils
-
-import com.nextcloud.client.account.User
-import com.nextcloud.client.jobs.BackgroundJobManager
-import com.owncloud.android.MainApp
-import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.datamodel.UploadsStorageManager
-import com.owncloud.android.db.OCUpload
-import com.owncloud.android.files.services.FileUploader.FileUploaderBinder
-import com.owncloud.android.files.services.NameCollisionPolicy
-import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
-import com.owncloud.android.lib.common.utils.Log_OC
-import javax.inject.Inject
-
-class FilesUploadHelper {
-    @Inject
-    lateinit var backgroundJobManager: BackgroundJobManager
-
-    @Inject
-    lateinit var uploadsStorageManager: UploadsStorageManager
-
-    init {
-        MainApp.getAppComponent().inject(this)
-    }
-
-    @Suppress("LongParameterList")
-    fun uploadNewFiles(
-        user: User,
-        localPaths: Array<String>,
-        remotePaths: Array<String>,
-        createRemoteFolder: Boolean,
-        createdBy: Int,
-        requiresWifi: Boolean,
-        requiresCharging: Boolean,
-        nameCollisionPolicy: NameCollisionPolicy,
-        localBehavior: Int
-    ) {
-        val uploads = localPaths.mapIndexed { index, localPath ->
-            OCUpload(localPath, remotePaths[index], user.accountName).apply {
-                this.nameCollisionPolicy = nameCollisionPolicy
-                isUseWifiOnly = requiresWifi
-                isWhileChargingOnly = requiresCharging
-                uploadStatus = UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS
-                this.createdBy = createdBy
-                isCreateRemoteFolder = createRemoteFolder
-                localAction = localBehavior
-            }
-        }
-        uploadsStorageManager.storeUploads(uploads)
-        backgroundJobManager.startFilesUploadJob(user)
-    }
-
-    fun cancelFileUpload(remotePath: String, user: User) {
-        // need to update now table in mUploadsStorageManager,
-        // since the operation will not get to be run by FileUploader#uploadFile
-        uploadsStorageManager.removeUpload(user.accountName, remotePath)
-
-        restartUploadJob(user)
-    }
-
-    fun restartUploadJob(user: User) {
-        backgroundJobManager.cancelFilesUploadJob(user)
-        backgroundJobManager.startFilesUploadJob(user)
-    }
-
-    fun uploadUpdatedFile(
-        user: User,
-        existingFiles: Array<OCFile>,
-        behaviour: Int,
-        nameCollisionPolicy: NameCollisionPolicy
-    ) {
-        Log_OC.d(this, "upload updated file")
-
-        val uploads = existingFiles.map { file ->
-            OCUpload(file, user).apply {
-                fileSize = file.fileLength
-                this.nameCollisionPolicy = nameCollisionPolicy
-                isCreateRemoteFolder = true
-                this.localAction = behaviour
-                isUseWifiOnly = false
-                isWhileChargingOnly = false
-                uploadStatus = UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS
-            }
-        }
-        uploadsStorageManager.storeUploads(uploads)
-        backgroundJobManager.startFilesUploadJob(user)
-    }
-
-    fun retryUpload(upload: OCUpload, user: User) {
-        Log_OC.d(this, "retry upload")
-
-        upload.uploadStatus = UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS
-        uploadsStorageManager.updateUpload(upload)
-
-        backgroundJobManager.startFilesUploadJob(user)
-    }
-
-    fun addDatatransferProgressListener(
-        listener: OnDatatransferProgressListener,
-        targetKey: String
-    ) {
-        mBoundListeners[targetKey] = listener
-    }
-
-    fun removeDatatransferProgressListener(
-        listener: OnDatatransferProgressListener,
-        targetKey: String
-    ) {
-        if (mBoundListeners[targetKey] === listener) {
-            mBoundListeners.remove(targetKey)
-        }
-    }
-
-    companion object Progress {
-        val mBoundListeners = HashMap<String, OnDatatransferProgressListener>()
-
-        fun onTransferProgress(
-            accountName: String?,
-            remotePath: String?,
-            progressRate: Long,
-            totalTransferredSoFar: Long,
-            totalToTransfer: Long,
-            fileName: String?
-        ) {
-            if (accountName == null || remotePath == null) return
-
-            val key: String =
-                FileUploaderBinder.buildRemoteName(accountName, remotePath)
-            val boundListener = mBoundListeners[key]
-
-            boundListener?.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName)
-        }
-    }
-}

+ 3 - 3
app/src/main/java/third_parties/sufficientlysecure/SaveCalendar.java

@@ -44,9 +44,9 @@ import android.widget.EditText;
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.files.Request;
 import com.nextcloud.client.files.UploadRequest;
-import com.nextcloud.client.files.transfer.TransferManagerConnection;
-import com.nextcloud.client.files.upload.PostUploadAction;
-import com.nextcloud.client.files.upload.UploadTrigger;
+import com.nextcloud.client.jobs.transfer.TransferManagerConnection;
+import com.nextcloud.client.jobs.upload.PostUploadAction;
+import com.nextcloud.client.jobs.upload.UploadTrigger;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.OCFile;

+ 1 - 1
app/src/main/res/values/setup.xml

@@ -89,7 +89,7 @@
     <string name="syncedFolder_remote_folder">@string/auto_upload_path</string>
     <bool name="syncedFolder_light_on_charging">false</bool>
     <bool name="syncedFolder_light_use_subfolders">false</bool>
-    <string name="syncedFolder_light_upload_behaviour">FileUploader.LOCAL_BEHAVIOUR_FORGET</string>
+    <string name="syncedFolder_light_upload_behaviour">FilesUploadWorker.LOCAL_BEHAVIOUR_FORGET</string>
 
     <!--Destination mail for sending log files -->
     <string name="mail_logger"></string>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -141,6 +141,7 @@
     <string name="upload_chooser_title">Upload from…</string>
     <string name="uploader_info_dirname">Folder name</string>
     <string name="uploader_upload_in_progress_ticker">Uploading…</string>
+    <string name="uploader_upload_in_progress">%1$d%% %2$s</string>
     <string name="uploader_upload_in_progress_content">%1$d%% Uploading %2$s</string>
     <string name="uploader_upload_succeeded_content_single">%1$s uploaded</string>
     <string name="uploader_upload_failed_ticker">Upload failed</string>

+ 2 - 2
app/src/test/java/com/owncloud/android/ui/activity/SyncedFoldersActivityTest.java

@@ -22,10 +22,10 @@
 
 package com.owncloud.android.ui.activity;
 
+import com.nextcloud.client.jobs.upload.FileUploadWorker;
 import com.nextcloud.client.preferences.SubFolderRule;
 import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.SyncedFolderDisplayItem;
-import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.NameCollisionPolicy;
 
 import org.junit.Test;
@@ -169,7 +169,7 @@ public class SyncedFoldersActivityTest {
                                            true,
                                            true,
                                            "test@nextcloud.com",
-                                           FileUploader.LOCAL_BEHAVIOUR_MOVE,
+                                           FileUploadWorker.LOCAL_BEHAVIOUR_MOVE,
                                            NameCollisionPolicy.ASK_USER.serialize(),
                                            enabled,
                                            System.currentTimeMillis(),

+ 6 - 0
app/src/test/java/com/owncloud/android/ui/adapter/GalleryAdapterTest.kt

@@ -24,6 +24,7 @@ package com.owncloud.android.ui.adapter
 
 import android.content.Context
 import com.nextcloud.client.account.User
+import com.nextcloud.client.jobs.upload.FileUploadHelper
 import com.nextcloud.client.preferences.AppPreferences
 import com.owncloud.android.datamodel.FileDataStorageManager
 import com.owncloud.android.datamodel.GalleryItems
@@ -60,6 +61,9 @@ class GalleryAdapterTest {
     @Mock
     lateinit var storageManager: FileDataStorageManager
 
+    @Mock
+    lateinit var fileUploadHelper: FileUploadHelper
+
     @Mock
     lateinit var viewThemeUtils: ViewThemeUtils
 
@@ -78,6 +82,8 @@ class GalleryAdapterTest {
     @Test
     fun testItemCount() {
         whenever(transferServiceGetter.storageManager) doReturn storageManager
+        whenever(transferServiceGetter.fileUploaderHelper) doReturn fileUploadHelper
+
         val thumbnailSize = 50
 
         val sut = GalleryAdapter(