Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/unreleased/4946
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Thumbnail caching

Thumbnail caching system has been improved for a better experience and performance.

https://github.com/owncloud/android/pull/4946
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class ThumbnailsCacheManager {
private static DiskLruImageCache mThumbnailCache = null;
private static boolean mThumbnailCacheStarting = true;

private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 250; // 250MB
private static final CompressFormat mCompressFormat = CompressFormat.JPEG;
private static final int mCompressQuality = 70;
private static OwnCloudClient mClient = null;
Expand Down Expand Up @@ -447,8 +447,7 @@ public static boolean cancelPotentialThumbnailWork(Object file, ImageView imageV
private static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncThumbnailDrawable) {
final AsyncThumbnailDrawable asyncDrawable = (AsyncThumbnailDrawable) drawable;
if (drawable instanceof AsyncThumbnailDrawable asyncDrawable) {
return asyncDrawable.getBitmapWorkerTask();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ package com.owncloud.android.presentation.files.filelist

import android.accounts.Account
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
Expand All @@ -42,7 +41,8 @@ import com.owncloud.android.R
import com.owncloud.android.databinding.GridItemBinding
import com.owncloud.android.databinding.ItemFileListBinding
import com.owncloud.android.databinding.ListFooterBinding
import com.owncloud.android.datamodel.ThumbnailsCacheManager
import com.owncloud.android.presentation.thumbnails.ThumbnailsRequester
import coil.load
import com.owncloud.android.domain.files.model.FileListOption
import com.owncloud.android.domain.files.model.OCFileWithSyncInfo
import com.owncloud.android.domain.files.model.OCFooterFile
Expand Down Expand Up @@ -188,7 +188,6 @@ class FileListAdapter(
val fileIcon = holder.itemView.findViewById<ImageView>(R.id.thumbnail).apply {
tag = file.id
}
val thumbnail: Bitmap? = file.remoteId?.let { ThumbnailsCacheManager.getBitmapFromDiskCache(file.remoteId) }

holder.itemView.findViewById<LinearLayout>(R.id.ListItemLayout)?.apply {
contentDescription = "LinearLayout-$name"
Expand All @@ -203,7 +202,7 @@ class FileListAdapter(
holder.itemView.findViewById<ImageView>(R.id.shared_via_users_icon).isVisible =
file.sharedWithSharee == true || file.isSharedWithMe

setSpecificViewHolder(viewType, holder, fileWithSyncInfo, thumbnail)
setSpecificViewHolder(viewType, holder, fileWithSyncInfo)

setIconPinAccordingToFilesLocalState(holder.itemView.findViewById(R.id.localFileIndicator), fileWithSyncInfo)

Expand Down Expand Up @@ -237,22 +236,12 @@ class FileListAdapter(
// Folder
fileIcon.setImageResource(R.drawable.ic_menu_archive)
} else {
// Set file icon depending on its mimetype. Ask for thumbnail later.
fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))

if (thumbnail != null) {
fileIcon.setImageBitmap(thumbnail)
}
if (file.needsToUpdateThumbnail && ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, fileIcon)) {
// generate new Thumbnail
val task = ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon, account)
val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(context.resources, thumbnail, task)

// If drawable is not visible, do not update it.
if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) {
fileIcon.setImageDrawable(asyncDrawable)
}
task.execute(file)
fileIcon.load(
ThumbnailsRequester.getPreviewUriForFile(fileWithSyncInfo, account!!),
ThumbnailsRequester.getCoilImageLoader()
) {
placeholder(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
error(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
}

if (file.mimeType == "image/png") {
Expand All @@ -272,7 +261,7 @@ class FileListAdapter(
}
}

private fun setSpecificViewHolder(viewType: Int, holder: RecyclerView.ViewHolder, fileWithSyncInfo: OCFileWithSyncInfo, thumbnail: Bitmap?) {
private fun setSpecificViewHolder(viewType: Int, holder: RecyclerView.ViewHolder, fileWithSyncInfo: OCFileWithSyncInfo) {
val file = fileWithSyncInfo.file

when (viewType) {
Expand Down Expand Up @@ -327,23 +316,14 @@ class FileListAdapter(
val fileIcon = holder.itemView.findViewById<ImageView>(R.id.thumbnail)
val layoutParams = fileIcon.layoutParams as ViewGroup.MarginLayoutParams

if (thumbnail == null) {
view.binding.Filename.text = file.fileName
// Reset layout params values default
manageGridLayoutParams(
layoutParams = layoutParams,
marginVertical = 0,
height = context.resources.getDimensionPixelSize(R.dimen.item_file_grid_height),
width = context.resources.getDimensionPixelSize(R.dimen.item_file_grid_width),
)
} else {
manageGridLayoutParams(
layoutParams = layoutParams,
marginVertical = context.resources.getDimensionPixelSize(R.dimen.item_file_image_grid_margin),
height = ViewGroup.LayoutParams.MATCH_PARENT,
width = ViewGroup.LayoutParams.MATCH_PARENT,
)
}
view.binding.Filename.text = file.fileName
// Reset layout params values default
manageGridLayoutParams(
layoutParams = layoutParams,
marginVertical = 0,
height = context.resources.getDimensionPixelSize(R.dimen.item_file_grid_height),
width = context.resources.getDimensionPixelSize(R.dimen.item_file_grid_width),
)
}
}
}
Expand Down Expand Up @@ -412,7 +392,7 @@ class FileListAdapter(
}

filesCount == 1 -> {
when {
when {
foldersCount <= 0 -> {
context.getString(R.string.file_list__footer__file)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import com.owncloud.android.lib.common.http.HttpConstants.AUTHORIZATION_HEADER
import com.owncloud.android.lib.common.http.HttpConstants.OC_X_REQUEST_ID
import com.owncloud.android.lib.common.http.HttpConstants.USER_AGENT_HEADER
import com.owncloud.android.lib.common.utils.RandomUtils
import com.owncloud.android.lib.common.accounts.AccountUtils as AppAccountUtils
import com.owncloud.android.presentation.authentication.AccountUtils
import okhttp3.Headers.Companion.toHeaders
import okhttp3.Interceptor
Expand All @@ -52,23 +53,37 @@ object ThumbnailsRequester : KoinComponent {
private val clientManager: ClientManager by inject()

private const val SPACE_SPECIAL_PREVIEW_URI = "%s?scalingup=0&a=1&x=%d&y=%d&c=%s&preview=1"
private const val FILE_PREVIEW_URI = "%s%s?x=%d&y=%d&c=%s&preview=1&id=%s"
private const val FILE_PREVIEW_URI = "%s/%s?x=%d&y=%d&c=%s&preview=1&id=%s"

private const val DISK_CACHE_SIZE: Long = 1024 * 1024 * 10 // 10MB
private const val DISK_CACHE_SIZE: Long = 1024 * 1024 * 250 // 250MB

private var imageLoader: ImageLoader? = null
private var lastAccountName: String? = null

@Synchronized
fun getCoilImageLoader(): ImageLoader {
val ownCloudClient = getOwnCloudClient()
val currentAccount = AccountUtils.getCurrentOwnCloudAccount(appContext)
val currentAccountName = currentAccount?.name

if (imageLoader != null && lastAccountName == currentAccountName) {
return imageLoader!!
}

val ownCloudClient = clientManager.getClientForCoilThumbnails(
accountName = currentAccountName ?: ""
)

val coilRequestHeaderInterceptor = CoilRequestHeaderInterceptor(
requestHeaders = hashMapOf(
AUTHORIZATION_HEADER to ownCloudClient.credentials.headerAuth,
AUTHORIZATION_HEADER to (ownCloudClient.credentials?.headerAuth ?: ""),
ACCEPT_ENCODING_HEADER to ACCEPT_ENCODING_IDENTITY,
USER_AGENT_HEADER to SingleSessionManager.getUserAgent(),
OC_X_REQUEST_ID to RandomUtils.generateRandomUUID(),
)
)

return ImageLoader(appContext).newBuilder().okHttpClient(
lastAccountName = currentAccountName
imageLoader = ImageLoader(appContext).newBuilder().okHttpClient(
okHttpClient = ownCloudClient.okHttpClient.newBuilder().addNetworkInterceptor(coilRequestHeaderInterceptor).build()
).logger(DebugLogger())
.memoryCache {
Expand All @@ -83,6 +98,8 @@ object ThumbnailsRequester : KoinComponent {
.build()
}
.build()

return imageLoader!!
}

fun getPreviewUriForSpaceSpecial(spaceSpecial: SpaceSpecial): String {
Expand All @@ -99,9 +116,8 @@ object ThumbnailsRequester : KoinComponent {
}

fun getPreviewUriForFile(ocFile: OCFileWithSyncInfo, account: Account): String {
var baseUrl = getOwnCloudClient().baseUri.toString() + "/remote.php/dav/files/" + account.name.split("@".toRegex())
.dropLastWhile { it.isEmpty() }
.toTypedArray()[0]
var baseUrl = getOwnCloudClient().baseUri.toString() + "/remote.php/dav/files/" +
AppAccountUtils.getUserId(account, appContext)
ocFile.space?.getSpaceSpecialImage()?.let {
baseUrl = it.webDavUrl
}
Expand All @@ -111,8 +127,8 @@ object ThumbnailsRequester : KoinComponent {
return String.format(
Locale.ROOT,
FILE_PREVIEW_URI,
baseUrl,
Uri.encode(ocFile.file.remotePath, "/"),
baseUrl.removeSuffix("/"),
Uri.encode(ocFile.file.remotePath, "/").removePrefix("/"),
fileThumbnailSize,
fileThumbnailSize,
ocFile.file.etag,
Expand All @@ -121,7 +137,7 @@ object ThumbnailsRequester : KoinComponent {
}

private fun getOwnCloudClient() = clientManager.getClientForCoilThumbnails(
accountName = AccountUtils.getCurrentOwnCloudAccount(appContext).name
accountName = AccountUtils.getCurrentOwnCloudAccount(appContext)?.name ?: ""
)

private class CoilRequestHeaderInterceptor(
Expand Down