From 84158b2eed25288b16c52c596b1ff1ccf1962eef Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 19 Mar 2026 15:42:08 +0100 Subject: [PATCH 1/6] fix(gallery): video thumbnail and overlay Signed-off-by: alperozturk96 --- .../jobs/gallery/GalleryImageGenerationJob.kt | 66 ++++++++++++++----- .../datamodel/ThumbnailsCacheManager.java | 8 +++ .../android/ui/adapter/OCFileListDelegate.kt | 15 +++-- 3 files changed, 68 insertions(+), 21 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 bc1be300a434..f48c913c762a 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 @@ -8,6 +8,8 @@ package com.nextcloud.client.jobs.gallery import android.graphics.Bitmap +import android.media.ThumbnailUtils +import android.provider.MediaStore import android.widget.ImageView import androidx.core.content.ContextCompat import com.nextcloud.client.account.User @@ -117,22 +119,56 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag private suspend fun getBitmap(file: OCFile, onThumbnailGeneration: () -> Unit): Bitmap? = withContext(Dispatchers.IO) { - val key = file.remoteId - val cachedThumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( + if (MimeTypeUtil.isVideo(file)) { + getVideoBitmap(file, onThumbnailGeneration) + } else { + getResizedImageBitmap(file, onThumbnailGeneration) + } + } + + private fun getVideoBitmap(file: OCFile, onThumbnailGeneration: () -> Unit): Bitmap? { + val key = ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.remoteId + val cached = ThumbnailsCacheManager.getBitmapFromDiskCache(key) + + if (cached != null && !file.isUpdateThumbnailNeeded) { + return ThumbnailsCacheManager.addVideoOverlay(cached, MainApp.getAppContext()) + } + + onThumbnailGeneration() + var bitmap: Bitmap? = null + if (file.isDown) { + bitmap = ThumbnailUtils.createVideoThumbnail( + file.storagePath, + MediaStore.Images.Thumbnails.MINI_KIND + ) + } + + if (bitmap == null) { + bitmap = ThumbnailsCacheManager.getBitmapFromDiskCache( ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId ) - if (cachedThumbnail != null && !file.isUpdateThumbnailNeeded) { - Log_OC.d(TAG, "cached thumbnail is used for: ${file.fileName}") - return@withContext getThumbnailFromCache(file, cachedThumbnail, key) - } + } - Log_OC.d(TAG, "generating new thumbnail for: ${file.fileName}") + if (bitmap != null) { + ThumbnailsCacheManager.addBitmapToCache(key, bitmap) + return ThumbnailsCacheManager.addVideoOverlay(bitmap, MainApp.getAppContext()) + } + return null + } - onThumbnailGeneration() - semaphore.withPermit { - return@withContext getThumbnailFromServerAndAddToCache(file, cachedThumbnail) - } + private suspend fun getResizedImageBitmap(file: OCFile, onThumbnailGeneration: () -> Unit): Bitmap? { + val key = file.remoteId + val cachedThumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( + ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + key + ) + if (cachedThumbnail != null && !file.isUpdateThumbnailNeeded) { + return getThumbnailFromCache(file, cachedThumbnail, key) + } + onThumbnailGeneration() + semaphore.withPermit { + return getThumbnailFromServerAndAddToCache(file, cachedThumbnail) } + } private suspend fun setThumbnail( bitmap: Bitmap, @@ -168,14 +204,12 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag private fun getThumbnailFromCache(file: OCFile, thumbnail: Bitmap, key: String): Bitmap { var result = thumbnail - if (MimeTypeUtil.isVideo(file)) { - result = ThumbnailsCacheManager.addVideoOverlay(thumbnail, MainApp.getAppContext()) - } - if (thumbnail.allocationKilobyte() > ThumbnailsCacheManager.THUMBNAIL_SIZE_IN_KB) { result = ThumbnailsCacheManager.getScaledThumbnailAfterSave(result, key) } - + if (MimeTypeUtil.isVideo(file)) { + result = ThumbnailsCacheManager.addVideoOverlay(thumbnail, MainApp.getAppContext()) + } return result } diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 72cfadc35f03..35a51bf69582 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -1325,4 +1325,12 @@ public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageMana return thumbnail; } + + public static String getCacheKey(@NonNull OCFile file) { + if (MimeTypeUtil.isVideo(file)) { + return ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId(); + } else { + return ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.getRemoteId(); + } + } } 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 72e619f869b0..814b436ed477 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 @@ -36,6 +36,7 @@ import com.owncloud.android.ui.fragment.SearchType import com.owncloud.android.ui.interfaces.OCFileListFragmentInterface import com.owncloud.android.utils.DisplayUtils import com.owncloud.android.utils.EncryptionUtils +import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.overlay.OverlayManager import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.CoroutineScope @@ -113,11 +114,15 @@ class OCFileListDelegate( imageView.tag = file.fileId // set placeholder before async job - val cached = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId - ) - if (cached != null) { - imageView.setImageBitmap(cached) + val cacheKey = ThumbnailsCacheManager.getCacheKey(file) + val cachedBitmap = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) + if (cachedBitmap != null) { + val overlay = if (MimeTypeUtil.isVideo(file)) { + ThumbnailsCacheManager.addVideoOverlay(cachedBitmap, context) + } else { + cachedBitmap + } + imageView.setImageBitmap(overlay) } else { imageView.setImageDrawable(OCFileUtils.getMediaPlaceholder(file, imageDimension)) } From 9d2ca5f74274adf05018bfd48a422b10d9d32c8d Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 19 Mar 2026 15:44:58 +0100 Subject: [PATCH 2/6] fix(gallery): video thumbnail and overlay Signed-off-by: alperozturk96 --- .../jobs/gallery/GalleryImageGenerationJob.kt | 24 +++++++++++++++---- .../datamodel/ThumbnailsCacheManager.java | 6 +++-- 2 files changed, 24 insertions(+), 6 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 f48c913c762a..d5908aa57371 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 @@ -9,12 +9,15 @@ package com.nextcloud.client.jobs.gallery import android.graphics.Bitmap import android.media.ThumbnailUtils +import android.os.Build import android.provider.MediaStore +import android.util.Size import android.widget.ImageView import androidx.core.content.ContextCompat import com.nextcloud.client.account.User import com.nextcloud.utils.allocationKilobyte import com.nextcloud.utils.extensions.isPNG +import com.nextcloud.utils.extensions.toFile import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.datamodel.FileDataStorageManager @@ -137,10 +140,7 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag onThumbnailGeneration() var bitmap: Bitmap? = null if (file.isDown) { - bitmap = ThumbnailUtils.createVideoThumbnail( - file.storagePath, - MediaStore.Images.Thumbnails.MINI_KIND - ) + bitmap = createVideoThumbnail(file.storagePath) } if (bitmap == null) { @@ -156,6 +156,22 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag return null } + private fun createVideoThumbnail(storagePath: String): Bitmap? { + val file = storagePath.toFile() ?: return null + val size = ThumbnailsCacheManager.getThumbnailDimension() + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + try { + ThumbnailUtils.createVideoThumbnail(file, Size(size, size), null) + } catch (e: Exception) { + Log_OC.e(TAG, "Failed to create video thumbnail: ${e.message}") + null + } + } else { + @Suppress("DEPRECATION") + ThumbnailUtils.createVideoThumbnail(storagePath, MediaStore.Images.Thumbnails.MINI_KIND) + } + } + private suspend fun getResizedImageBitmap(file: OCFile, onThumbnailGeneration: () -> Unit): Bitmap? { val key = file.remoteId val cachedThumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 35a51bf69582..e56f023ed7c8 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -1092,10 +1092,12 @@ public static Bitmap addVideoOverlay(Bitmap thumbnail, Context context) { c.drawBitmap(thumbnail, 0, 0, null); + float left = (thumbnail.getWidth() - px) / 2f; + float top = (thumbnail.getHeight() - px) / 2f; + Paint p = new Paint(); p.setAlpha(230); - - c.drawBitmap(resizedPlayButton, px, px, p); + c.drawBitmap(resizedPlayButton, left, top, p); return resultBitmap; } From 326c05450993d68780258dc9d86ed2279956433c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 19 Mar 2026 15:46:52 +0100 Subject: [PATCH 3/6] fix(gallery): video thumbnail and overlay Signed-off-by: alperozturk96 --- .../nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt | 3 +-- 1 file changed, 1 insertion(+), 2 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 d5908aa57371..b37dd94e5fe9 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 @@ -34,6 +34,7 @@ import kotlinx.coroutines.withContext import java.util.Collections import java.util.WeakHashMap +@Suppress("DEPRECATION", "TooGenericExceptionCaught", "ReturnCount") class GalleryImageGenerationJob(private val user: User, private val storageManager: FileDataStorageManager) { companion object { private const val TAG = "GalleryImageGenerationJob" @@ -88,7 +89,6 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } } - @Suppress("TooGenericExceptionCaught") suspend fun run(file: OCFile, imageView: ImageView, listener: GalleryImageGenerationListener) { try { var newImage = false @@ -229,7 +229,6 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag return result } - @Suppress("DEPRECATION", "TooGenericExceptionCaught") private suspend fun getThumbnailFromServerAndAddToCache(file: OCFile, thumbnail: Bitmap?): Bitmap? { var thumbnail = thumbnail try { From 0f299e70a1734c808672836beb128bf5dcd0ac5b Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 26 Mar 2026 14:57:11 +0100 Subject: [PATCH 4/6] use PREFIX_RESIZED_IMAGE Signed-off-by: alperozturk96 --- .../jobs/gallery/GalleryImageGenerationJob.kt | 174 ++++++++---------- .../datamodel/ThumbnailsCacheManager.java | 13 +- .../android/ui/adapter/OCFileListDelegate.kt | 2 +- 3 files changed, 80 insertions(+), 109 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 b37dd94e5fe9..586153d45ee2 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 @@ -8,14 +8,15 @@ package com.nextcloud.client.jobs.gallery import android.graphics.Bitmap +import android.graphics.Point import android.media.ThumbnailUtils import android.os.Build import android.provider.MediaStore import android.util.Size +import android.view.WindowManager import android.widget.ImageView import androidx.core.content.ContextCompat import com.nextcloud.client.account.User -import com.nextcloud.utils.allocationKilobyte import com.nextcloud.utils.extensions.isPNG import com.nextcloud.utils.extensions.toFile import com.owncloud.android.MainApp @@ -25,6 +26,7 @@ import com.owncloud.android.datamodel.OCFile 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.BitmapUtils import com.owncloud.android.utils.MimeTypeUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -36,14 +38,10 @@ import java.util.WeakHashMap @Suppress("DEPRECATION", "TooGenericExceptionCaught", "ReturnCount") class GalleryImageGenerationJob(private val user: User, private val storageManager: FileDataStorageManager) { + companion object { private const val TAG = "GalleryImageGenerationJob" - private val semaphore = Semaphore( - maxOf( - 3, - Runtime.getRuntime().availableProcessors() / 2 - ) - ) + private val semaphore = Semaphore(maxOf(3, Runtime.getRuntime().availableProcessors() / 2)) private val activeJobs = Collections.synchronizedMap(WeakHashMap()) fun cancelAllActiveJobs() { @@ -91,79 +89,89 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag suspend fun run(file: OCFile, imageView: ImageView, listener: GalleryImageGenerationListener) { try { - var newImage = false - if (file.remoteId == null && !file.isPreviewAvailable) { Log_OC.e(TAG, "file has no remoteId and no preview") - withContext(Dispatchers.Main) { - listener.onError() - } + withContext(Dispatchers.Main) { listener.onError() } return } - val bitmap: Bitmap? = getBitmap(file, onThumbnailGeneration = { - newImage = true - }) + var newImage = false + val bitmap: Bitmap? = getBitmap(file, onNewThumbnail = { newImage = true }) if (bitmap == null) { - withContext(Dispatchers.Main) { - listener.onError() - } + withContext(Dispatchers.Main) { listener.onError() } return } setThumbnail(bitmap, file, imageView, newImage, listener) } catch (_: Exception) { - withContext(Dispatchers.Main) { - listener.onError() - } + withContext(Dispatchers.Main) { listener.onError() } } } - private suspend fun getBitmap(file: OCFile, onThumbnailGeneration: () -> Unit): Bitmap? = + private suspend fun getBitmap(file: OCFile, onNewThumbnail: () -> Unit): Bitmap? = withContext(Dispatchers.IO) { - if (MimeTypeUtil.isVideo(file)) { - getVideoBitmap(file, onThumbnailGeneration) - } else { - getResizedImageBitmap(file, onThumbnailGeneration) + val cacheKey = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId + + val cached = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) + if (cached != null && !file.isUpdateThumbnailNeeded) { + return@withContext applyVideoOverlayIfNeeded(file, cached) } - } - private fun getVideoBitmap(file: OCFile, onThumbnailGeneration: () -> Unit): Bitmap? { - val key = ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.remoteId - val cached = ThumbnailsCacheManager.getBitmapFromDiskCache(key) + onNewThumbnail() - if (cached != null && !file.isUpdateThumbnailNeeded) { - return ThumbnailsCacheManager.addVideoOverlay(cached, MainApp.getAppContext()) - } + val local = decodeLocalThumbnail(file) + if (local != null) { + ThumbnailsCacheManager.addBitmapToCache(cacheKey, local) + return@withContext applyVideoOverlayIfNeeded(file, local) + } + + val remote = semaphore.withPermit { fetchFromServer(file) } + if (remote != null) { + return@withContext applyVideoOverlayIfNeeded(file, remote) + } - onThumbnailGeneration() - var bitmap: Bitmap? = null - if (file.isDown) { - bitmap = createVideoThumbnail(file.storagePath) + null } - if (bitmap == null) { - bitmap = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId - ) + private fun decodeLocalThumbnail(file: OCFile): Bitmap? { + return if (MimeTypeUtil.isVideo(file)) { + createVideoThumbnail(file.storagePath) + } else { + doResizedImageInBackgroundFromLocalFile(file) } + } + + private fun doResizedImageInBackgroundFromLocalFile(file: OCFile): Bitmap? { + val wm = MainApp.getAppContext().getSystemService(android.content.Context.WINDOW_SERVICE) as WindowManager + val p = Point() + wm.defaultDisplay.getSize(p) + + val pxW = p.x + val pxH = p.y + + val cacheKey = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId - if (bitmap != null) { - ThumbnailsCacheManager.addBitmapToCache(key, bitmap) - return ThumbnailsCacheManager.addVideoOverlay(bitmap, MainApp.getAppContext()) + var bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.storagePath, pxW, pxH) ?: return null + + if (file.isPNG()) { + bitmap = ThumbnailsCacheManager.handlePNG(bitmap, pxW, pxH) } - return null + + val thumbnail = ThumbnailsCacheManager.addThumbnailToCache(cacheKey, bitmap, file.storagePath, pxW, pxH) + file.isUpdateThumbnailNeeded = false + + return thumbnail } private fun createVideoThumbnail(storagePath: String): Bitmap? { - val file = storagePath.toFile() ?: return null + val ioFile = storagePath.toFile() ?: return null val size = ThumbnailsCacheManager.getThumbnailDimension() return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { try { - ThumbnailUtils.createVideoThumbnail(file, Size(size, size), null) + ThumbnailUtils.createVideoThumbnail(ioFile, Size(size, size), null) } catch (e: Exception) { - Log_OC.e(TAG, "Failed to create video thumbnail: ${e.message}") + Log_OC.e(TAG, "Failed to create video thumbnail from local file: ${e.message}") null } } else { @@ -172,17 +180,25 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } } - private suspend fun getResizedImageBitmap(file: OCFile, onThumbnailGeneration: () -> Unit): Bitmap? { - val key = file.remoteId - val cachedThumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache( - ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + key - ) - if (cachedThumbnail != null && !file.isUpdateThumbnailNeeded) { - return getThumbnailFromCache(file, cachedThumbnail, key) + private suspend fun fetchFromServer(file: OCFile): Bitmap? { + return try { + val client = withContext(Dispatchers.IO) { + OwnCloudClientManagerFactory.getDefaultSingleton() + .getClientFor(user.toOwnCloudAccount(), MainApp.getAppContext()) + } + ThumbnailsCacheManager.setClient(client) + ThumbnailsCacheManager.doResizedImageInBackground(file, storageManager) + } catch (t: Throwable) { + Log_OC.e(TAG, "Server fetch failed for $file", t) + null } - onThumbnailGeneration() - semaphore.withPermit { - return getThumbnailFromServerAndAddToCache(file, cachedThumbnail) + } + + private fun applyVideoOverlayIfNeeded(file: OCFile, bitmap: Bitmap): Bitmap { + return if (MimeTypeUtil.isVideo(file)) { + ThumbnailsCacheManager.addVideoOverlay(bitmap, MainApp.getAppContext()) + } else { + bitmap } } @@ -198,16 +214,11 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag if (imageView.tag.toString() == tagId) { if (file.isPNG()) { imageView.setBackgroundColor( - ContextCompat.getColor( - MainApp.getAppContext(), - R.color.bg_default - ) + ContextCompat.getColor(MainApp.getAppContext(), R.color.bg_default) ) } - if (newImage) { - listener.onNewGalleryImage() - } + if (newImage) listener.onNewGalleryImage() if (imageView.isAttachedToWindow) { imageView.setImageBitmap(bitmap) @@ -217,37 +228,4 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag listener.onSuccess() } - - private fun getThumbnailFromCache(file: OCFile, thumbnail: Bitmap, key: String): Bitmap { - var result = thumbnail - if (thumbnail.allocationKilobyte() > ThumbnailsCacheManager.THUMBNAIL_SIZE_IN_KB) { - result = ThumbnailsCacheManager.getScaledThumbnailAfterSave(result, key) - } - if (MimeTypeUtil.isVideo(file)) { - result = ThumbnailsCacheManager.addVideoOverlay(thumbnail, MainApp.getAppContext()) - } - return result - } - - private suspend fun getThumbnailFromServerAndAddToCache(file: OCFile, thumbnail: Bitmap?): Bitmap? { - var thumbnail = thumbnail - try { - val client = withContext(Dispatchers.IO) { - OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor( - user.toOwnCloudAccount(), - MainApp.getAppContext() - ) - } - ThumbnailsCacheManager.setClient(client) - thumbnail = ThumbnailsCacheManager.doResizedImageInBackground(file, storageManager) - - if (MimeTypeUtil.isVideo(file) && thumbnail != null) { - thumbnail = ThumbnailsCacheManager.addVideoOverlay(thumbnail, MainApp.getAppContext()) - } - } catch (t: Throwable) { - Log_OC.e(TAG, "Generation of gallery image for $file failed", t) - } - - return thumbnail - } } diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index e56f023ed7c8..26d2a90a9d8a 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -187,6 +187,7 @@ private static Point getScreenDimension() { /** * Add thumbnail to cache + * * @param imageKey: thumb key * @param bitmap: image for extracting thumbnail * @param path: image path @@ -194,7 +195,7 @@ private static Point getScreenDimension() { * @param pxH: thumbnail height in pixel * @return Bitmap */ - private static Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int pxW, int pxH){ + public static Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int pxW, int pxH) { Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH); @@ -1142,7 +1143,7 @@ public AsyncMediaThumbnailDrawable(Resources res, Bitmap bitmap) { /** * adapted from ... */ - private static Bitmap handlePNG(Bitmap source, int newWidth, int newHeight) { + public static Bitmap handlePNG(Bitmap source, int newWidth, int newHeight) { Bitmap softwareBitmap = source.copy(Bitmap.Config.ARGB_8888, false); int sourceWidth = source.getWidth(); @@ -1327,12 +1328,4 @@ public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageMana return thumbnail; } - - public static String getCacheKey(@NonNull OCFile file) { - if (MimeTypeUtil.isVideo(file)) { - return ThumbnailsCacheManager.PREFIX_THUMBNAIL + file.getRemoteId(); - } else { - return ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.getRemoteId(); - } - } } 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 814b436ed477..3d1966239d0f 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 @@ -114,7 +114,7 @@ class OCFileListDelegate( imageView.tag = file.fileId // set placeholder before async job - val cacheKey = ThumbnailsCacheManager.getCacheKey(file) + val cacheKey = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId val cachedBitmap = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) if (cachedBitmap != null) { val overlay = if (MimeTypeUtil.isVideo(file)) { From 53d9feefcf512d9d4a8f57fe30608030ad19eb9d Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 26 Mar 2026 15:07:37 +0100 Subject: [PATCH 5/6] use PREFIX_RESIZED_IMAGE Signed-off-by: alperozturk96 --- .../client/jobs/gallery/GalleryImageGenerationJob.kt | 4 ++-- .../owncloud/android/datamodel/ThumbnailsCacheManager.java | 2 +- 2 files changed, 3 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 586153d45ee2..6f234c888897 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 @@ -138,11 +138,11 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag return if (MimeTypeUtil.isVideo(file)) { createVideoThumbnail(file.storagePath) } else { - doResizedImageInBackgroundFromLocalFile(file) + createImageThumbnail(file) } } - private fun doResizedImageInBackgroundFromLocalFile(file: OCFile): Bitmap? { + private fun createImageThumbnail(file: OCFile): Bitmap? { val wm = MainApp.getAppContext().getSystemService(android.content.Context.WINDOW_SERVICE) as WindowManager val p = Point() wm.defaultDisplay.getSize(p) diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 26d2a90a9d8a..3586f6adeba5 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -1299,7 +1299,7 @@ public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageMana Log_OC.d(TAG, "resized image generated"); } } else { - Log_OC.e(TAG, "cannot generate thumbnail not supported file type, status: " + status); + Log_OC.e(TAG, "cannot generate thumbnail not supported file type, status: " + status + " file: " + file.getRemotePath()); mClient.exhaustResponse(getMethod.getResponseBodyAsStream()); } From 58b7738c5767ab0a2571db71ab6363824e6940a0 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 26 Mar 2026 15:07:54 +0100 Subject: [PATCH 6/6] use PREFIX_RESIZED_IMAGE Signed-off-by: alperozturk96 --- .../jobs/gallery/GalleryImageGenerationJob.kt | 77 +++++++++---------- 1 file changed, 35 insertions(+), 42 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 6f234c888897..2e532c00ac88 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 @@ -109,37 +109,34 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } } - private suspend fun getBitmap(file: OCFile, onNewThumbnail: () -> Unit): Bitmap? = - withContext(Dispatchers.IO) { - val cacheKey = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId - - val cached = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) - if (cached != null && !file.isUpdateThumbnailNeeded) { - return@withContext applyVideoOverlayIfNeeded(file, cached) - } - - onNewThumbnail() + private suspend fun getBitmap(file: OCFile, onNewThumbnail: () -> Unit): Bitmap? = withContext(Dispatchers.IO) { + val cacheKey = ThumbnailsCacheManager.PREFIX_RESIZED_IMAGE + file.remoteId - val local = decodeLocalThumbnail(file) - if (local != null) { - ThumbnailsCacheManager.addBitmapToCache(cacheKey, local) - return@withContext applyVideoOverlayIfNeeded(file, local) - } + val cached = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) + if (cached != null && !file.isUpdateThumbnailNeeded) { + return@withContext applyVideoOverlayIfNeeded(file, cached) + } - val remote = semaphore.withPermit { fetchFromServer(file) } - if (remote != null) { - return@withContext applyVideoOverlayIfNeeded(file, remote) - } + onNewThumbnail() - null + val local = decodeLocalThumbnail(file) + if (local != null) { + ThumbnailsCacheManager.addBitmapToCache(cacheKey, local) + return@withContext applyVideoOverlayIfNeeded(file, local) } - private fun decodeLocalThumbnail(file: OCFile): Bitmap? { - return if (MimeTypeUtil.isVideo(file)) { - createVideoThumbnail(file.storagePath) - } else { - createImageThumbnail(file) + val remote = semaphore.withPermit { fetchFromServer(file) } + if (remote != null) { + return@withContext applyVideoOverlayIfNeeded(file, remote) } + + null + } + + private fun decodeLocalThumbnail(file: OCFile): Bitmap? = if (MimeTypeUtil.isVideo(file)) { + createVideoThumbnail(file.storagePath) + } else { + createImageThumbnail(file) } private fun createImageThumbnail(file: OCFile): Bitmap? { @@ -180,26 +177,22 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } } - private suspend fun fetchFromServer(file: OCFile): Bitmap? { - return try { - val client = withContext(Dispatchers.IO) { - OwnCloudClientManagerFactory.getDefaultSingleton() - .getClientFor(user.toOwnCloudAccount(), MainApp.getAppContext()) - } - ThumbnailsCacheManager.setClient(client) - ThumbnailsCacheManager.doResizedImageInBackground(file, storageManager) - } catch (t: Throwable) { - Log_OC.e(TAG, "Server fetch failed for $file", t) - null + private suspend fun fetchFromServer(file: OCFile): Bitmap? = try { + val client = withContext(Dispatchers.IO) { + OwnCloudClientManagerFactory.getDefaultSingleton() + .getClientFor(user.toOwnCloudAccount(), MainApp.getAppContext()) } + ThumbnailsCacheManager.setClient(client) + ThumbnailsCacheManager.doResizedImageInBackground(file, storageManager) + } catch (t: Throwable) { + Log_OC.e(TAG, "Server fetch failed for $file", t) + null } - private fun applyVideoOverlayIfNeeded(file: OCFile, bitmap: Bitmap): Bitmap { - return if (MimeTypeUtil.isVideo(file)) { - ThumbnailsCacheManager.addVideoOverlay(bitmap, MainApp.getAppContext()) - } else { - bitmap - } + private fun applyVideoOverlayIfNeeded(file: OCFile, bitmap: Bitmap): Bitmap = if (MimeTypeUtil.isVideo(file)) { + ThumbnailsCacheManager.addVideoOverlay(bitmap, MainApp.getAppContext()) + } else { + bitmap } private suspend fun setThumbnail(