From 2c2c8cd1b9099588f1ca0931bdf42953802aa990 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 20 Oct 2026 12:07:23 +0200 Subject: [PATCH 1/4] remove dead code Signed-off-by: alperozturk96 --- .../android/datamodel/ThumbnailsCacheManager.java | 13 ------------- 1 file changed, 13 deletions(-) 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 a5a01bd45fae..0e074633a851 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -300,19 +300,6 @@ public static Bitmap getBitmapFromDiskCache(String key) { return null; } - public static Bitmap getScaledThumbnailAfterSave(Bitmap thumbnail, String imageKey) { - Bitmap result = BitmapExtensionsKt.scaleUntil(thumbnail, THUMBNAIL_SIZE_IN_KB); - - synchronized (mThumbnailsDiskCacheLock) { - if (mThumbnailCache != null) { - Log_OC.d(TAG, "Scaling bitmap before caching: " + imageKey); - mThumbnailCache.put(imageKey, result); - } - } - - return result; - } - public static class ResizedImageGenerationTask extends AsyncTask { private final FileFragment fileFragment; private final FileDataStorageManager storageManager; From 9a331f76f05847fd2941ba7901741635b10b386c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 20 Oct 2026 12:25:18 +0200 Subject: [PATCH 2/4] refactor(thumbail-cache-manager): resized image generation Signed-off-by: alperozturk96 --- .../jobs/gallery/GalleryImageGenerationJob.kt | 3 +- .../utils/thumbnail/ResizedImageGenerator.kt | 135 +++++++++++ .../job/ResizedImageGenerationJob.kt | 98 ++++++++ .../datamodel/ThumbnailsCacheManager.java | 225 +----------------- .../ui/preview/media/PreviewMediaFragment.kt | 43 ++-- 5 files changed, 250 insertions(+), 254 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt create mode 100644 app/src/main/java/com/nextcloud/utils/thumbnail/job/ResizedImageGenerationJob.kt 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 8c0ea4571f29..0bc56487db0c 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,6 +21,7 @@ import com.nextcloud.utils.extensions.getSmallThumbnail import com.nextcloud.utils.extensions.isPNG import com.nextcloud.utils.extensions.setMediaThumbnail import com.nextcloud.utils.extensions.toFile +import com.nextcloud.utils.thumbnail.ResizedImageGenerator import com.owncloud.android.MainApp import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile @@ -185,7 +186,7 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag .getClientFor(user.toOwnCloudAccount(), MainApp.getAppContext()) } ThumbnailsCacheManager.setClient(client) - ThumbnailsCacheManager.doResizedImageInBackground(file, storageManager) + ResizedImageGenerator.getOrCreate(file, client, storageManager) } catch (t: Throwable) { Log_OC.e(TAG, "Server fetch failed for $file", t) null diff --git a/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt new file mode 100644 index 000000000000..e865bbc648b1 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt @@ -0,0 +1,135 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.thumbnail + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Point +import com.nextcloud.utils.AppConstants +import com.nextcloud.utils.extensions.getBigThumbnailKey +import com.nextcloud.utils.extensions.getPreviewEndpoint +import com.nextcloud.utils.extensions.getVideoPreviewEndpoint +import com.nextcloud.utils.extensions.isPNG +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.ThumbnailsCacheManager +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.files.model.ImageDimension +import com.owncloud.android.utils.BitmapUtils +import com.owncloud.android.utils.MimeTypeUtil +import org.apache.commons.httpclient.HttpStatus +import org.apache.commons.httpclient.methods.GetMethod + +object ResizedImageGenerator { + private const val TAG = "ResizedImageGenerator" + + @Suppress("MagicNumber") + private val VIDEO_PREVIEW_SIZES = listOf(1024, 512, 256, 128, 64) + + fun getOrCreate(file: OCFile, client: OwnCloudClient, storageManager: FileDataStorageManager): Bitmap? { + val cacheKey = file.getBigThumbnailKey() + val cachedImage = ThumbnailsCacheManager.getBitmapFromDiskCache(cacheKey) + if (cachedImage != null && !file.isUpdateThumbnailNeeded) { + Log_OC.d(TAG, "cache hit: ${file.remotePath}") + return cachedImage + } + + val screenSize = ThumbnailsCacheManager.getScreenDimension() + val resizedImage = if (file.isDown && MimeTypeUtil.isImage(file)) { + Log_OC.d(TAG, "creating from local file: ${file.remotePath}") + val decodedImage = BitmapUtils.decodeSampledBitmapFromFile(file.storagePath, screenSize.x, screenSize.y) + if (decodedImage == null) { + Log_OC.w(TAG, "local file decode failed, falling back to cached image: ${file.remotePath}") + cachedImage + } else { + resizeAndCacheLocalImage(file, cacheKey, decodedImage, screenSize) + } + } else { + Log_OC.d(TAG, "creating from server preview: ${file.remotePath}") + downloadAndCacheServerPreview(file, cacheKey, client, screenSize) + } + + if (resizedImage != null && file.imageDimension == null) { + saveImageDimension(file, resizedImage, storageManager) + } + + return resizedImage + } + + private fun resizeAndCacheLocalImage( + file: OCFile, + cacheKey: String, + decodedImage: Bitmap, + screenSize: Point + ): Bitmap? { + val image = if (file.isPNG()) { + BitmapUtils.centerCropOnPngBackground(decodedImage, screenSize.x, screenSize.y) + } else { + decodedImage + } + file.isUpdateThumbnailNeeded = false + return ThumbnailsCacheManager.addThumbnailToCache(cacheKey, image, file.storagePath, screenSize.x, screenSize.y) + } + + private fun downloadAndCacheServerPreview( + file: OCFile, + cacheKey: String, + client: OwnCloudClient, + screenSize: Point + ): Bitmap? { + val preview = if (MimeTypeUtil.isVideo(file)) { + downloadVideoPreview(file, client) + } else { + downloadPreview(file, client, client.getPreviewEndpoint(file.localId, screenSize.x, screenSize.y)) + } ?: return null + + val image = if (file.isPNG()) { + BitmapUtils.centerCropOnPngBackground(preview, preview.width, preview.height) + } else { + preview + } + ThumbnailsCacheManager.addBitmapToCache(cacheKey, image) + return image + } + + private fun downloadVideoPreview(file: OCFile, client: OwnCloudClient): Bitmap? = + VIDEO_PREVIEW_SIZES.firstNotNullOfOrNull { size -> + downloadPreview(file, client, client.getVideoPreviewEndpoint(file.localId, size)) + } + + @Suppress("TooGenericExceptionCaught") + private fun downloadPreview(file: OCFile, client: OwnCloudClient, uri: String): Bitmap? { + Log_OC.d(TAG, "downloading preview: ${file.remotePath}, uri: $uri") + + var getMethod: GetMethod? = null + return try { + getMethod = GetMethod(uri).apply { params.soTimeout = AppConstants.DEFAULT_READ_TIMEOUT_MS } + + val status = client.executeMethod(getMethod) + if (status != HttpStatus.SC_OK) { + Log_OC.e(TAG, "preview download failed, status: $status, file: ${file.remotePath}") + client.exhaustResponse(getMethod.responseBodyAsStream) + return null + } + + getMethod.responseBodyAsStream.use { BitmapFactory.decodeStream(it) } + } catch (e: Exception) { + Log_OC.e(TAG, "preview download failed: ${file.remotePath}", e) + null + } finally { + getMethod?.releaseConnection() + } + } + + private fun saveImageDimension(file: OCFile, image: Bitmap, storageManager: FileDataStorageManager) { + file.imageDimension = ImageDimension(image.width.toFloat(), image.height.toFloat()) + file.isUpdateThumbnailNeeded = false + storageManager.saveFile(file) + } +} diff --git a/app/src/main/java/com/nextcloud/utils/thumbnail/job/ResizedImageGenerationJob.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/job/ResizedImageGenerationJob.kt new file mode 100644 index 000000000000..1041b38ade6c --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/job/ResizedImageGenerationJob.kt @@ -0,0 +1,98 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.thumbnail.job + +import android.graphics.Bitmap +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.core.content.ContextCompat +import com.nextcloud.client.network.ConnectivityService +import com.nextcloud.repository.ClientRepository +import com.nextcloud.utils.thumbnail.ResizedImageGenerator +import com.nextcloud.utils.thumbnail.VideoOverlayGenerator +import com.owncloud.android.MainApp +import com.owncloud.android.R +import com.owncloud.android.datamodel.FileDataStorageManager +import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.datamodel.ThumbnailsCacheManager +import com.owncloud.android.lib.common.OwnCloudClient +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.preview.media.PreviewMediaFragment +import com.owncloud.android.utils.MimeTypeUtil +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class ResizedImageGenerationJob( + private val fragment: PreviewMediaFragment, + private val storageManager: FileDataStorageManager, + private val connectivityService: ConnectivityService, + private val clientRepository: ClientRepository, + private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO +) { + companion object { + private const val TAG = "ResizedImageGenerationJob" + } + + suspend fun load(file: OCFile, imageView: ImageView, progressContainer: FrameLayout) { + val image = getOrCreateDisplayImage(file) + if (image == null) { + Log_OC.e(TAG, "load failed, no resized image: ${file.remotePath}") + showLoadError() + return + } + + if (imageView.tag?.toString() != file.fileId.toString()) { + Log_OC.d(TAG, "load skipped, image view now shows another file: ${file.remotePath}") + return + } + + imageView.run { + visibility = View.VISIBLE + setImageBitmap(image) + setBackgroundColor(ContextCompat.getColor(context, R.color.background_color_inverse)) + } + progressContainer.visibility = View.GONE + Log_OC.d(TAG, "load completed: ${file.remotePath}") + } + + private suspend fun getOrCreateDisplayImage(file: OCFile): Bitmap? { + val client = clientRepository.getOwncloudClient() + if (client == null) { + Log_OC.e(TAG, "no client available: ${file.remotePath}") + return null + } + ThumbnailsCacheManager.setClient(client) + return withContext(ioDispatcher) { getOrCreateWithVideoOverlay(file, client) } + } + + @Suppress("TooGenericExceptionCaught") + private fun getOrCreateWithVideoOverlay(file: OCFile, client: OwnCloudClient): Bitmap? = try { + val resizedImage = ResizedImageGenerator.getOrCreate(file, client, storageManager) + if (resizedImage != null && MimeTypeUtil.isVideo(file)) { + VideoOverlayGenerator.addOverlay(resizedImage, MainApp.getAppContext()) + } else { + resizedImage + } + } catch (t: Throwable) { + Log_OC.e(TAG, "resized image generation crashed: ${file.remotePath}", t) + null + } + + private suspend fun showLoadError() { + val isInternetWalled = withContext(ioDispatcher) { connectivityService.isInternetWalled() } + if (isInternetWalled) { + Log_OC.d(TAG, "showing no connection error") + fragment.setNoConnectionErrorMessage() + } else { + Log_OC.d(TAG, "showing unsupported image error") + fragment.handleUnsupportedImage() + } + } +} 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 0e074633a851..f25f7466675c 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -25,18 +25,13 @@ import android.os.AsyncTask; import android.provider.MediaStore; import android.view.Display; -import android.view.View; import android.view.WindowManager; -import android.widget.FrameLayout; import android.widget.ImageView; import com.nextcloud.client.account.User; -import com.nextcloud.client.network.ConnectivityService; import com.nextcloud.utils.AppConstants; import com.nextcloud.utils.BitmapExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; -import com.nextcloud.utils.extensions.OCFileExtensionsKt; -import com.nextcloud.utils.extensions.OwnCloudClientExtensionsKt; import com.nextcloud.utils.thumbnail.VideoOverlayGenerator; import com.owncloud.android.MainApp; import com.owncloud.android.R; @@ -45,12 +40,9 @@ import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.files.model.ImageDimension; import com.owncloud.android.lib.resources.files.model.ServerFileInterface; import com.owncloud.android.lib.resources.trashbin.model.TrashbinFile; import com.owncloud.android.ui.adapter.DiskLruImageCache; -import com.owncloud.android.ui.fragment.FileFragment; -import com.owncloud.android.ui.preview.media.PreviewMediaFragment; import com.owncloud.android.utils.BitmapUtils; import com.owncloud.android.utils.FileStorageUtils; import com.owncloud.android.utils.MimeTypeUtil; @@ -77,8 +69,6 @@ * Manager for concurrent access to thumbnails cache. */ public final class ThumbnailsCacheManager { - private static final int[] VIDEO_PREVIEW_SIZES = {1024, 512, 256, 128, 64}; - /** Cache key prefix {@code }; sized from {@link #getScreenDimension()}. */ public static final String PREFIX_RESIZED_IMAGE = "r"; @@ -171,7 +161,7 @@ public static int getThumbnailDimension() { * * @return Point */ - private static Point getScreenDimension() { + public static Point getScreenDimension() { WindowManager wm = (WindowManager) MainApp.getAppContext().getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); Point point = new Point(); @@ -300,98 +290,6 @@ public static Bitmap getBitmapFromDiskCache(String key) { return null; } - public static class ResizedImageGenerationTask extends AsyncTask { - private final FileFragment fileFragment; - private final FileDataStorageManager storageManager; - private final User user; - private final WeakReference imageViewReference; - private final WeakReference frameLayoutReference; - private OCFile file; - private final ConnectivityService connectivityService; - private final int backgroundColor; - - - public ResizedImageGenerationTask(FileFragment fileFragment, - ImageView imageView, - FrameLayout emptyListProgress, - FileDataStorageManager storageManager, - ConnectivityService connectivityService, - User user, - int backgroundColor) throws IllegalArgumentException { - this.fileFragment = fileFragment; - imageViewReference = new WeakReference<>(imageView); - frameLayoutReference = new WeakReference<>(emptyListProgress); - this.storageManager = storageManager; - this.connectivityService = connectivityService; - this.user = user; - this.backgroundColor = backgroundColor; - } - - @Override - protected Bitmap doInBackground(Object... params) { - Bitmap thumbnail = null; - - file = (OCFile) params[0]; - - try { - mClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(user.toOwnCloudAccount(), - MainApp.getAppContext()); - - thumbnail = doResizedImageInBackground(file, storageManager); - - if (MimeTypeUtil.isVideo(file) && thumbnail != null) { - thumbnail = VideoOverlayGenerator.addOverlay(thumbnail, MainApp.getAppContext()); - } - - } catch (OutOfMemoryError oome) { - Log_OC.e(TAG, "Out of memory"); - } catch (Throwable t) { - // the app should never break due to a problem with thumbnails - Log_OC.e(TAG, "Generation of thumbnail for " + file + " failed", t); - } - - return thumbnail; - } - - protected void onPostExecute(Bitmap bitmap) { - if (imageViewReference.get() != null) { - final ImageView imageView = imageViewReference.get(); - final FrameLayout frameLayout = frameLayoutReference.get(); - - if (bitmap != null) { - final ResizedImageGenerationTask bitmapWorkerTask = getResizedImageGenerationWorkerTask(imageView); - - if (this == bitmapWorkerTask) { - String tagId = String.valueOf(file.getFileId()); - - if (String.valueOf(imageView.getTag()).equals(tagId)) { - imageView.setVisibility(View.VISIBLE); - imageView.setImageBitmap(bitmap); - imageView.setBackgroundColor(backgroundColor); - - if (frameLayout != null) { - frameLayout.setVisibility(View.GONE); - } - } - } - } else { - new Thread(() -> { - if (connectivityService.isInternetWalled()) { - if (fileFragment instanceof PreviewMediaFragment) { - ((PreviewMediaFragment) fileFragment).setNoConnectionErrorMessage(); - } - } else { - if (fileFragment instanceof PreviewMediaFragment) { - ((PreviewMediaFragment) fileFragment).handleUnsupportedImage(); - } - } - }).start(); - - } - } - } - } - public static class ThumbnailGenerationTaskObject { private final Object file; private final String imageKey; @@ -770,16 +668,6 @@ public static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) { return null; } - private static ResizedImageGenerationTask getResizedImageGenerationWorkerTask(ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncResizedImageDrawable asyncDrawable) { - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } - public static class AsyncThumbnailDrawable extends BitmapDrawable { private final WeakReference bitmapWorkerTaskReference; @@ -796,19 +684,6 @@ public ThumbnailGenerationTask getBitmapWorkerTask() { } } - public static class AsyncResizedImageDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; - - public AsyncResizedImageDrawable(Resources res, Bitmap bitmap, ResizedImageGenerationTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); - } - - private ResizedImageGenerationTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } - public static void generateResizedImage(OCFile file) { Point p = getScreenDimension(); int pxW = p.x; @@ -882,52 +757,6 @@ public static void generateThumbnailFromOCFile(OCFile file, User user, Context c } } - private static Bitmap downloadVideoPreview(OCFile file) { - for (int size : VIDEO_PREVIEW_SIZES) { - Bitmap preview = downloadPreview(file, - OwnCloudClientExtensionsKt.getVideoPreviewEndpoint(mClient, - file.getLocalId(), - size)); - if (preview != null) { - return preview; - } - } - - return null; - } - - private static Bitmap downloadPreview(OCFile file, String uri) { - Log_OC.d(TAG, "generating resized image: " + file.getFileName() + " URI: " + uri); - - GetMethod getMethod = null; - - try { - getMethod = new GetMethod(uri); - getMethod.getParams().setSoTimeout(AppConstants.DEFAULT_READ_TIMEOUT_MS); - - int status = mClient.executeMethod(getMethod); - if (status != HttpStatus.SC_OK) { - Log_OC.e(TAG, "cannot generate thumbnail not supported file type, status: " + status - + " file: " + file.getRemotePath()); - mClient.exhaustResponse(getMethod.getResponseBodyAsStream()); - return null; - } - - try (InputStream inputStream = getMethod.getResponseBodyAsStream()) { - Bitmap preview = BitmapFactory.decodeStream(inputStream); - Log_OC.d(TAG, "resized image generated"); - return preview; - } - } catch (Exception e) { - Log_OC.e(TAG, "doResizedBitmap: ", e); - return null; - } finally { - if (getMethod != null) { - getMethod.releaseConnection(); - } - } - } - @VisibleForTesting public static void clearCache() { synchronized (mThumbnailsDiskCacheLock) { @@ -941,56 +770,4 @@ public static void clearCache() { public static void setClient(OwnCloudClient client) { mClient = client; } - - public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageManager storageManager) { - Bitmap thumbnail; - String imageKey = FileExtensionsKt.getBigThumbnailKey(file); - - // Check disk cache in background thread - thumbnail = getBitmapFromDiskCache(imageKey); - if (thumbnail != null && !file.isUpdateThumbnailNeeded()) { - Log_OC.d(TAG, "Thumbnail found in cache"); - return thumbnail; - } - - Point p = getScreenDimension(); - int pxW = p.x; - int pxH = p.y; - - if (file.isDown() && MimeTypeUtil.isImage(file)) { - Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getStoragePath(), pxW, pxH); - if (bitmap != null) { - if (OCFileExtensionsKt.isPNG(file)) { - bitmap = BitmapUtils.centerCropOnPngBackground(bitmap, pxW, pxH); - } - thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), pxW, pxH); - file.setUpdateThumbnailNeeded(false); - } - } else if (mClient != null) { - thumbnail = MimeTypeUtil.isVideo(file) - ? downloadVideoPreview(file) - : downloadPreview(file, - OwnCloudClientExtensionsKt.getPreviewEndpoint(mClient, - file.getLocalId(), - pxW, - pxH)); - - if (thumbnail != null && PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) { - thumbnail = BitmapUtils.centerCropOnPngBackground(thumbnail, thumbnail.getWidth(), thumbnail.getHeight()); - } - - if (thumbnail != null) { - addBitmapToCache(imageKey, thumbnail); - } - } - - // resized dimensions and set update thumbnail needed to false to prevent rendering loop - if (thumbnail != null && file.getImageDimension() == null) { - file.setImageDimension(new ImageDimension(thumbnail.getWidth(), thumbnail.getHeight())); - file.setUpdateThumbnailNeeded(false); - storageManager.saveFile(file); - } - - return thumbnail; - } } diff --git a/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaFragment.kt index b044c70f6782..7acbfab9cedf 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaFragment.kt @@ -49,6 +49,7 @@ import com.nextcloud.client.di.Injectable import com.nextcloud.client.jobs.BackgroundJobManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.player.model.file.PlaybackCollection +import com.nextcloud.repository.ClientRepository import com.nextcloud.ui.fileactions.FileAction import com.nextcloud.ui.fileactions.FileActionsBottomSheet.Companion.newInstance import com.nextcloud.utils.SnackbarUtil @@ -57,14 +58,12 @@ import com.nextcloud.utils.extensions.getBigThumbnailKey import com.nextcloud.utils.extensions.getParcelableArgument import com.nextcloud.utils.extensions.getSmallThumbnail import com.nextcloud.utils.extensions.typedActivity +import com.nextcloud.utils.thumbnail.job.ResizedImageGenerationJob import com.nextcloud.utils.view.ScreenMetrics -import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.databinding.PreviewImageFragmentBinding import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.ThumbnailsCacheManager -import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncResizedImageDrawable -import com.owncloud.android.datamodel.ThumbnailsCacheManager.ResizedImageGenerationTask import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.operations.FetchRemoteFileOperation import com.owncloud.android.ui.activity.FileActivity @@ -76,6 +75,7 @@ import com.owncloud.android.utils.MimeTypeUtil import com.owncloud.android.utils.theme.ViewThemeUtils import edu.umd.cs.findbugs.annotations.SuppressFBWarnings import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import pl.droidsonroids.gif.GifDrawable @@ -108,10 +108,14 @@ class PreviewMediaFragment : private var ignoreFirstSavedState = false private var loadBitmapTask: LoadBitmapTask? = null + private var resizedImageJob: Job? = null @Inject lateinit var connectivityService: ConnectivityService + @Inject + lateinit var clientRepository: ClientRepository + @Inject lateinit var accountManager: UserAccountManager @@ -266,7 +270,7 @@ class PreviewMediaFragment : } private fun adjustResizedImage(thumbnail: Bitmap?, width: Int, height: Int) { - var resizedImage = getResizedBitmap(file, width, height) + val resizedImage = getResizedBitmap(file, width, height) if (resizedImage != null && !file.isUpdateThumbnailNeeded) { binding.image.setImageBitmap(resizedImage) @@ -275,31 +279,12 @@ class PreviewMediaFragment : binding.emptyListProgress.visibility = View.GONE binding.image.setBackgroundColor(resources.getColor(R.color.background_color_inverse)) } else { - // generate new resized image - if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, binding.image) && - containerActivity.storageManager != null - ) { - val task = - ResizedImageGenerationTask( - this, - binding.image, - binding.emptyListProgress, - containerActivity.storageManager, - connectivityService, - containerActivity.storageManager.user, - resources.getColor(R.color.background_color_inverse) - ) - if (resizedImage == null) { - resizedImage = thumbnail - } - val asyncDrawable = - AsyncResizedImageDrawable( - MainApp.getAppContext().resources, - resizedImage, - task - ) - binding.image.setImageDrawable(asyncDrawable) - task.execute(file) + val storageManager = containerActivity.storageManager ?: return + val job = ResizedImageGenerationJob(this, storageManager, connectivityService, clientRepository) + binding.image.setImageBitmap(resizedImage ?: thumbnail) + resizedImageJob?.cancel() + resizedImageJob = viewLifecycleOwner.lifecycleScope.launch { + job.load(file, binding.image, binding.emptyListProgress) } } } From e00baeca702bbae30db6449b60c3bf6ce8113fde Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 20 Oct 2026 12:30:24 +0200 Subject: [PATCH 3/4] deduplicate display size logic Signed-off-by: alperozturk96 --- .../jobs/gallery/GalleryImageGenerationJob.kt | 7 ++----- .../utils/thumbnail/ResizedImageGenerator.kt | 3 ++- .../com/nextcloud/utils/view/ScreenMetrics.kt | 9 +++++++++ .../android/datamodel/ThumbnailsCacheManager.java | 15 +++------------ 4 files changed, 16 insertions(+), 18 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 0bc56487db0c..e1027dd12fb9 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,12 +8,10 @@ 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 com.nextcloud.client.account.User import com.nextcloud.utils.extensions.getBigThumbnailKey @@ -22,6 +20,7 @@ import com.nextcloud.utils.extensions.isPNG import com.nextcloud.utils.extensions.setMediaThumbnail import com.nextcloud.utils.extensions.toFile import com.nextcloud.utils.thumbnail.ResizedImageGenerator +import com.nextcloud.utils.view.ScreenMetrics import com.owncloud.android.MainApp import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile @@ -143,9 +142,7 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } 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) + val p = ScreenMetrics.defaultDisplaySize() val pxW = p.x val pxH = p.y diff --git a/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt index e865bbc648b1..a72a8335c181 100644 --- a/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt @@ -15,6 +15,7 @@ import com.nextcloud.utils.extensions.getBigThumbnailKey import com.nextcloud.utils.extensions.getPreviewEndpoint import com.nextcloud.utils.extensions.getVideoPreviewEndpoint import com.nextcloud.utils.extensions.isPNG +import com.nextcloud.utils.view.ScreenMetrics import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.datamodel.ThumbnailsCacheManager @@ -40,7 +41,7 @@ object ResizedImageGenerator { return cachedImage } - val screenSize = ThumbnailsCacheManager.getScreenDimension() + val screenSize = ScreenMetrics.defaultDisplaySize() val resizedImage = if (file.isDown && MimeTypeUtil.isImage(file)) { Log_OC.d(TAG, "creating from local file: ${file.remotePath}") val decodedImage = BitmapUtils.decodeSampledBitmapFromFile(file.storagePath, screenSize.x, screenSize.y) diff --git a/app/src/main/java/com/nextcloud/utils/view/ScreenMetrics.kt b/app/src/main/java/com/nextcloud/utils/view/ScreenMetrics.kt index 1a8bca4676df..cee07c4fafaf 100644 --- a/app/src/main/java/com/nextcloud/utils/view/ScreenMetrics.kt +++ b/app/src/main/java/com/nextcloud/utils/view/ScreenMetrics.kt @@ -11,6 +11,8 @@ import android.app.Activity import android.content.Context import android.graphics.Point import android.util.DisplayMetrics +import android.view.WindowManager +import com.owncloud.android.MainApp object ScreenMetrics { @@ -20,6 +22,13 @@ object ScreenMetrics { activity?.windowManager?.defaultDisplay?.getSize(size) } + @JvmStatic + fun defaultDisplaySize(): Point = Point().also { size -> + val windowManager = MainApp.getAppContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager + @Suppress("DEPRECATION") + windowManager.defaultDisplay.getSize(size) + } + @JvmStatic fun dpToPx(dp: Float, context: Context): Int = (dp * (context.densityDpi / DisplayMetrics.DENSITY_DEFAULT)).toInt() 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 f25f7466675c..1016b24ec0b7 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -24,8 +24,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.provider.MediaStore; -import android.view.Display; -import android.view.WindowManager; import android.widget.ImageView; import com.nextcloud.client.account.User; @@ -33,6 +31,7 @@ import com.nextcloud.utils.BitmapExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; import com.nextcloud.utils.thumbnail.VideoOverlayGenerator; +import com.nextcloud.utils.view.ScreenMetrics; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.lib.common.OwnCloudAccount; @@ -69,7 +68,7 @@ * Manager for concurrent access to thumbnails cache. */ public final class ThumbnailsCacheManager { - /** Cache key prefix {@code }; sized from {@link #getScreenDimension()}. */ + /** Cache key prefix {@code }; sized from {@link ScreenMetrics#defaultDisplaySize()}. */ public static final String PREFIX_RESIZED_IMAGE = "r"; /** Cache key prefix {@code }; square, sized from {@link #getThumbnailDimension()}. */ @@ -161,14 +160,6 @@ public static int getThumbnailDimension() { * * @return Point */ - public static Point getScreenDimension() { - WindowManager wm = (WindowManager) MainApp.getAppContext().getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Point point = new Point(); - display.getSize(point); - return point; - } - /** * Add thumbnail to cache * @@ -685,7 +676,7 @@ public ThumbnailGenerationTask getBitmapWorkerTask() { } public static void generateResizedImage(OCFile file) { - Point p = getScreenDimension(); + Point p = ScreenMetrics.defaultDisplaySize(); int pxW = p.x; int pxH = p.y; String imageKey = FileExtensionsKt.getBigThumbnailKey(file); From 75c655a544d2b529df1394d5a311d54cbd20dc81 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 20 Oct 2026 12:36:29 +0200 Subject: [PATCH 4/4] replace deprecated api with display metrics Signed-off-by: alperozturk96 --- .../jobs/gallery/GalleryImageGenerationJob.kt | 2 +- .../utils/thumbnail/ResizedImageGenerator.kt | 2 +- .../com/nextcloud/utils/view/ScreenMetrics.kt | 21 +++++++------------ .../datamodel/ThumbnailsCacheManager.java | 4 ++-- 4 files changed, 12 insertions(+), 17 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 e1027dd12fb9..b37ebf1d03c6 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 @@ -142,7 +142,7 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag } private fun createImageThumbnail(file: OCFile): Bitmap? { - val p = ScreenMetrics.defaultDisplaySize() + val p = ScreenMetrics.size() val pxW = p.x val pxH = p.y diff --git a/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt index a72a8335c181..8e6a8c2bf340 100644 --- a/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/ResizedImageGenerator.kt @@ -41,7 +41,7 @@ object ResizedImageGenerator { return cachedImage } - val screenSize = ScreenMetrics.defaultDisplaySize() + val screenSize = ScreenMetrics.size() val resizedImage = if (file.isDown && MimeTypeUtil.isImage(file)) { Log_OC.d(TAG, "creating from local file: ${file.remotePath}") val decodedImage = BitmapUtils.decodeSampledBitmapFromFile(file.storagePath, screenSize.x, screenSize.y) diff --git a/app/src/main/java/com/nextcloud/utils/view/ScreenMetrics.kt b/app/src/main/java/com/nextcloud/utils/view/ScreenMetrics.kt index cee07c4fafaf..e77bccc7d5a7 100644 --- a/app/src/main/java/com/nextcloud/utils/view/ScreenMetrics.kt +++ b/app/src/main/java/com/nextcloud/utils/view/ScreenMetrics.kt @@ -7,26 +7,18 @@ package com.nextcloud.utils.view -import android.app.Activity import android.content.Context import android.graphics.Point import android.util.DisplayMetrics -import android.view.WindowManager import com.owncloud.android.MainApp object ScreenMetrics { @JvmStatic - fun size(activity: Activity?): Point = Point().also { size -> - @Suppress("DEPRECATION") - activity?.windowManager?.defaultDisplay?.getSize(size) - } - - @JvmStatic - fun defaultDisplaySize(): Point = Point().also { size -> - val windowManager = MainApp.getAppContext().getSystemService(Context.WINDOW_SERVICE) as WindowManager - @Suppress("DEPRECATION") - windowManager.defaultDisplay.getSize(size) + @JvmOverloads + fun size(context: Context? = null): Point { + val displayMetrics = (context ?: MainApp.getAppContext()).displayMetrics + return Point(displayMetrics.widthPixels, displayMetrics.heightPixels) } @JvmStatic @@ -35,6 +27,9 @@ object ScreenMetrics { @JvmStatic fun pxToDp(px: Int, context: Context): Float = px * (DisplayMetrics.DENSITY_DEFAULT / context.densityDpi) + private val Context.displayMetrics: DisplayMetrics + get() = resources.displayMetrics + private val Context.densityDpi: Float - get() = resources.displayMetrics.densityDpi.toFloat() + get() = displayMetrics.densityDpi.toFloat() } 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 1016b24ec0b7..ec0cdc6f10f6 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -68,7 +68,7 @@ * Manager for concurrent access to thumbnails cache. */ public final class ThumbnailsCacheManager { - /** Cache key prefix {@code }; sized from {@link ScreenMetrics#defaultDisplaySize()}. */ + /** Cache key prefix {@code }; sized from {@link ScreenMetrics#size()}. */ public static final String PREFIX_RESIZED_IMAGE = "r"; /** Cache key prefix {@code }; square, sized from {@link #getThumbnailDimension()}. */ @@ -676,7 +676,7 @@ public ThumbnailGenerationTask getBitmapWorkerTask() { } public static void generateResizedImage(OCFile file) { - Point p = ScreenMetrics.defaultDisplaySize(); + Point p = ScreenMetrics.size(); int pxW = p.x; int pxH = p.y; String imageKey = FileExtensionsKt.getBigThumbnailKey(file);