From 3fe61e113acf4a0b1fb8e0d69b6db45342b6d2f6 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 24 Feb 2026 14:59:55 +0100 Subject: [PATCH 1/2] fix(gallery): image job cancellation Signed-off-by: alperozturk96 --- .../jobs/gallery/GalleryImageGenerationJob.kt | 39 ++++++++++++------- .../android/ui/adapter/OCFileListDelegate.kt | 4 +- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt index 3247771a7307..8f51345d20c6 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt @@ -21,12 +21,12 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager import com.owncloud.android.lib.common.OwnCloudClientManagerFactory import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.utils.MimeTypeUtil -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext +import java.util.Collections import java.util.WeakHashMap class GalleryImageGenerationJob(private val user: User, private val storageManager: FileDataStorageManager) { @@ -38,35 +38,48 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag Runtime.getRuntime().availableProcessors() / 2 ) ) - private val activeJobs = WeakHashMap() + private val activeJobs = Collections.synchronizedMap(WeakHashMap()) fun cancelAllActiveJobs() { - val entries = activeJobs.entries.toList() - for ((_, job) in entries) { + val jobsToCancel = synchronized(activeJobs) { + val list = activeJobs.values.toList() + activeJobs.clear() + list + } + for (job in jobsToCancel) { job.cancel() } - activeJobs.clear() } - fun removeActiveJob(imageView: ImageView, job: CoroutineScope) { - if (isActiveJob(imageView, job)) { - removeJob(imageView) + fun removeActiveJob(imageView: ImageView, job: Job) { + synchronized(activeJobs) { + if (isActiveJob(imageView, job)) { + removeJob(imageView) + } } } - fun isActiveJob(imageView: ImageView, job: CoroutineScope): Boolean = activeJobs[imageView] === job + fun isActiveJob(imageView: ImageView, job: Job): Boolean = synchronized(activeJobs) { + activeJobs[imageView] === job + } fun storeJob(job: Job, imageView: ImageView) { - activeJobs[imageView] = job + synchronized(activeJobs) { + activeJobs[imageView] = job + } } fun cancelPreviousJob(imageView: ImageView) { - activeJobs[imageView]?.cancel() - removeJob(imageView) + synchronized(activeJobs) { + activeJobs[imageView]?.cancel() + activeJobs.remove(imageView) + } } fun removeJob(imageView: ImageView) { - activeJobs.remove(imageView) + synchronized(activeJobs) { + activeJobs.remove(imageView) + } } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index 343659e8938a..0f25615538a0 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -42,6 +42,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -152,7 +153,8 @@ class OCFileListDelegate( } ) } finally { - GalleryImageGenerationJob.removeActiveJob(imageView, this) + val currentJob = coroutineContext.job + GalleryImageGenerationJob.removeActiveJob(imageView, currentJob) } } From 071bca2d5c35fb1a13395c89edd764cd170b33fb Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 25 Feb 2026 08:16:58 +0100 Subject: [PATCH 2/2] check job cancellation via try catch Signed-off-by: alperozturk96 --- .../client/jobs/gallery/GalleryImageGenerationJob.kt | 3 +-- .../com/owncloud/android/ui/adapter/OCFileListDelegate.kt | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt index 8f51345d20c6..bc1be300a434 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt @@ -108,8 +108,7 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } setThumbnail(bitmap, file, imageView, newImage, listener) - } catch (e: Exception) { - Log_OC.e(TAG, "gallery image generation job: ", e) + } catch (_: Exception) { withContext(Dispatchers.Main) { listener.onError() } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt index 0f25615538a0..c095014ffbb7 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCFileListDelegate.kt @@ -420,10 +420,15 @@ class OCFileListDelegate( showShareAvatar = bool } + @Suppress("TooGenericExceptionCaught") fun cleanup() { ioScope.cancel() - GalleryImageGenerationJob.cancelAllActiveJobs() + try { + GalleryImageGenerationJob.cancelAllActiveJobs() + } catch (e: Exception) { + Log_OC.e(TAG, "exception: ", e) + } // cancel async tasks from ThumbnailsCacheManager cancelAllPendingTasks()