Skip to content

Commit 8fb36bf

Browse files
author
zerox80
committed
fix(cache): add LruCache and null-safe disk cache init for thumbnails
1 parent f31f04e commit 8fb36bf

File tree

8 files changed

+334
-105
lines changed

8 files changed

+334
-105
lines changed

opencloudApp/src/main/java/eu/opencloud/android/MainApp.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ class MainApp : Application() {
9999

100100
appContext = applicationContext
101101

102+
// Ensure Logcat shows Timber logs in debug builds
103+
if (BuildConfig.DEBUG) {
104+
try {
105+
Timber.plant(Timber.DebugTree())
106+
} catch (_: Throwable) {
107+
// ignore if already planted
108+
}
109+
}
110+
102111
startLogsIfEnabled()
103112

104113
DebugInjector.injectDebugTools(appContext)

opencloudApp/src/main/java/eu/opencloud/android/datamodel/ThumbnailsCacheManager.java

Lines changed: 212 additions & 48 deletions
Large diffs are not rendered by default.

opencloudApp/src/main/java/eu/opencloud/android/presentation/files/details/FileDetailsFragment.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -428,8 +428,7 @@ class FileDetailsFragment : FileFragment() {
428428
}
429429
}
430430
if (ocFile.isImage) {
431-
val tagId = ocFile.remoteId.toString()
432-
var thumbnail: Bitmap? = ThumbnailsCacheManager.getBitmapFromDiskCache(tagId)
431+
var thumbnail: Bitmap? = ThumbnailsCacheManager.getBitmapFromDiskCache(ocFile)
433432
if (thumbnail != null && !ocFile.needsToUpdateThumbnail) {
434433
imageView.setImageBitmap(thumbnail)
435434
} else {
@@ -441,7 +440,7 @@ class FileDetailsFragment : FileFragment() {
441440
}
442441
val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(MainApp.appContext.resources, thumbnail, task)
443442
imageView.setImageDrawable(asyncDrawable)
444-
task.execute(ocFile)
443+
ThumbnailsCacheManager.executeThumbnailTask(task, ocFile)
445444
}
446445
}
447446
} else {

opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/FileListAdapter.kt

Lines changed: 89 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,19 @@ class FileListAdapter(
6060
var files = mutableListOf<Any>()
6161
private var account: Account? = AccountUtils.getCurrentOpenCloudAccount(context)
6262
private var fileListOption: FileListOption = FileListOption.ALL_FILES
63+
private val disallowTouchesWithOtherWindows =
64+
PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
65+
66+
init {
67+
setHasStableIds(true)
68+
}
6369

6470
fun updateFileList(filesToAdd: List<OCFileWithSyncInfo>, fileListOption: FileListOption) {
6571

6672
val listWithFooter = mutableListOf<Any>()
6773
listWithFooter.addAll(filesToAdd)
6874

69-
if (listWithFooter.isNotEmpty()) {
75+
if (listWithFooter.isNotEmpty() && !isPickerMode) {
7076
listWithFooter.add(OCFooterFile(manageListOfFilesAndGenerateText(filesToAdd)))
7177
}
7278

@@ -85,13 +91,22 @@ class FileListAdapter(
8591
diffResult.dispatchUpdatesTo(this)
8692
}
8793

94+
override fun getItemId(position: Int): Long {
95+
val item = files.getOrNull(position)
96+
return when (item) {
97+
is OCFileWithSyncInfo -> item.file.id ?: item.file.remotePath.hashCode().toLong()
98+
is OCFooterFile -> Long.MIN_VALUE + position
99+
else -> position.toLong()
100+
}
101+
}
102+
88103
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
89104
when (viewType) {
90105
ViewType.LIST_ITEM.ordinal -> {
91106
val binding = ItemFileListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
92107
binding.root.apply {
93108
tag = ViewType.LIST_ITEM
94-
filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
109+
filterTouchesWhenObscured = disallowTouchesWithOtherWindows
95110
}
96111
ListViewHolder(binding)
97112
}
@@ -100,7 +115,7 @@ class FileListAdapter(
100115
val binding = GridItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
101116
binding.root.apply {
102117
tag = ViewType.GRID_IMAGE
103-
filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
118+
filterTouchesWhenObscured = disallowTouchesWithOtherWindows
104119
}
105120
GridImageViewHolder(binding)
106121
}
@@ -109,7 +124,7 @@ class FileListAdapter(
109124
val binding = GridItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
110125
binding.root.apply {
111126
tag = ViewType.GRID_ITEM
112-
filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
127+
filterTouchesWhenObscured = disallowTouchesWithOtherWindows
113128
}
114129
GridViewHolder(binding)
115130
}
@@ -118,17 +133,19 @@ class FileListAdapter(
118133
val binding = ListFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false)
119134
binding.root.apply {
120135
tag = ViewType.FOOTER
121-
filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
136+
filterTouchesWhenObscured = disallowTouchesWithOtherWindows
122137
}
123138
FooterViewHolder(binding)
124139
}
125140
}
126141

127142
override fun getItemCount(): Int = files.size
128143

129-
override fun getItemId(position: Int): Long = position.toLong()
144+
private fun hasFooter(): Boolean = files.lastOrNull() is OCFooterFile
130145

131-
private fun isFooter(position: Int) = position == files.size.minus(1)
146+
private fun isFooter(position: Int) = files.getOrNull(position) is OCFooterFile
147+
148+
private fun selectableItemCount(): Int = files.size - if (hasFooter()) 1 else 0
132149

133150
override fun getItemViewType(position: Int): Int =
134151

@@ -166,33 +183,44 @@ class FileListAdapter(
166183

167184
fun selectAll() {
168185
// Last item on list is the footer, so that element must be excluded from selection
169-
selectAll(totalItems = files.size - 1)
186+
selectAll(totalItems = selectableItemCount())
170187
}
171188

172189
fun selectInverse() {
173190
// Last item on list is the footer, so that element must be excluded from selection
174-
toggleSelectionInBulk(totalItems = files.size - 1)
191+
toggleSelectionInBulk(totalItems = selectableItemCount())
175192
}
176193

177194
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
178195

179196
val viewType = getItemViewType(position)
180197

198+
AccountUtils.getCurrentOpenCloudAccount(context)?.let { currentAccount ->
199+
if (currentAccount != account) {
200+
account = currentAccount
201+
}
202+
} ?: run {
203+
if (account != null) {
204+
account = null
205+
}
206+
}
207+
181208
if (viewType != ViewType.FOOTER.ordinal) { // Is Item
182209

210+
val hasActiveSelection = selectedItemCount > 0
183211
val fileWithSyncInfo = files[position] as OCFileWithSyncInfo
184212
val file = fileWithSyncInfo.file
185213
val name = file.fileName
186214
val fileIcon = holder.itemView.findViewById<ImageView>(R.id.thumbnail).apply {
187215
tag = file.id
188216
}
189-
val thumbnail: Bitmap? = file.remoteId?.let { ThumbnailsCacheManager.getBitmapFromDiskCache(file.remoteId) }
217+
val thumbnail: Bitmap? = ThumbnailsCacheManager.getBitmapFromDiskCache(file)
190218

191219
holder.itemView.findViewById<LinearLayout>(R.id.ListItemLayout)?.apply {
192220
contentDescription = "LinearLayout-$name"
193221

194222
// Allow or disallow touches with other visible windows
195-
filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
223+
filterTouchesWhenObscured = disallowTouchesWithOtherWindows
196224
}
197225

198226
holder.itemView.findViewById<LinearLayout>(R.id.share_icons_layout).isVisible =
@@ -201,26 +229,35 @@ class FileListAdapter(
201229
holder.itemView.findViewById<ImageView>(R.id.shared_via_users_icon).isVisible =
202230
file.sharedWithSharee == true || file.isSharedWithMe
203231

204-
setSpecificViewHolder(viewType, holder, fileWithSyncInfo, thumbnail)
232+
setSpecificViewHolder(viewType, holder, fileWithSyncInfo, thumbnail, hasActiveSelection)
205233

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

208236
holder.itemView.setOnClickListener {
237+
val adapterPosition = holder.bindingAdapterPosition
238+
if (adapterPosition == RecyclerView.NO_POSITION) {
239+
return@setOnClickListener
240+
}
241+
val currentItem = files.getOrNull(adapterPosition) as? OCFileWithSyncInfo ?: return@setOnClickListener
209242
listener.onItemClick(
210-
ocFileWithSyncInfo = fileWithSyncInfo,
211-
position = position
243+
ocFileWithSyncInfo = currentItem,
244+
position = adapterPosition
212245
)
213246
}
214247

215248
holder.itemView.setOnLongClickListener {
249+
val adapterPosition = holder.bindingAdapterPosition
250+
if (adapterPosition == RecyclerView.NO_POSITION) {
251+
return@setOnLongClickListener false
252+
}
216253
listener.onLongItemClick(
217-
position = position
254+
position = adapterPosition
218255
)
219256
}
220257
holder.itemView.setBackgroundColor(Color.WHITE)
221258

222259
val checkBoxV = holder.itemView.findViewById<ImageView>(R.id.custom_checkbox).apply {
223-
isVisible = getCheckedItems().isNotEmpty()
260+
isVisible = hasActiveSelection
224261
}
225262

226263
if (isSelected(position)) {
@@ -234,27 +271,36 @@ class FileListAdapter(
234271
if (file.isFolder) {
235272
// Folder
236273
fileIcon.setImageResource(R.drawable.ic_menu_archive)
274+
fileIcon.setBackgroundColor(Color.TRANSPARENT)
237275
} else {
238276
// Set file icon depending on its mimetype. Ask for thumbnail later.
239277
fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
240278

241279
if (thumbnail != null) {
242280
fileIcon.setImageBitmap(thumbnail)
243281
}
244-
if (file.needsToUpdateThumbnail && ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, fileIcon)) {
245-
// generate new Thumbnail
246-
val task = ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon, account)
247-
val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(context.resources, thumbnail, task)
248-
249-
// If drawable is not visible, do not update it.
250-
if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) {
251-
fileIcon.setImageDrawable(asyncDrawable)
282+
283+
if (file.needsToUpdateThumbnail) {
284+
val canStartTask = ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, fileIcon)
285+
val activeAccount = account
286+
if (activeAccount != null && canStartTask) {
287+
// generate new Thumbnail
288+
val task = ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon, activeAccount)
289+
val placeholder = thumbnail ?: ThumbnailsCacheManager.mDefaultImg
290+
val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(context.resources, placeholder, task)
291+
292+
// If drawable is not visible, do not update it.
293+
if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) {
294+
fileIcon.setImageDrawable(asyncDrawable)
295+
}
296+
ThumbnailsCacheManager.executeThumbnailTask(task, file)
252297
}
253-
task.execute(file)
254298
}
255299

256-
if (file.mimeType == "image/png") {
300+
if (file.mimeType.equals("image/png", ignoreCase = true)) {
257301
fileIcon.setBackgroundColor(ContextCompat.getColor(context, R.color.background_color))
302+
} else {
303+
fileIcon.setBackgroundColor(Color.TRANSPARENT)
258304
}
259305
}
260306

@@ -270,18 +316,24 @@ class FileListAdapter(
270316
}
271317
}
272318

273-
private fun setSpecificViewHolder(viewType: Int, holder: RecyclerView.ViewHolder, fileWithSyncInfo: OCFileWithSyncInfo, thumbnail: Bitmap?) {
319+
private fun setSpecificViewHolder(
320+
viewType: Int,
321+
holder: RecyclerView.ViewHolder,
322+
fileWithSyncInfo: OCFileWithSyncInfo,
323+
thumbnail: Bitmap?,
324+
hasActiveSelection: Boolean,
325+
) {
274326
val file = fileWithSyncInfo.file
275327

276328
when (viewType) {
277329
ViewType.LIST_ITEM.ordinal -> {
278330
val view = holder as ListViewHolder
279331
view.binding.let {
280-
it.fileListConstraintLayout.filterTouchesWhenObscured = PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(context)
332+
it.fileListConstraintLayout.filterTouchesWhenObscured = disallowTouchesWithOtherWindows
281333
it.Filename.text = file.fileName
282334
it.fileListSize.text = DisplayUtils.bytesToHumanReadable(file.length, context, true)
283335
it.fileListLastMod.text = DisplayUtils.getRelativeTimestamp(context, file.modificationTimestamp)
284-
it.threeDotMenu.isVisible = getCheckedItems().isEmpty()
336+
it.threeDotMenu.isVisible = !hasActiveSelection
285337
it.threeDotMenu.contentDescription = context.getString(R.string.content_description_file_operations, file.fileName)
286338
if (fileListOption.isAvailableOffline() || (fileListOption.isSharedByLink() && fileWithSyncInfo.space == null)) {
287339
it.spacePathLine.path.apply {
@@ -321,7 +373,10 @@ class FileListAdapter(
321373
val layoutParams = fileIcon.layoutParams as ViewGroup.MarginLayoutParams
322374

323375
if (thumbnail == null) {
324-
view.binding.Filename.text = file.fileName
376+
view.binding.Filename.apply {
377+
text = file.fileName
378+
isVisible = true
379+
}
325380
// Reset layout params values default
326381
manageGridLayoutParams(
327382
layoutParams = layoutParams,
@@ -330,6 +385,10 @@ class FileListAdapter(
330385
width = context.resources.getDimensionPixelSize(R.dimen.item_file_grid_width),
331386
)
332387
} else {
388+
view.binding.Filename.apply {
389+
text = ""
390+
isVisible = false
391+
}
333392
manageGridLayoutParams(
334393
layoutParams = layoutParams,
335394
marginVertical = context.resources.getDimensionPixelSize(R.dimen.item_file_image_grid_margin),

opencloudApp/src/main/java/eu/opencloud/android/presentation/files/filelist/MainFileListFragment.kt

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -603,29 +603,28 @@ class MainFileListFragment : Fragment(),
603603
} else {
604604
// Set file icon depending on its mimetype. Ask for thumbnail later.
605605
thumbnailBottomSheet.setImageResource(MimetypeIconUtil.getFileTypeIconId(file.mimeType, file.fileName))
606-
if (file.remoteId != null) {
607-
val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.remoteId)
608-
if (thumbnail != null) {
609-
thumbnailBottomSheet.setImageBitmap(thumbnail)
610-
}
611-
if (file.needsToUpdateThumbnail && ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailBottomSheet)) {
612-
// generate new Thumbnail
613-
val task = ThumbnailsCacheManager.ThumbnailGenerationTask(
614-
thumbnailBottomSheet,
615-
AccountUtils.getCurrentOpenCloudAccount(requireContext())
616-
)
617-
val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(resources, thumbnail, task)
618606

619-
// If drawable is not visible, do not update it.
620-
if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) {
621-
thumbnailBottomSheet.setImageDrawable(asyncDrawable)
622-
}
623-
task.execute(file)
624-
}
607+
val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file)
608+
if (thumbnail != null) {
609+
thumbnailBottomSheet.setImageBitmap(thumbnail)
610+
}
611+
if (file.needsToUpdateThumbnail && ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, thumbnailBottomSheet)) {
612+
// generate new Thumbnail
613+
val task = ThumbnailsCacheManager.ThumbnailGenerationTask(
614+
thumbnailBottomSheet,
615+
AccountUtils.getCurrentOpenCloudAccount(requireContext())
616+
)
617+
val asyncDrawable = ThumbnailsCacheManager.AsyncThumbnailDrawable(resources, thumbnail, task)
625618

626-
if (file.mimeType == "image/png") {
627-
thumbnailBottomSheet.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.background_color))
619+
// If drawable is not visible, do not update it.
620+
if (asyncDrawable.minimumHeight > 0 && asyncDrawable.minimumWidth > 0) {
621+
thumbnailBottomSheet.setImageDrawable(asyncDrawable)
628622
}
623+
ThumbnailsCacheManager.executeThumbnailTask(task, file)
624+
}
625+
626+
if (file.mimeType == "image/png") {
627+
thumbnailBottomSheet.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.background_color))
629628
}
630629
}
631630

opencloudApp/src/main/java/eu/opencloud/android/presentation/files/removefile/RemoveFilesDialogFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ class RemoveFilesDialogFragment : DialogFragment() {
121121
if (files.size == 1) {
122122
val file = files[0]
123123
// Show the thumbnail when the file has one
124-
val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.remoteId)
124+
val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file)
125125
if (thumbnail != null) {
126126
thumbnailImageView.setImageBitmap(thumbnail)
127127
} else {

opencloudApp/src/main/java/eu/opencloud/android/presentation/sharing/ShareFileFragment.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,7 @@ class ShareFileFragment : Fragment(), ShareUserListAdapter.ShareUserAdapterListe
237237
)
238238
)
239239
if (file!!.isImage) {
240-
val remoteId = file?.remoteId.toString()
241-
val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(remoteId)
240+
val thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file!!)
242241
if (thumbnail != null) {
243242
binding.shareFileIcon.setImageBitmap(thumbnail)
244243
}

opencloudApp/src/main/java/eu/opencloud/android/ui/adapter/ReceiveExternalFilesAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public View getView(int position, View convertView, ViewGroup parent) {
167167
task
168168
);
169169
fileIcon.setImageDrawable(asyncDrawable);
170-
task.execute(file);
170+
ThumbnailsCacheManager.executeThumbnailTask(task, file);
171171
}
172172
}
173173
} else {

0 commit comments

Comments
 (0)