diff --git a/owncloudApp/src/main/java/com/owncloud/android/usecases/transfers/downloads/DownloadFileUseCase.kt b/owncloudApp/src/main/java/com/owncloud/android/usecases/transfers/downloads/DownloadFileUseCase.kt index a60909cb5f1..7c2505429b6 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/usecases/transfers/downloads/DownloadFileUseCase.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/usecases/transfers/downloads/DownloadFileUseCase.kt @@ -18,6 +18,7 @@ */ package com.owncloud.android.usecases.transfers.downloads +import androidx.work.BackoffPolicy import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.workDataOf @@ -29,8 +30,10 @@ import com.owncloud.android.extensions.getTagsForDownload import com.owncloud.android.usecases.transfers.MAXIMUM_NUMBER_OF_RETRIES import com.owncloud.android.usecases.transfers.TRANSFER_TAG_DOWNLOAD import com.owncloud.android.workers.DownloadFileWorker +import com.owncloud.android.workers.DownloadFileWorker.Companion.SECONDS_BACKOFF_DELAY import timber.log.Timber import java.util.UUID +import java.util.concurrent.TimeUnit /** * We will use [WorkManager] to perform downloads. @@ -93,6 +96,7 @@ class DownloadFileUseCase( ) val downloadFileWork = OneTimeWorkRequestBuilder() + .setBackoffCriteria(backoffPolicy = BackoffPolicy.LINEAR, backoffDelay = SECONDS_BACKOFF_DELAY, timeUnit = TimeUnit.MILLISECONDS) .setInputData(inputData) .addTag(ocFile.id.toString()) .addTag(accountName) diff --git a/owncloudApp/src/main/java/com/owncloud/android/usecases/transfers/uploads/UploadFileFromContentUriUseCase.kt b/owncloudApp/src/main/java/com/owncloud/android/usecases/transfers/uploads/UploadFileFromContentUriUseCase.kt index ab4a0475000..ab75d20f9ee 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/usecases/transfers/uploads/UploadFileFromContentUriUseCase.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/usecases/transfers/uploads/UploadFileFromContentUriUseCase.kt @@ -23,6 +23,7 @@ package com.owncloud.android.usecases.transfers.uploads import android.net.Uri +import androidx.work.BackoffPolicy import androidx.work.Constraints import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder @@ -32,7 +33,9 @@ import com.owncloud.android.domain.BaseUseCase import com.owncloud.android.domain.camerauploads.model.UploadBehavior import com.owncloud.android.workers.RemoveSourceFileWorker import com.owncloud.android.workers.UploadFileFromContentUriWorker +import com.owncloud.android.workers.UploadFileFromContentUriWorker.Companion.SECONDS_BACKOFF_DELAY import timber.log.Timber +import java.util.concurrent.TimeUnit class UploadFileFromContentUriUseCase( private val workManager: WorkManager @@ -58,6 +61,7 @@ class UploadFileFromContentUriUseCase( .build() val uploadFileFromContentUriWorker = OneTimeWorkRequestBuilder() + .setBackoffCriteria(backoffPolicy = BackoffPolicy.LINEAR, backoffDelay = SECONDS_BACKOFF_DELAY, timeUnit = TimeUnit.MILLISECONDS) .setInputData(inputDataUploadFileFromContentUriWorker) .setConstraints(constraints) .addTag(params.accountName) diff --git a/owncloudApp/src/main/java/com/owncloud/android/utils/ConnectivityUtils.java b/owncloudApp/src/main/java/com/owncloud/android/utils/ConnectivityUtils.java index e85621511b3..d8f6b7e2288 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/utils/ConnectivityUtils.java +++ b/owncloudApp/src/main/java/com/owncloud/android/utils/ConnectivityUtils.java @@ -2,7 +2,9 @@ * ownCloud Android client application * * @author David A. Velasco - * Copyright (C) 2017 ownCloud GmbH. + * @author Aitor Ballesteros Pavón + * + * Copyright (C) 2024 ownCloud GmbH. *

* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -21,6 +23,8 @@ import android.content.Context; import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; import timber.log.Timber; @@ -51,4 +55,24 @@ public static boolean isNetworkActive(Context context) { return (activeNetwork != null && activeNetwork.isConnectedOrConnecting()); } + + public static boolean isInternetAvailable(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + + Network network = connectivityManager.getActiveNetwork(); + if (network == null) return false; + + NetworkCapabilities activeNetwork = connectivityManager.getNetworkCapabilities(network); + if (activeNetwork == null) return false; + + if (activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return true; + } else if (activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return true; + } else if (activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return true; + } else { + return false; + } + } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt b/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt index 3d004608257..df4de6e9869 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/workers/DownloadFileWorker.kt @@ -34,6 +34,7 @@ import com.owncloud.android.data.executeRemoteOperation import com.owncloud.android.data.providers.LocalStorageProvider import com.owncloud.android.domain.exceptions.CancelledException import com.owncloud.android.domain.exceptions.LocalStorageNotMovedException +import com.owncloud.android.domain.exceptions.NetworkErrorException import com.owncloud.android.domain.exceptions.NoConnectionWithServerException import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.usecases.CleanConflictUseCase @@ -58,6 +59,7 @@ import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.errorhandling.ErrorMessageAdapter import com.owncloud.android.ui.preview.PreviewImageActivity import com.owncloud.android.ui.preview.PreviewImageFragment.Companion.canBePreviewed +import com.owncloud.android.utils.ConnectivityUtils.isNetworkActive import com.owncloud.android.utils.DOWNLOAD_NOTIFICATION_CHANNEL_ID import com.owncloud.android.utils.DOWNLOAD_NOTIFICATION_ID_DEFAULT import com.owncloud.android.utils.FileStorageUtils @@ -125,7 +127,17 @@ class DownloadFileWorker( notifyDownloadResult(null) } catch (throwable: Throwable) { Timber.e(throwable) - notifyDownloadResult(throwable) + val isNetworkError = throwable is NetworkErrorException || throwable is NoConnectionWithServerException + val shouldRetry = isNetworkError && isNetworkActive(appContext) + if (shouldRetry) { + if (runAttemptCount < MAX_RETRIES) { + Result.retry() + } else { + notifyDownloadResult(throwable) + } + } else { + notifyDownloadResult(throwable) + } } } @@ -358,6 +370,8 @@ class DownloadFileWorker( } companion object { + const val SECONDS_BACKOFF_DELAY = 10_000L + const val MAX_RETRIES = 2 const val KEY_PARAM_ACCOUNT = "KEY_PARAM_ACCOUNT" const val KEY_PARAM_FILE_ID = "KEY_PARAM_FILE_ID" const val WORKER_KEY_PROGRESS = "KEY_PROGRESS" diff --git a/owncloudApp/src/main/java/com/owncloud/android/workers/UploadFileFromContentUriWorker.kt b/owncloudApp/src/main/java/com/owncloud/android/workers/UploadFileFromContentUriWorker.kt index 120fc336caf..1c6af50de0a 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/workers/UploadFileFromContentUriWorker.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/workers/UploadFileFromContentUriWorker.kt @@ -36,6 +36,8 @@ import com.owncloud.android.data.providers.LocalStorageProvider import com.owncloud.android.domain.camerauploads.model.UploadBehavior import com.owncloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase import com.owncloud.android.domain.exceptions.LocalFileNotFoundException +import com.owncloud.android.domain.exceptions.NetworkErrorException +import com.owncloud.android.domain.exceptions.NoConnectionWithServerException import com.owncloud.android.domain.exceptions.UnauthorizedException import com.owncloud.android.domain.files.model.OCFile import com.owncloud.android.domain.files.usecases.GetWebDavUrlForSpaceUseCase @@ -58,6 +60,7 @@ import com.owncloud.android.lib.resources.files.chunks.ChunkedUploadFromFileSyst import com.owncloud.android.lib.resources.files.chunks.ChunkedUploadFromFileSystemOperation.Companion.CHUNK_SIZE import com.owncloud.android.lib.resources.files.services.implementation.OCChunkService import com.owncloud.android.presentation.authentication.AccountUtils +import com.owncloud.android.utils.ConnectivityUtils.isInternetAvailable import com.owncloud.android.utils.NotificationUtils import com.owncloud.android.utils.RemoteFileUtils.Companion.getAvailableRemotePath import com.owncloud.android.utils.SecurityUtils @@ -124,9 +127,22 @@ class UploadFileFromContentUriWorker( Result.success() } catch (throwable: Throwable) { Timber.e(throwable) - showNotification(throwable) - updateUploadsDatabaseWithResult(throwable) - Result.failure() + val isNetworkError = throwable is NetworkErrorException || throwable is NoConnectionWithServerException + val shouldRetry = isNetworkError && isInternetAvailable(appContext) + + if (shouldRetry) { + if (runAttemptCount < MAX_RETRIES) { + Result.retry() + } else { + showNotification(throwable) + updateUploadsDatabaseWithResult(throwable) + Result.failure() + } + } else { + showNotification(throwable) + updateUploadsDatabaseWithResult(throwable) + Result.failure() + } } } @@ -378,6 +394,8 @@ class UploadFileFromContentUriWorker( } companion object { + const val SECONDS_BACKOFF_DELAY = 10_000L + const val MAX_RETRIES = 2 const val KEY_PARAM_ACCOUNT_NAME = "KEY_PARAM_ACCOUNT_NAME" const val KEY_PARAM_BEHAVIOR = "KEY_PARAM_BEHAVIOR" const val KEY_PARAM_CONTENT_URI = "KEY_PARAM_CONTENT_URI"