@@ -427,18 +427,32 @@ abstract class CryptoImplDecorator(
427427						}
428428					}
429429					thumbnailWriter.flush()
430- 					closeQuietly(thumbnailWriter)
431430				}
432431			} finally  {
433432				encryptedTmpFile.delete()
434- 				if  (genThumbnail) {
435- 					futureThumbnail.get()
436- 				}
437433				progressAware.onProgress(Progress .completed(DownloadState .decryption(cryptoFile)))
438434			}
439435
436+ 			//  Close thumbnail writer first, then wait for thumbnail generation to complete
437+ 			if  (genThumbnail) {
438+ 				closeQuietly(thumbnailWriter)
439+ 				try  {
440+ 					futureThumbnail.get(5 , java.util.concurrent.TimeUnit .SECONDS ) //  Add timeout to prevent hanging
441+ 				} catch  (e:  java.util.concurrent.TimeoutException ) {
442+ 					Timber .w(" Thumbnail generation timed out for ${cryptoFile.name} "  )
443+ 					futureThumbnail.cancel(true )
444+ 				} catch  (e:  Exception ) {
445+ 					Timber .w(e, " Error waiting for thumbnail generation for ${cryptoFile.name} "  )
446+ 				}
447+ 			}
440448			closeQuietly(thumbnailReader)
441449		} catch  (e:  IOException ) {
450+ 			//  Don't treat thumbnail-related pipe closed errors as fatal
451+ 			if  (e.message?.contains(" Pipe closed"  ) ==  true  &&  genThumbnail) {
452+ 				Timber .d(" Pipe closed during thumbnail generation (expected): ${cryptoFile.name} "  )
453+ 				//  The file was successfully decrypted, just the thumbnail failed
454+ 				return 
455+ 			}
442456			throw  FatalBackendException (e)
443457		}
444458	}
@@ -456,24 +470,68 @@ abstract class CryptoImplDecorator(
456470			try  {
457471				val  options =  BitmapFactory .Options ()
458472				val  thumbnailBitmap:  Bitmap ? 
459- 				options.inSampleSize =  4  //  pixel number reduced by a factor of 1/16
473+ 
474+ 				//  Use aggressive sampling for memory efficiency with large images
475+ 				//  Estimate file size and adjust sample size accordingly
476+ 				val  fileSize =  cryptoFile.size ? :  0L 
477+ 				val  fileSizeMB =  fileSize /  (1024  *  1024 )
478+ 
479+ 				//  Calculate sample size based on file size to prevent OOM
480+ 				var  sampleSize =  when  {
481+ 					fileSizeMB >  50  ->  16   //  1/256 of original size for very large files
482+ 					fileSizeMB >  30  ->  12   //  1/144 of original size for large files
483+ 					fileSizeMB >  20  ->  8    //  1/64 of original size for medium-large files
484+ 					fileSizeMB >  10  ->  6    //  1/36 of original size for medium files
485+ 					else  ->  4               //  1/16 of original size for smaller files
486+ 				}
487+ 
488+ 				options.inSampleSize =  sampleSize
489+ 				options.inPreferredConfig =  Bitmap .Config .RGB_565  //  Use less memory than ARGB_8888
490+ 				options.inDither =  false 
491+ 				options.inPurgeable =  true  //  Allow system to purge bitmap from memory if needed
492+ 				options.inInputShareable =  true 
493+ 
494+ 				Timber .d(" Generating thumbnail for ${cryptoFile.name}  (${fileSizeMB} MB) with sampleSize: $sampleSize "  )
495+ 
460496				val  bitmap =  BitmapFactory .decodeStream(thumbnailReader, null , options)
461497				if  (bitmap ==  null ) {
462498					closeQuietly(thumbnailReader)
499+ 					Timber .w(" Failed to decode bitmap for thumbnail generation: ${cryptoFile.name} "  )
463500					return @submit
464501				}
465502
466503				val  thumbnailWidth =  100 
467504				val  thumbnailHeight =  100 
468505				thumbnailBitmap =  ThumbnailUtils .extractThumbnail(bitmap, thumbnailWidth, thumbnailHeight)
506+ 
507+ 				//  Clean up the original bitmap to free memory immediately
508+ 				if  (bitmap !=  thumbnailBitmap) {
509+ 					bitmap.recycle()
510+ 				}
511+ 
469512				if  (thumbnailBitmap !=  null ) {
470513					storeThumbnail(diskCache, cacheKey, thumbnailBitmap)
514+ 					thumbnailBitmap.recycle() //  Clean up thumbnail bitmap after storing
471515				}
472516				closeQuietly(thumbnailReader)
473517
474518				cryptoFile.thumbnail =  diskCache[cacheKey]
519+ 				Timber .d(" Successfully generated thumbnail for ${cryptoFile.name} "  )
520+ 			} catch  (e:  OutOfMemoryError ) {
521+ 				closeQuietly(thumbnailReader)
522+ 				Timber .e(e, " OutOfMemoryError during thumbnail generation for large image: ${cryptoFile.name}  (${(cryptoFile.size ? :  0L ) /  (1024  *  1024 )} MB)"  )
523+ 				//  Try to recover by forcing garbage collection
524+ 				System .gc()
525+ 			} catch  (e:  java.io.IOException ) {
526+ 				closeQuietly(thumbnailReader)
527+ 				if  (e.message?.contains(" Pipe closed"  ) ==  true ) {
528+ 					Timber .d(" Thumbnail generation stream closed (expected for large files): ${cryptoFile.name} "  )
529+ 				} else  {
530+ 					Timber .w(e, " IOException during thumbnail generation for file: ${cryptoFile.name} "  )
531+ 				}
475532			} catch  (e:  Exception ) {
476- 				Timber .e(e, " Bitmap generation crashed"  )
533+ 				closeQuietly(thumbnailReader)
534+ 				Timber .e(e, " Bitmap generation crashed for file: ${cryptoFile.name} "  )
477535			}
478536		}
479537	}
0 commit comments