From 2a855c3fbd77b615229ee381289381cfcb0ea8c9 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 6 Nov 2025 14:11:28 +0100 Subject: [PATCH 1/7] fix: handle ask user name collision policy Signed-off-by: alperozturk --- .../jobs/autoUpload/AutoUploadWorker.kt | 42 +++- .../notification/WorkerNotificationManager.kt | 4 + .../client/jobs/upload/FileUploadWorker.kt | 104 ++------- .../client/jobs/upload/FileUploaderIntents.kt | 56 ----- .../jobs/upload/UploadNotificationManager.kt | 79 +------ .../client/jobs/utils/SyncConflictManager.kt | 201 ++++++++++++++++++ 6 files changed, 265 insertions(+), 221 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index 2bef1d4bedd6..e0252fea6e2a 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -23,9 +23,11 @@ import com.nextcloud.client.database.entity.toUploadEntity import com.nextcloud.client.device.PowerManagementService import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.client.jobs.upload.FileUploadWorker +import com.nextcloud.client.jobs.utils.SyncConflictManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.SubFolderRule import com.nextcloud.utils.ForegroundServiceHelper +import com.nextcloud.utils.extensions.isFileSpecificError import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.ArbitraryDataProviderImpl @@ -38,6 +40,7 @@ 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.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.ui.activity.SettingsActivity @@ -74,6 +77,7 @@ class AutoUploadWorker( private const val CHANNEL_ID = NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD private const val NOTIFICATION_ID = 266 + private const val NOTIFICATION_ERROR_ID = 813 } private val helper = AutoUploadHelper() @@ -137,7 +141,9 @@ class AutoUploadWorker( setForeground(foregroundInfo) } - private fun createNotification(title: String): Notification = NotificationCompat.Builder(context, CHANNEL_ID) + private fun notificationBuilder(): NotificationCompat.Builder = NotificationCompat.Builder(context, CHANNEL_ID) + + private fun createNotification(title: String): Notification = notificationBuilder() .setContentTitle(title) .setSmallIcon(R.drawable.uploads) .setOngoing(true) @@ -313,6 +319,7 @@ class AutoUploadWorker( val result = operation.execute(client) uploadsStorageManager.updateStatus(uploadEntity, result.isSuccess) + handleUploadResult(operation, result) if (result.isSuccess) { repository.markFileAsUploaded(localPath, syncedFolder) @@ -472,9 +479,34 @@ class AutoUploadWorker( } private fun getUploadAction(action: String): Int = when (action) { - "LOCAL_BEHAVIOUR_FORGET" -> FileUploadWorker.Companion.LOCAL_BEHAVIOUR_FORGET - "LOCAL_BEHAVIOUR_MOVE" -> FileUploadWorker.Companion.LOCAL_BEHAVIOUR_MOVE - "LOCAL_BEHAVIOUR_DELETE" -> FileUploadWorker.Companion.LOCAL_BEHAVIOUR_DELETE - else -> FileUploadWorker.Companion.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 + } + + private fun handleUploadResult(operation: UploadFileOperation, result: RemoteOperationResult) { + Log_OC.d(TAG, "handleUploadResult with resultCode: " + result.code) + val notificationBuilder = notificationBuilder() + + val notification = SyncConflictManager.getNotification( + context, + notificationBuilder, + operation, + result, + notifyOnSameFileExists = { + operation.handleLocalBehaviour() + } + ) ?: return + + if (result.code.isFileSpecificError()) { + notificationManager.notify( + NotificationUtils.createUploadNotificationTag(operation.file), + NOTIFICATION_ERROR_ID, + notification + ) + } else { + notificationManager.notify(NOTIFICATION_ID, notification) + } } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt index e427142decda..36444380fc47 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt @@ -46,6 +46,10 @@ open class WorkerNotificationManager( notificationManager.notify(id, notificationBuilder.build()) } + fun showNotification(notification: Notification) { + notificationManager.notify(id, notification) + } + @Suppress("MagicNumber") fun setProgress(percent: Int, progressText: String?, indeterminate: Boolean) { notificationBuilder.run { diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index 093a3a9b86ff..0937941f6934 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -8,7 +8,6 @@ package com.nextcloud.client.jobs.upload import android.app.Notification -import android.app.PendingIntent import android.content.Context import androidx.core.app.NotificationCompat import androidx.localbroadcastmanager.content.LocalBroadcastManager @@ -19,12 +18,14 @@ 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.jobs.utils.SyncConflictManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.model.WorkerState import com.nextcloud.model.WorkerStateLiveData import com.nextcloud.utils.ForegroundServiceHelper import com.nextcloud.utils.extensions.getPercent +import com.nextcloud.utils.extensions.isFileSpecificError import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager @@ -41,7 +42,6 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.ui.notifications.NotificationUtils -import com.owncloud.android.utils.ErrorMessageAdapter import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ensureActive @@ -349,97 +349,33 @@ class FileUploadWorker( private fun cleanupUploadProcess(result: RemoteOperationResult, uploadFileOperation: UploadFileOperation) { if (!isStopped || !result.isCancelled) { uploadsStorageManager.updateDatabaseUploadResult(result, uploadFileOperation) - notifyUploadResult(uploadFileOperation, result) + handleUploadResult(uploadFileOperation, result) } } - @Suppress("ReturnCount", "LongMethod") - private fun notifyUploadResult( - uploadFileOperation: UploadFileOperation, - uploadResult: RemoteOperationResult - ) { - Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.code) + private fun handleUploadResult(operation: UploadFileOperation, result: RemoteOperationResult) { + Log_OC.d(TAG, "handleUploadResult with resultCode: " + result.code) val showSameFileAlreadyExistsNotification = inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false) - if (uploadResult.isSuccess) { - notificationManager.dismissOldErrorNotification(uploadFileOperation) - return - } - - if (uploadResult.isCancelled) { - return - } - - // Only notify if it is not same file on remote that causes conflict - if (uploadResult.code == ResultCode.SYNC_CONFLICT && - FileUploadHelper().isSameFileOnRemote( - uploadFileOperation.user, - File(uploadFileOperation.storagePath), - uploadFileOperation.remotePath, - context - ) - ) { - if (showSameFileAlreadyExistsNotification) { - notificationManager.showSameFileAlreadyExistsNotification(uploadFileOperation.fileName) - } - - uploadFileOperation.handleLocalBehaviour() - 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 - } - - if (uploadResult.code == ResultCode.USER_CANCELLED) { - return - } - - notificationManager.run { - val errorMessage = ErrorMessageAdapter.getErrorCauseMessage( - uploadResult, - uploadFileOperation, - context.resources - ) - - 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 - } + val notification = SyncConflictManager.getNotification( + context, + notificationManager.notificationBuilder, + operation, + result, + notifyOnSameFileExists = { + if (showSameFileAlreadyExistsNotification) { + notificationManager.showSameFileAlreadyExistsNotification(operation.fileName) + } - val cancelUploadActionIntent = if (conflictResolveIntent != null) { - intents.cancelUploadActionIntent(uploadFileOperation) - } else { - null + operation.handleLocalBehaviour() } + ) ?: return - notifyForFailedResult( - uploadFileOperation, - uploadResult.code, - conflictResolveIntent, - cancelUploadActionIntent, - credentialIntent, - errorMessage - ) + if (result.code.isFileSpecificError()) { + notificationManager.showNewNotification(operation, notification) + } else { + notificationManager.showNotification(notification) } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt index b99310fa1e09..ad0101aa5d59 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploaderIntents.kt @@ -10,10 +10,7 @@ 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.operations.UploadFileOperation -import com.owncloud.android.ui.activity.ConflictsResolveActivity.Companion.createIntent import com.owncloud.android.ui.activity.UploadListActivity import java.security.SecureRandom @@ -39,23 +36,6 @@ class FileUploaderIntents(private val context: Context) { ) } - 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 notificationStartIntent(operation: UploadFileOperation?): PendingIntent { val intent = UploadListActivity.createIntent( operation?.file, @@ -71,40 +51,4 @@ class FileUploaderIntents(private val context: Context) { 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 - ) - } - } - - fun cancelUploadActionIntent(uploadFileOperation: UploadFileOperation): PendingIntent { - val intent = Intent(context, FileUploadBroadcastReceiver::class.java).apply { - putExtra(FileUploadBroadcastReceiver.UPLOAD_ID, uploadFileOperation.ocUploadId) - putExtra(FileUploadBroadcastReceiver.REMOTE_PATH, uploadFileOperation.file.remotePath) - putExtra(FileUploadBroadcastReceiver.STORAGE_PATH, uploadFileOperation.file.storagePath) - } - - return PendingIntent.getBroadcast( - context, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt index 4ba76bfab738..96b127993991 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt @@ -7,13 +7,12 @@ */ package com.nextcloud.client.jobs.upload +import android.app.Notification import android.app.PendingIntent import android.content.Context import com.nextcloud.client.jobs.notification.WorkerNotificationManager -import com.nextcloud.utils.extensions.isFileSpecificError import com.nextcloud.utils.numberFormatter.NumberFormatter import com.owncloud.android.R -import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.ui.notifications.NotificationUtils import com.owncloud.android.utils.theme.ViewThemeUtils @@ -77,83 +76,11 @@ class UploadNotificationManager(private val context: Context, viewThemeUtils: Vi dismissOldErrorNotification(currentOperation) } - fun notifyForFailedResult( - uploadFileOperation: UploadFileOperation, - resultCode: RemoteOperationResult.ResultCode, - conflictsResolveIntent: PendingIntent?, - cancelUploadActionIntent: PendingIntent?, - credentialIntent: PendingIntent?, - errorMessage: String - ) { - if (uploadFileOperation.isMissingPermissionThrown) { - return - } - - val textId = getFailedResultTitleId(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 - ) - } - - cancelUploadActionIntent?.let { - addAction( - R.drawable.ic_delete, - R.string.upload_list_cancel_upload, - cancelUploadActionIntent - ) - } - - credentialIntent?.let { - setContentIntent(it) - } - - setContentText(errorMessage) - } - - if (resultCode.isFileSpecificError()) { - showNewNotification(uploadFileOperation) - } else { - showNotification() - } - } - - private fun getFailedResultTitleId(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 - ) - } - - private fun showNewNotification(operation: UploadFileOperation) { + fun showNewNotification(operation: UploadFileOperation, notification: Notification) { notificationManager.notify( NotificationUtils.createUploadNotificationTag(operation.file), FileUploadWorker.NOTIFICATION_ERROR_ID, - notificationBuilder.build() + notification ) } diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt new file mode 100644 index 000000000000..410dc0199cd6 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt @@ -0,0 +1,201 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.jobs.utils + +import android.app.Notification +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.core.app.NotificationCompat +import com.nextcloud.client.jobs.upload.FileUploadBroadcastReceiver +import com.nextcloud.client.jobs.upload.FileUploadHelper +import com.owncloud.android.R +import com.owncloud.android.authentication.AuthenticatorActivity +import com.owncloud.android.lib.common.operations.RemoteOperationResult +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.utils.ErrorMessageAdapter +import java.io.File +import java.security.SecureRandom + +object SyncConflictManager { + fun getNotification( + context: Context, + notificationBuilder: NotificationCompat.Builder, + operation: UploadFileOperation, + result: RemoteOperationResult, + notifyOnSameFileExists: () -> Unit + ): Notification? { + if (!handle(context, operation, result, notifyOnSameFileExists)) { + return null + } + + val errorMessage = ErrorMessageAdapter.getErrorCauseMessage( + result, + operation, + context.resources + ) + + val conflictsResolveIntent = if (result.code == ResultCode.SYNC_CONFLICT) { + conflictResolveActionIntents(context, operation) + } else { + null + } + + val credentialIntent: PendingIntent? = if (result.code == ResultCode.UNAUTHORIZED) { + credentialIntent(context, operation) + } else { + null + } + + val cancelUploadActionIntent = if (conflictsResolveIntent != null) { + cancelUploadActionIntent(context, operation) + } else { + null + } + + val textId = getFailedResultTitleId(result.code) + + return 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, + context.getString(R.string.upload_list_resolve_conflict), + it + ) + } + + cancelUploadActionIntent?.let { + addAction( + R.drawable.ic_delete, + context.getString(R.string.upload_list_cancel_upload), + cancelUploadActionIntent + ) + } + + credentialIntent?.let { + setContentIntent(it) + } + + setContentText(errorMessage) + }.build() + } + + private fun getFailedResultTitleId(resultCode: ResultCode): Int { + val needsToUpdateCredentials = (resultCode == ResultCode.UNAUTHORIZED) + + return if (needsToUpdateCredentials) { + R.string.uploader_upload_failed_credentials_error + } else if (resultCode == ResultCode.SYNC_CONFLICT) { + R.string.uploader_upload_failed_sync_conflict_error + } else { + R.string.uploader_upload_failed_ticker + } + } + + private fun credentialIntent(context: Context, 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 + ) + } + + private 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 + ) + } + } + + private fun cancelUploadActionIntent(context: Context, operation: UploadFileOperation): PendingIntent { + val intent = Intent(context, FileUploadBroadcastReceiver::class.java).apply { + putExtra(FileUploadBroadcastReceiver.UPLOAD_ID, operation.ocUploadId) + putExtra(FileUploadBroadcastReceiver.REMOTE_PATH, operation.file.remotePath) + putExtra(FileUploadBroadcastReceiver.STORAGE_PATH, operation.file.storagePath) + } + + return PendingIntent.getBroadcast( + context, + 0, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + } + + private fun handle( + context: Context, + operation: UploadFileOperation, + result: RemoteOperationResult, + notifyOnSameFileExists: () -> Unit + ): Boolean { + if (result.isSuccess || result.isCancelled || operation.isMissingPermissionThrown) { + return false + } + + if (result.code == ResultCode.SYNC_CONFLICT && + FileUploadHelper.instance().isSameFileOnRemote( + operation.user, + File(operation.storagePath), + operation.remotePath, + context + ) + ) { + notifyOnSameFileExists() + return false + } + + val notDelayed = result.code !in setOf( + ResultCode.DELAYED_FOR_WIFI, + ResultCode.DELAYED_FOR_CHARGING, + ResultCode.DELAYED_IN_POWER_SAVE_MODE + ) + + val isValidFile = result.code !in setOf( + ResultCode.LOCAL_FILE_NOT_FOUND, + ResultCode.LOCK_FAILED + ) + + return !(!notDelayed || !isValidFile || result.code == ResultCode.USER_CANCELLED) + } +} From ee5f61501c3719cbad551825652074474c784483 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 6 Nov 2025 14:46:03 +0100 Subject: [PATCH 2/7] simplify SyncConflictManager Signed-off-by: alperozturk --- .../client/jobs/utils/SyncConflictManager.kt | 142 ++++++------------ 1 file changed, 46 insertions(+), 96 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt index 410dc0199cd6..bf969ca0f4ed 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt @@ -20,7 +20,7 @@ import com.owncloud.android.authentication.AuthenticatorActivity import com.owncloud.android.lib.common.operations.RemoteOperationResult 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.ConflictsResolveActivity import com.owncloud.android.utils.ErrorMessageAdapter import java.io.File import java.security.SecureRandom @@ -28,92 +28,55 @@ import java.security.SecureRandom object SyncConflictManager { fun getNotification( context: Context, - notificationBuilder: NotificationCompat.Builder, + builder: NotificationCompat.Builder, operation: UploadFileOperation, result: RemoteOperationResult, notifyOnSameFileExists: () -> Unit ): Notification? { - if (!handle(context, operation, result, notifyOnSameFileExists)) { - return null - } - - val errorMessage = ErrorMessageAdapter.getErrorCauseMessage( - result, - operation, - context.resources - ) + if (!shouldShowConflictDialog(context, operation, result, notifyOnSameFileExists)) return null - val conflictsResolveIntent = if (result.code == ResultCode.SYNC_CONFLICT) { - conflictResolveActionIntents(context, operation) - } else { - null - } + val textId = result.code.toFailedResultTitleId() + val errorMessage = ErrorMessageAdapter.getErrorCauseMessage(result, operation, context.resources) - val credentialIntent: PendingIntent? = if (result.code == ResultCode.UNAUTHORIZED) { - credentialIntent(context, operation) - } else { - null - } - - val cancelUploadActionIntent = if (conflictsResolveIntent != null) { - cancelUploadActionIntent(context, operation) - } else { - null - } - - val textId = getFailedResultTitleId(result.code) - - return notificationBuilder.run { + return builder.apply { setTicker(context.getString(textId)) setContentTitle(context.getString(textId)) + setContentText(errorMessage) setAutoCancel(false) setOngoing(false) setProgress(0, 0, false) clearActions() - conflictsResolveIntent?.let { + result.code.takeIf { it == ResultCode.SYNC_CONFLICT }?.let { addAction( R.drawable.ic_cloud_upload, context.getString(R.string.upload_list_resolve_conflict), - it + conflictResolvePendingIntent(context, operation) ) - } - - cancelUploadActionIntent?.let { addAction( R.drawable.ic_delete, context.getString(R.string.upload_list_cancel_upload), - cancelUploadActionIntent + cancelUploadPendingIntent(context, operation) ) } - credentialIntent?.let { - setContentIntent(it) + result.code.takeIf { it == ResultCode.UNAUTHORIZED }?.let { + setContentIntent(credentialPendingIntent(context, operation)) } - - setContentText(errorMessage) }.build() } - private fun getFailedResultTitleId(resultCode: ResultCode): Int { - val needsToUpdateCredentials = (resultCode == ResultCode.UNAUTHORIZED) - - return if (needsToUpdateCredentials) { - R.string.uploader_upload_failed_credentials_error - } else if (resultCode == ResultCode.SYNC_CONFLICT) { - R.string.uploader_upload_failed_sync_conflict_error - } else { - R.string.uploader_upload_failed_ticker - } + private fun ResultCode.toFailedResultTitleId(): Int = when (this) { + ResultCode.UNAUTHORIZED -> R.string.uploader_upload_failed_credentials_error + ResultCode.SYNC_CONFLICT -> R.string.uploader_upload_failed_sync_conflict_error + else -> R.string.uploader_upload_failed_ticker } - private fun credentialIntent(context: Context, operation: UploadFileOperation): PendingIntent { + private fun credentialPendingIntent(context: Context, 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) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or Intent.FLAG_FROM_BACKGROUND) } return PendingIntent.getActivity( @@ -124,31 +87,21 @@ object SyncConflictManager { ) } - private fun conflictResolveActionIntents( - context: Context, - uploadFileOperation: UploadFileOperation - ): PendingIntent { - val intent = createIntent( - uploadFileOperation.file, - uploadFileOperation.user, - uploadFileOperation.ocUploadId, + private fun conflictResolvePendingIntent(context: Context, operation: UploadFileOperation): PendingIntent { + val intent = ConflictsResolveActivity.createIntent( + operation.file, + operation.user, + operation.ocUploadId, Intent.FLAG_ACTIVITY_CLEAR_TOP, context ) + val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + PendingIntent.FLAG_MUTABLE else PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE - 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 - ) - } + return PendingIntent.getActivity(context, SecureRandom().nextInt(), intent, flags) } - private fun cancelUploadActionIntent(context: Context, operation: UploadFileOperation): PendingIntent { + private fun cancelUploadPendingIntent(context: Context, operation: UploadFileOperation): PendingIntent { val intent = Intent(context, FileUploadBroadcastReceiver::class.java).apply { putExtra(FileUploadBroadcastReceiver.UPLOAD_ID, operation.ocUploadId) putExtra(FileUploadBroadcastReceiver.REMOTE_PATH, operation.file.remotePath) @@ -157,45 +110,42 @@ object SyncConflictManager { return PendingIntent.getBroadcast( context, - 0, + operation.file.fileId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) } - private fun handle( + private fun shouldShowConflictDialog( context: Context, operation: UploadFileOperation, result: RemoteOperationResult, notifyOnSameFileExists: () -> Unit ): Boolean { - if (result.isSuccess || result.isCancelled || operation.isMissingPermissionThrown) { + if (result.isSuccess || + result.isCancelled || + result.code == ResultCode.USER_CANCELLED || + operation.isMissingPermissionThrown + ) { return false } - if (result.code == ResultCode.SYNC_CONFLICT && - FileUploadHelper.instance().isSameFileOnRemote( - operation.user, - File(operation.storagePath), - operation.remotePath, - context - ) - ) { + val isSameFileOnRemote = FileUploadHelper.instance().isSameFileOnRemote( + operation.user, + File(operation.storagePath), + operation.remotePath, + context + ) + + if (result.code == ResultCode.SYNC_CONFLICT && isSameFileOnRemote) { notifyOnSameFileExists() return false } - val notDelayed = result.code !in setOf( - ResultCode.DELAYED_FOR_WIFI, - ResultCode.DELAYED_FOR_CHARGING, - ResultCode.DELAYED_IN_POWER_SAVE_MODE - ) - - val isValidFile = result.code !in setOf( - ResultCode.LOCAL_FILE_NOT_FOUND, - ResultCode.LOCK_FAILED - ) + val delayedCodes = + setOf(ResultCode.DELAYED_FOR_WIFI, ResultCode.DELAYED_FOR_CHARGING, ResultCode.DELAYED_IN_POWER_SAVE_MODE) + val invalidCodes = setOf(ResultCode.LOCAL_FILE_NOT_FOUND, ResultCode.LOCK_FAILED) - return !(!notDelayed || !isValidFile || result.code == ResultCode.USER_CANCELLED) + return result.code !in delayedCodes && result.code !in invalidCodes } } From ccc03ee312b8a6d653a9399c61acc6322eded200 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 6 Nov 2025 17:42:06 +0100 Subject: [PATCH 3/7] fix pending intents Signed-off-by: alperozturk --- .../client/jobs/utils/SyncConflictManager.kt | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt index bf969ca0f4ed..ab885f8a442c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt @@ -11,7 +11,6 @@ import android.app.Notification import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.os.Build import androidx.core.app.NotificationCompat import com.nextcloud.client.jobs.upload.FileUploadBroadcastReceiver import com.nextcloud.client.jobs.upload.FileUploadHelper @@ -76,14 +75,20 @@ object SyncConflictManager { 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 or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or Intent.FLAG_FROM_BACKGROUND) + addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or + Intent.FLAG_FROM_BACKGROUND + ) + setClass(context, AuthenticatorActivity::class.java) + setPackage(context.packageName) } return PendingIntent.getActivity( context, System.currentTimeMillis().toInt(), intent, - PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_IMMUTABLE ) } @@ -94,11 +99,12 @@ object SyncConflictManager { operation.ocUploadId, Intent.FLAG_ACTIVITY_CLEAR_TOP, context - ) - val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - PendingIntent.FLAG_MUTABLE else PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE + ).apply { + setClass(context, ConflictsResolveActivity::class.java) + setPackage(context.packageName) + } - return PendingIntent.getActivity(context, SecureRandom().nextInt(), intent, flags) + return PendingIntent.getActivity(context, SecureRandom().nextInt(), intent, PendingIntent.FLAG_IMMUTABLE) } private fun cancelUploadPendingIntent(context: Context, operation: UploadFileOperation): PendingIntent { @@ -106,6 +112,8 @@ object SyncConflictManager { putExtra(FileUploadBroadcastReceiver.UPLOAD_ID, operation.ocUploadId) putExtra(FileUploadBroadcastReceiver.REMOTE_PATH, operation.file.remotePath) putExtra(FileUploadBroadcastReceiver.STORAGE_PATH, operation.file.storagePath) + setClass(context, FileUploadBroadcastReceiver::class.java) + setPackage(context.packageName) } return PendingIntent.getBroadcast( @@ -116,6 +124,7 @@ object SyncConflictManager { ) } + @Suppress("ReturnCount", "ComplexCondition") private fun shouldShowConflictDialog( context: Context, operation: UploadFileOperation, From caa5adaee6522bc5dc43155b14baa54844333d9a Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 13 Nov 2025 16:45:11 +0100 Subject: [PATCH 4/7] fix error handling Signed-off-by: alperozturk --- .../client/jobs/BackgroundJobFactory.kt | 3 +- .../AutoUploadNotificationManager.kt | 25 ++++++ .../jobs/autoUpload/AutoUploadWorker.kt | 77 ++++++----------- .../notification/WorkerNotificationManager.kt | 4 + .../client/jobs/upload/FileUploadWorker.kt | 84 +++++++++---------- .../jobs/upload/UploadNotificationManager.kt | 9 -- ...r.kt => UploadErrorNotificationManager.kt} | 56 ++++++++++--- .../RemoteOperationResultExtensions.kt | 1 + .../ui/activity/ConflictsResolveActivity.kt | 12 +-- 9 files changed, 146 insertions(+), 125 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadNotificationManager.kt rename app/src/main/java/com/nextcloud/client/jobs/utils/{SyncConflictManager.kt => UploadErrorNotificationManager.kt} (77%) diff --git a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt index 401fee165ac3..b7d06ee2d9d3 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/BackgroundJobFactory.kt @@ -179,7 +179,8 @@ class BackgroundJobFactory @Inject constructor( powerManagementService = powerManagementService, syncedFolderProvider = syncedFolderProvider, backgroundJobManager = backgroundJobManager.get(), - repository = FileSystemRepository(dao = database.fileSystemDao()) + repository = FileSystemRepository(dao = database.fileSystemDao()), + viewThemeUtils = viewThemeUtils.get() ) private fun createOfflineSyncWork(context: Context, params: WorkerParameters): OfflineSyncWork = OfflineSyncWork( diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadNotificationManager.kt new file mode 100644 index 000000000000..70c1a5648cbf --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadNotificationManager.kt @@ -0,0 +1,25 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2025 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.jobs.autoUpload + +import android.content.Context +import com.nextcloud.client.jobs.notification.WorkerNotificationManager +import com.owncloud.android.R +import com.owncloud.android.ui.notifications.NotificationUtils +import com.owncloud.android.utils.theme.ViewThemeUtils + +class AutoUploadNotificationManager(private val context: Context, viewThemeUtils: ViewThemeUtils, id: Int) : + WorkerNotificationManager( + id, + context, + viewThemeUtils, + tickerId = R.string.foreground_service_upload, + channelId = NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD + ) { + +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index e0252fea6e2a..fc8b572661ac 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -8,10 +8,8 @@ package com.nextcloud.client.jobs.autoUpload import android.app.Notification -import android.app.NotificationManager import android.content.Context import android.content.res.Resources -import androidx.core.app.NotificationCompat import androidx.exifinterface.media.ExifInterface import androidx.work.CoroutineWorker import androidx.work.WorkerParameters @@ -22,12 +20,12 @@ import com.nextcloud.client.database.entity.toOCUpload import com.nextcloud.client.database.entity.toUploadEntity 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.jobs.utils.SyncConflictManager +import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.SubFolderRule import com.nextcloud.utils.ForegroundServiceHelper -import com.nextcloud.utils.extensions.isFileSpecificError import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.ArbitraryDataProviderImpl @@ -40,14 +38,13 @@ 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.operations.RemoteOperationResult import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.ui.activity.SettingsActivity -import com.owncloud.android.ui.notifications.NotificationUtils import com.owncloud.android.utils.FileStorageUtils import com.owncloud.android.utils.FilesSyncHelper import com.owncloud.android.utils.MimeType +import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File @@ -66,7 +63,8 @@ class AutoUploadWorker( private val powerManagementService: PowerManagementService, private val syncedFolderProvider: SyncedFolderProvider, private val backgroundJobManager: BackgroundJobManager, - private val repository: FileSystemRepository + private val repository: FileSystemRepository, + val viewThemeUtils: ViewThemeUtils, ) : CoroutineWorker(context, params) { companion object { @@ -74,17 +72,12 @@ class AutoUploadWorker( const val OVERRIDE_POWER_SAVING = "overridePowerSaving" const val CONTENT_URIS = "content_uris" const val SYNCED_FOLDER_ID = "syncedFolderId" - private const val CHANNEL_ID = NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD - - private const val NOTIFICATION_ID = 266 - private const val NOTIFICATION_ERROR_ID = 813 + const val NOTIFICATION_ID = 266 } private val helper = AutoUploadHelper() private lateinit var syncedFolder: SyncedFolder - private val notificationManager by lazy { - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - } + private val notificationManager = AutoUploadNotificationManager(context, viewThemeUtils, NOTIFICATION_ID) @Suppress("TooGenericExceptionCaught", "ReturnCount") override suspend fun doWork(): Result { @@ -128,7 +121,7 @@ class AutoUploadWorker( ) ) - notificationManager.notify(NOTIFICATION_ID, startNotification) + notificationManager.showNotification(startNotification) } } @@ -141,9 +134,7 @@ class AutoUploadWorker( setForeground(foregroundInfo) } - private fun notificationBuilder(): NotificationCompat.Builder = NotificationCompat.Builder(context, CHANNEL_ID) - - private fun createNotification(title: String): Notification = notificationBuilder() + private fun createNotification(title: String): Notification = notificationManager.notificationBuilder .setContentTitle(title) .setSmallIcon(R.drawable.uploads) .setOngoing(true) @@ -265,7 +256,7 @@ class AutoUploadWorker( SettingsActivity.SYNCED_FOLDER_LIGHT_UPLOAD_ON_WIFI ) val uploadActionString = context.resources.getString(R.string.syncedFolder_light_upload_behaviour) - val uploadAction = getUploadAction(uploadActionString) + val uploadAction = FileUploadWorker.getUploadAction(uploadActionString) Log_OC.d(TAG, "upload action is: $uploadAction") Triple(needsCharging, needsWifi, uploadAction) } else { @@ -319,7 +310,21 @@ class AutoUploadWorker( val result = operation.execute(client) uploadsStorageManager.updateStatus(uploadEntity, result.isSuccess) - handleUploadResult(operation, result) + + val isSameFileOnRemote = FileUploadHelper.instance().isSameFileOnRemote( + operation.user, + File(operation.storagePath), + operation.remotePath, + context + ) + + UploadErrorNotificationManager.handleUploadResult( + context, + notificationManager, + isSameFileOnRemote, + operation, + result + ) if (result.isSuccess) { repository.markFileAsUploaded(localPath, syncedFolder) @@ -477,36 +482,4 @@ class AutoUploadWorker( } return lastModificationTime } - - private fun getUploadAction(action: String): Int = when (action) { - "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 - } - - private fun handleUploadResult(operation: UploadFileOperation, result: RemoteOperationResult) { - Log_OC.d(TAG, "handleUploadResult with resultCode: " + result.code) - val notificationBuilder = notificationBuilder() - - val notification = SyncConflictManager.getNotification( - context, - notificationBuilder, - operation, - result, - notifyOnSameFileExists = { - operation.handleLocalBehaviour() - } - ) ?: return - - if (result.code.isFileSpecificError()) { - notificationManager.notify( - NotificationUtils.createUploadNotificationTag(operation.file), - NOTIFICATION_ERROR_ID, - notification - ) - } else { - notificationManager.notify(NOTIFICATION_ID, notification) - } - } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt index 36444380fc47..573e1d1d02ac 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/notification/WorkerNotificationManager.kt @@ -50,6 +50,10 @@ open class WorkerNotificationManager( notificationManager.notify(id, notification) } + fun showNotification(id: Int, notification: Notification) { + notificationManager.notify(id, notification) + } + @Suppress("MagicNumber") fun setProgress(percent: Int, progressText: String?, indeterminate: Boolean) { notificationBuilder.run { diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index 0937941f6934..6c596eaf3935 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -18,14 +18,13 @@ 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.jobs.utils.SyncConflictManager +import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.model.WorkerState import com.nextcloud.model.WorkerStateLiveData import com.nextcloud.utils.ForegroundServiceHelper import com.nextcloud.utils.extensions.getPercent -import com.nextcloud.utils.extensions.isFileSpecificError import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager @@ -118,6 +117,13 @@ class FileUploadWorker( return false } + + fun getUploadAction(action: String): Int = when (action) { + "LOCAL_BEHAVIOUR_FORGET" -> LOCAL_BEHAVIOUR_FORGET + "LOCAL_BEHAVIOUR_MOVE" -> LOCAL_BEHAVIOUR_MOVE + "LOCAL_BEHAVIOUR_DELETE" -> LOCAL_BEHAVIOUR_DELETE + else -> LOCAL_BEHAVIOUR_FORGET + } } private var lastPercent = 0 @@ -322,61 +328,51 @@ class FileUploadWorker( } @Suppress("TooGenericExceptionCaught", "DEPRECATION") - private fun upload( - uploadFileOperation: UploadFileOperation, + private suspend fun upload( + operation: UploadFileOperation, user: User, client: OwnCloudClient - ): RemoteOperationResult { + ): RemoteOperationResult = withContext(Dispatchers.IO) { lateinit var result: RemoteOperationResult + val isSameFileOnRemote = FileUploadHelper.instance().isSameFileOnRemote( + operation.user, + File(operation.storagePath), + operation.remotePath, + context + ) + try { - val storageManager = uploadFileOperation.storageManager - result = uploadFileOperation.execute(client) + val storageManager = operation.storageManager + result = operation.execute(client) val task = ThumbnailsCacheManager.ThumbnailGenerationTask(storageManager, user) - val file = File(uploadFileOperation.originalStoragePath) - val remoteId: String? = uploadFileOperation.file.remoteId + 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(e) } finally { - cleanupUploadProcess(result, uploadFileOperation) - } - - return result - } - - private fun cleanupUploadProcess(result: RemoteOperationResult, uploadFileOperation: UploadFileOperation) { - if (!isStopped || !result.isCancelled) { - uploadsStorageManager.updateDatabaseUploadResult(result, uploadFileOperation) - handleUploadResult(uploadFileOperation, result) - } - } - - private fun handleUploadResult(operation: UploadFileOperation, result: RemoteOperationResult) { - Log_OC.d(TAG, "handleUploadResult with resultCode: " + result.code) - val showSameFileAlreadyExistsNotification = - inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false) - - val notification = SyncConflictManager.getNotification( - context, - notificationManager.notificationBuilder, - operation, - result, - notifyOnSameFileExists = { - if (showSameFileAlreadyExistsNotification) { - notificationManager.showSameFileAlreadyExistsNotification(operation.fileName) - } - - operation.handleLocalBehaviour() + if (!isStopped || !result.isCancelled) { + uploadsStorageManager.updateDatabaseUploadResult(result, operation) + UploadErrorNotificationManager.handleUploadResult( + context, + notificationManager, + isSameFileOnRemote, + operation, + result, + showSameFileAlreadyExistsNotification = { + val showSameFileAlreadyExistsNotification = + inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false) + if (showSameFileAlreadyExistsNotification) { + notificationManager.showSameFileAlreadyExistsNotification(operation.fileName) + } + } + ) } - ) ?: return - - if (result.code.isFileSpecificError()) { - notificationManager.showNewNotification(operation, notification) - } else { - notificationManager.showNotification(notification) } + + return@withContext result } @Suppress("MagicNumber") diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt index 96b127993991..6a93c4db4fad 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt @@ -7,7 +7,6 @@ */ package com.nextcloud.client.jobs.upload -import android.app.Notification import android.app.PendingIntent import android.content.Context import com.nextcloud.client.jobs.notification.WorkerNotificationManager @@ -76,14 +75,6 @@ class UploadNotificationManager(private val context: Context, viewThemeUtils: Vi dismissOldErrorNotification(currentOperation) } - fun showNewNotification(operation: UploadFileOperation, notification: Notification) { - notificationManager.notify( - NotificationUtils.createUploadNotificationTag(operation.file), - FileUploadWorker.NOTIFICATION_ERROR_ID, - notification - ) - } - fun showSameFileAlreadyExistsNotification(filename: String) { notificationBuilder.run { setAutoCancel(true) diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt similarity index 77% rename from app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt rename to app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt index ab885f8a442c..31dfffd7f063 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/SyncConflictManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt @@ -12,31 +12,68 @@ import android.app.PendingIntent import android.content.Context import android.content.Intent import androidx.core.app.NotificationCompat +import com.nextcloud.client.jobs.notification.WorkerNotificationManager import com.nextcloud.client.jobs.upload.FileUploadBroadcastReceiver -import com.nextcloud.client.jobs.upload.FileUploadHelper +import com.nextcloud.utils.extensions.isFileSpecificError import com.owncloud.android.R import com.owncloud.android.authentication.AuthenticatorActivity 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.ui.activity.ConflictsResolveActivity import com.owncloud.android.utils.ErrorMessageAdapter -import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import java.security.SecureRandom -object SyncConflictManager { - fun getNotification( +object UploadErrorNotificationManager { + private const val TAG = "UploadErrorNotificationManager" + + suspend fun handleUploadResult( + context: Context, + notificationManager: WorkerNotificationManager, + isSameFileOnRemote: Boolean, + operation: UploadFileOperation, + result: RemoteOperationResult, + showSameFileAlreadyExistsNotification: () -> Unit = {} + ) = withContext(Dispatchers.Main) { + Log_OC.d(TAG, "handle upload result with result code: " + result.code) + + val notification = getNotification( + isSameFileOnRemote, + context, + notificationManager.notificationBuilder, + operation, + result, + notifyOnSameFileExists = { + showSameFileAlreadyExistsNotification() + operation.handleLocalBehaviour() + } + ) ?: return@withContext + + if (result.code.isFileSpecificError()) { + notificationManager.showNotification(operation.file.fileId.toInt(), notification) + } else { + notificationManager.showNotification(notification) + } + } + + private fun getNotification( + isSameFileOnRemote: Boolean, context: Context, builder: NotificationCompat.Builder, operation: UploadFileOperation, result: RemoteOperationResult, notifyOnSameFileExists: () -> Unit ): Notification? { - if (!shouldShowConflictDialog(context, operation, result, notifyOnSameFileExists)) return null + if (!shouldShowConflictDialog(isSameFileOnRemote, operation, result, notifyOnSameFileExists)) return null val textId = result.code.toFailedResultTitleId() val errorMessage = ErrorMessageAdapter.getErrorCauseMessage(result, operation, context.resources) + Log_OC.d(TAG, "🔔" + "notification created: ${operation.fileName}") + return builder.apply { setTicker(context.getString(textId)) setContentTitle(context.getString(textId)) @@ -126,7 +163,7 @@ object SyncConflictManager { @Suppress("ReturnCount", "ComplexCondition") private fun shouldShowConflictDialog( - context: Context, + isSameFileOnRemote: Boolean, operation: UploadFileOperation, result: RemoteOperationResult, notifyOnSameFileExists: () -> Unit @@ -139,13 +176,6 @@ object SyncConflictManager { return false } - val isSameFileOnRemote = FileUploadHelper.instance().isSameFileOnRemote( - operation.user, - File(operation.storagePath), - operation.remotePath, - context - ) - if (result.code == ResultCode.SYNC_CONFLICT && isSameFileOnRemote) { notifyOnSameFileExists() return false diff --git a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt index caf0ad8b99f8..4bd1e84efc83 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt @@ -26,6 +26,7 @@ fun Pair?, RemoteOperation<*>?>?.getErrorMessage(): Str fun ResultCode.isFileSpecificError(): Boolean { val errorCodes = listOf( + ResultCode.SYNC_CONFLICT, ResultCode.INSTANCE_NOT_CONFIGURED, ResultCode.QUOTA_EXCEEDED, ResultCode.LOCAL_STORAGE_FULL, diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt index fc5f5799482e..1556c092a1b3 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt @@ -41,7 +41,6 @@ import com.owncloud.android.lib.resources.files.model.RemoteFile import com.owncloud.android.ui.dialog.ConflictsResolveDialog import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision import com.owncloud.android.ui.dialog.ConflictsResolveDialog.OnConflictDecisionMadeListener -import com.owncloud.android.ui.notifications.NotificationUtils import com.owncloud.android.utils.FileStorageUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -149,12 +148,13 @@ class ConflictsResolveActivity : } } + // notification id must be file id because only if upload failed via SYNC_CONFLICT can create conflict + // resolve activity private fun dismissConflictResolveNotification(file: OCFile?) { - file ?: return - - val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - val tag = NotificationUtils.createUploadNotificationTag(file) - notificationManager.cancel(tag, FileUploadWorker.NOTIFICATION_ERROR_ID) + file?.let { + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(file.fileId.toInt()) + } } private fun keepBothFolder(offlineOperation: OfflineOperationEntity?, serverFile: OCFile?) { From b142d194ef07f28ad4cd502f8cf7d857a39e59fa Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 14 Nov 2025 09:10:14 +0100 Subject: [PATCH 5/7] fix error handling Signed-off-by: alperozturk --- .../AutoUploadNotificationManager.kt | 6 +- .../jobs/autoUpload/AutoUploadWorker.kt | 11 +--- .../client/jobs/upload/FileUploadWorker.kt | 20 ++---- .../utils/UploadErrorNotificationManager.kt | 64 ++++++++++++------- .../RemoteOperationResultExtensions.kt | 1 - 5 files changed, 50 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadNotificationManager.kt index 70c1a5648cbf..3b4532ecbfa4 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadNotificationManager.kt @@ -13,13 +13,11 @@ import com.owncloud.android.R import com.owncloud.android.ui.notifications.NotificationUtils import com.owncloud.android.utils.theme.ViewThemeUtils -class AutoUploadNotificationManager(private val context: Context, viewThemeUtils: ViewThemeUtils, id: Int) : +class AutoUploadNotificationManager(context: Context, viewThemeUtils: ViewThemeUtils, id: Int) : WorkerNotificationManager( id, context, viewThemeUtils, tickerId = R.string.foreground_service_upload, channelId = NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD - ) { - -} \ No newline at end of file + ) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index fc8b572661ac..3e25eacb1109 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -20,7 +20,6 @@ import com.nextcloud.client.database.entity.toOCUpload import com.nextcloud.client.database.entity.toUploadEntity 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.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService @@ -311,17 +310,9 @@ class AutoUploadWorker( val result = operation.execute(client) uploadsStorageManager.updateStatus(uploadEntity, result.isSuccess) - val isSameFileOnRemote = FileUploadHelper.instance().isSameFileOnRemote( - operation.user, - File(operation.storagePath), - operation.remotePath, - context - ) - - UploadErrorNotificationManager.handleUploadResult( + UploadErrorNotificationManager.handleResult( context, notificationManager, - isSameFileOnRemote, operation, result ) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index 6c596eaf3935..896eaf70bd42 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -335,13 +335,6 @@ class FileUploadWorker( ): RemoteOperationResult = withContext(Dispatchers.IO) { lateinit var result: RemoteOperationResult - val isSameFileOnRemote = FileUploadHelper.instance().isSameFileOnRemote( - operation.user, - File(operation.storagePath), - operation.remotePath, - context - ) - try { val storageManager = operation.storageManager result = operation.execute(client) @@ -355,17 +348,18 @@ class FileUploadWorker( } finally { if (!isStopped || !result.isCancelled) { uploadsStorageManager.updateDatabaseUploadResult(result, operation) - UploadErrorNotificationManager.handleUploadResult( + UploadErrorNotificationManager.handleResult( context, notificationManager, - isSameFileOnRemote, operation, result, showSameFileAlreadyExistsNotification = { - val showSameFileAlreadyExistsNotification = - inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false) - if (showSameFileAlreadyExistsNotification) { - notificationManager.showSameFileAlreadyExistsNotification(operation.fileName) + withContext(Dispatchers.Main) { + val showSameFileAlreadyExistsNotification = + inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false) + if (showSameFileAlreadyExistsNotification) { + notificationManager.showSameFileAlreadyExistsNotification(operation.fileName) + } } } ) diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt index 31dfffd7f063..33f58cf527b6 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt @@ -1,7 +1,7 @@ /* * Nextcloud - Android Client * - * SPDX-FileCopyrightText: Alper Ozturk + * SPDX-FileCopyrightText: 2025 Alper Ozturk * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -14,6 +14,7 @@ import android.content.Intent import androidx.core.app.NotificationCompat import com.nextcloud.client.jobs.notification.WorkerNotificationManager import com.nextcloud.client.jobs.upload.FileUploadBroadcastReceiver +import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.utils.extensions.isFileSpecificError import com.owncloud.android.R import com.owncloud.android.authentication.AuthenticatorActivity @@ -25,47 +26,60 @@ import com.owncloud.android.ui.activity.ConflictsResolveActivity import com.owncloud.android.utils.ErrorMessageAdapter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.io.File import java.security.SecureRandom object UploadErrorNotificationManager { private const val TAG = "UploadErrorNotificationManager" - suspend fun handleUploadResult( + suspend fun handleResult( context: Context, notificationManager: WorkerNotificationManager, - isSameFileOnRemote: Boolean, operation: UploadFileOperation, result: RemoteOperationResult, - showSameFileAlreadyExistsNotification: () -> Unit = {} - ) = withContext(Dispatchers.Main) { + showSameFileAlreadyExistsNotification: suspend () -> Unit = {} + ) { Log_OC.d(TAG, "handle upload result with result code: " + result.code) - val notification = getNotification( - isSameFileOnRemote, - context, - notificationManager.notificationBuilder, - operation, - result, - notifyOnSameFileExists = { - showSameFileAlreadyExistsNotification() - operation.handleLocalBehaviour() - } - ) ?: return@withContext + val notification = withContext(Dispatchers.IO) { + val isSameFileOnRemote = FileUploadHelper.instance().isSameFileOnRemote( + operation.user, + File(operation.storagePath), + operation.remotePath, + context + ) - if (result.code.isFileSpecificError()) { - notificationManager.showNotification(operation.file.fileId.toInt(), notification) - } else { - notificationManager.showNotification(notification) + getNotification( + isSameFileOnRemote, + context, + notificationManager.notificationBuilder, + operation, + result, + notifyOnSameFileExists = { + showSameFileAlreadyExistsNotification() + operation.handleLocalBehaviour() + } + ) + } ?: return + + Log_OC.d(TAG, "🔔" + "notification created") + + withContext(Dispatchers.Main) { + if (result.code.isFileSpecificError()) { + notificationManager.showNotification(operation.file.fileId.toInt(), notification) + } else { + notificationManager.showNotification(notification) + } } } - private fun getNotification( + private suspend fun getNotification( isSameFileOnRemote: Boolean, context: Context, builder: NotificationCompat.Builder, operation: UploadFileOperation, result: RemoteOperationResult, - notifyOnSameFileExists: () -> Unit + notifyOnSameFileExists: suspend () -> Unit ): Notification? { if (!shouldShowConflictDialog(isSameFileOnRemote, operation, result, notifyOnSameFileExists)) return null @@ -162,21 +176,23 @@ object UploadErrorNotificationManager { } @Suppress("ReturnCount", "ComplexCondition") - private fun shouldShowConflictDialog( + private suspend fun shouldShowConflictDialog( isSameFileOnRemote: Boolean, operation: UploadFileOperation, result: RemoteOperationResult, - notifyOnSameFileExists: () -> Unit + notifyOnSameFileExists: suspend () -> Unit ): Boolean { if (result.isSuccess || result.isCancelled || result.code == ResultCode.USER_CANCELLED || operation.isMissingPermissionThrown ) { + Log_OC.w(TAG, "operation is successful, cancelled or lack of storage permission") return false } if (result.code == ResultCode.SYNC_CONFLICT && isSameFileOnRemote) { + Log_OC.w(TAG, "same file exists on remote") notifyOnSameFileExists() return false } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt index 4bd1e84efc83..caf0ad8b99f8 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt @@ -26,7 +26,6 @@ fun Pair?, RemoteOperation<*>?>?.getErrorMessage(): Str fun ResultCode.isFileSpecificError(): Boolean { val errorCodes = listOf( - ResultCode.SYNC_CONFLICT, ResultCode.INSTANCE_NOT_CONFIGURED, ResultCode.QUOTA_EXCEEDED, ResultCode.LOCAL_STORAGE_FULL, From d11e4e35a0d9b806c9102ad9ec349c64b144b867 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 14 Nov 2025 09:29:26 +0100 Subject: [PATCH 6/7] fix notification error id handling Signed-off-by: alperozturk --- .../jobs/autoUpload/AutoUploadWorker.kt | 2 +- .../upload/FileUploadBroadcastReceiver.kt | 28 ++++++++++++------- .../client/jobs/upload/FileUploadWorker.kt | 2 +- .../utils/UploadErrorNotificationManager.kt | 27 +++++------------- .../ui/activity/ConflictsResolveActivity.kt | 12 +++----- 5 files changed, 31 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index 3e25eacb1109..39856a809d1d 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -63,7 +63,7 @@ class AutoUploadWorker( private val syncedFolderProvider: SyncedFolderProvider, private val backgroundJobManager: BackgroundJobManager, private val repository: FileSystemRepository, - val viewThemeUtils: ViewThemeUtils, + val viewThemeUtils: ViewThemeUtils ) : CoroutineWorker(context, params) { companion object { diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt index eca87bfd3751..6cf451759234 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt @@ -8,12 +8,12 @@ package com.nextcloud.client.jobs.upload import android.app.NotificationManager +import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import com.owncloud.android.MainApp import com.owncloud.android.datamodel.UploadsStorageManager -import com.owncloud.android.ui.notifications.NotificationUtils import javax.inject.Inject class FileUploadBroadcastReceiver : BroadcastReceiver() { @@ -22,17 +22,28 @@ class FileUploadBroadcastReceiver : BroadcastReceiver() { lateinit var uploadsStorageManager: UploadsStorageManager companion object { - const val UPLOAD_ID = "UPLOAD_ID" - const val REMOTE_PATH = "REMOTE_PATH" - const val STORAGE_PATH = "STORAGE_PATH" + private const val UPLOAD_ID = "UPLOAD_ID" + + fun getBroadcast(context: Context, id: Int): PendingIntent { + val intent = Intent(context, FileUploadBroadcastReceiver::class.java).apply { + putExtra(UPLOAD_ID, id) + setClass(context, FileUploadBroadcastReceiver::class.java) + setPackage(context.packageName) + } + + return PendingIntent.getBroadcast( + context, + id, + intent, + PendingIntent.FLAG_IMMUTABLE + ) + } } @Suppress("ReturnCount") override fun onReceive(context: Context, intent: Intent) { MainApp.getAppComponent().inject(this) - val remotePath = intent.getStringExtra(REMOTE_PATH) ?: return - val storagePath = intent.getStringExtra(STORAGE_PATH) ?: return val uploadId = intent.getLongExtra(UPLOAD_ID, -1L) if (uploadId == -1L) { return @@ -40,9 +51,6 @@ class FileUploadBroadcastReceiver : BroadcastReceiver() { uploadsStorageManager.removeUpload(uploadId) val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationManager.cancel( - NotificationUtils.createUploadNotificationTag(remotePath, storagePath), - FileUploadWorker.NOTIFICATION_ERROR_ID - ) + notificationManager.cancel(uploadId.toInt()) } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index 896eaf70bd42..03420fa5d4a0 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -353,7 +353,7 @@ class FileUploadWorker( notificationManager, operation, result, - showSameFileAlreadyExistsNotification = { + showSameFileAlreadyExistsNotification = { withContext(Dispatchers.Main) { val showSameFileAlreadyExistsNotification = inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false) diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt index 33f58cf527b6..ba0a1877cce0 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt @@ -27,7 +27,6 @@ import com.owncloud.android.utils.ErrorMessageAdapter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File -import java.security.SecureRandom object UploadErrorNotificationManager { private const val TAG = "UploadErrorNotificationManager" @@ -38,7 +37,7 @@ object UploadErrorNotificationManager { operation: UploadFileOperation, result: RemoteOperationResult, showSameFileAlreadyExistsNotification: suspend () -> Unit = {} - ) { + ) { Log_OC.d(TAG, "handle upload result with result code: " + result.code) val notification = withContext(Dispatchers.IO) { @@ -66,7 +65,7 @@ object UploadErrorNotificationManager { withContext(Dispatchers.Main) { if (result.code.isFileSpecificError()) { - notificationManager.showNotification(operation.file.fileId.toInt(), notification) + notificationManager.showNotification(operation.ocUploadId.toInt(), notification) } else { notificationManager.showNotification(notification) } @@ -106,7 +105,7 @@ object UploadErrorNotificationManager { addAction( R.drawable.ic_delete, context.getString(R.string.upload_list_cancel_upload), - cancelUploadPendingIntent(context, operation) + FileUploadBroadcastReceiver.getBroadcast(context, operation.ocUploadId.toInt()) ) } @@ -147,7 +146,7 @@ object UploadErrorNotificationManager { val intent = ConflictsResolveActivity.createIntent( operation.file, operation.user, - operation.ocUploadId, + conflictUploadId = operation.ocUploadId, Intent.FLAG_ACTIVITY_CLEAR_TOP, context ).apply { @@ -155,23 +154,11 @@ object UploadErrorNotificationManager { setPackage(context.packageName) } - return PendingIntent.getActivity(context, SecureRandom().nextInt(), intent, PendingIntent.FLAG_IMMUTABLE) - } - - private fun cancelUploadPendingIntent(context: Context, operation: UploadFileOperation): PendingIntent { - val intent = Intent(context, FileUploadBroadcastReceiver::class.java).apply { - putExtra(FileUploadBroadcastReceiver.UPLOAD_ID, operation.ocUploadId) - putExtra(FileUploadBroadcastReceiver.REMOTE_PATH, operation.file.remotePath) - putExtra(FileUploadBroadcastReceiver.STORAGE_PATH, operation.file.storagePath) - setClass(context, FileUploadBroadcastReceiver::class.java) - setPackage(context.packageName) - } - - return PendingIntent.getBroadcast( + return PendingIntent.getActivity( context, - operation.file.fileId.toInt(), + operation.ocUploadId.toInt(), intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_IMMUTABLE ) } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt index 1556c092a1b3..c1ba17a82aec 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ConflictsResolveActivity.kt @@ -130,7 +130,7 @@ class ConflictsResolveActivity : updateThumbnailIfNeeded(decision, file, oldFile) } - dismissConflictResolveNotification(file) + dismissConflictResolveNotification() finish() } } @@ -148,13 +148,9 @@ class ConflictsResolveActivity : } } - // notification id must be file id because only if upload failed via SYNC_CONFLICT can create conflict - // resolve activity - private fun dismissConflictResolveNotification(file: OCFile?) { - file?.let { - val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - notificationManager.cancel(file.fileId.toInt()) - } + private fun dismissConflictResolveNotification() { + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(conflictUploadId.toInt()) } private fun keepBothFolder(offlineOperation: OfflineOperationEntity?, serverFile: OCFile?) { From c76f6a0819013fc185622469f9dc3dd4f1b2c678 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 14 Nov 2025 11:23:39 +0100 Subject: [PATCH 7/7] fix notification error id handling Signed-off-by: alperozturk --- .../client/jobs/upload/FileUploadBroadcastReceiver.kt | 4 ++-- .../client/jobs/utils/UploadErrorNotificationManager.kt | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt index 6cf451759234..7f7df3a32cb3 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt @@ -24,7 +24,7 @@ class FileUploadBroadcastReceiver : BroadcastReceiver() { companion object { private const val UPLOAD_ID = "UPLOAD_ID" - fun getBroadcast(context: Context, id: Int): PendingIntent { + fun getBroadcast(context: Context, id: Long): PendingIntent { val intent = Intent(context, FileUploadBroadcastReceiver::class.java).apply { putExtra(UPLOAD_ID, id) setClass(context, FileUploadBroadcastReceiver::class.java) @@ -33,7 +33,7 @@ class FileUploadBroadcastReceiver : BroadcastReceiver() { return PendingIntent.getBroadcast( context, - id, + id.toInt(), intent, PendingIntent.FLAG_IMMUTABLE ) diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt index ba0a1877cce0..0ae1ec154d05 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt @@ -85,8 +85,6 @@ object UploadErrorNotificationManager { val textId = result.code.toFailedResultTitleId() val errorMessage = ErrorMessageAdapter.getErrorCauseMessage(result, operation, context.resources) - Log_OC.d(TAG, "🔔" + "notification created: ${operation.fileName}") - return builder.apply { setTicker(context.getString(textId)) setContentTitle(context.getString(textId)) @@ -105,7 +103,7 @@ object UploadErrorNotificationManager { addAction( R.drawable.ic_delete, context.getString(R.string.upload_list_cancel_upload), - FileUploadBroadcastReceiver.getBroadcast(context, operation.ocUploadId.toInt()) + FileUploadBroadcastReceiver.getBroadcast(context, operation.ocUploadId) ) }