safer compressed bitmap allocation

This commit is contained in:
Thibault Deckers 2022-11-07 19:43:37 +01:00
parent e8b46b02d8
commit eb3acaa307
5 changed files with 19 additions and 7 deletions

View file

@ -188,7 +188,7 @@ class ActivityResultStreamHandler(private val activity: Activity, arguments: Any
} }
private fun pickCollectionFilters() { private fun pickCollectionFilters() {
val initialFilters = (args["initialFilters"] as List<*>?)?.mapNotNull { if (it is String) it else null } ?: listOf() val initialFilters = (args["initialFilters"] as? List<*>)?.mapNotNull { if (it is String) it else null } ?: listOf()
val intent = Intent(MainActivity.INTENT_ACTION_PICK_COLLECTION_FILTERS, null, activity, MainActivity::class.java) val intent = Intent(MainActivity.INTENT_ACTION_PICK_COLLECTION_FILTERS, null, activity, MainActivity::class.java)
.putExtra(MainActivity.EXTRA_KEY_FILTERS_ARRAY, initialFilters.toTypedArray()) .putExtra(MainActivity.EXTRA_KEY_FILTERS_ARRAY, initialFilters.toTypedArray())
.putExtra(MainActivity.EXTRA_KEY_FILTERS_STRING, initialFilters.joinToString(MainActivity.EXTRA_STRING_ARRAY_SEPARATOR)) .putExtra(MainActivity.EXTRA_KEY_FILTERS_STRING, initialFilters.joinToString(MainActivity.EXTRA_STRING_ARRAY_SEPARATOR))

View file

@ -28,7 +28,7 @@ object SafePngMetadataReader {
private val LOG_TAG = LogUtils.createTag<SafePngMetadataReader>() private val LOG_TAG = LogUtils.createTag<SafePngMetadataReader>()
// arbitrary size to detect chunks that may yield an OOM // arbitrary size to detect chunks that may yield an OOM
private const val chunkSizeDangerThreshold = SafeXmpReader.segmentTypeSizeDangerThreshold private const val chunkSizeDangerThreshold = SafeXmpReader.SEGMENT_TYPE_SIZE_DANGER_THRESHOLD
private val latin1Encoding = Charsets.ISO_8859_1 private val latin1Encoding = Charsets.ISO_8859_1
private val desiredChunkTypes: Set<PngChunkType> = hashSetOf( private val desiredChunkTypes: Set<PngChunkType> = hashSetOf(

View file

@ -48,7 +48,7 @@ class SafeXmpReader : XmpReader() {
extendedXMPBuffer?.let { xmpBytes -> extendedXMPBuffer?.let { xmpBytes ->
val totalSize = xmpBytes.size val totalSize = xmpBytes.size
if (totalSize > segmentTypeSizeDangerThreshold) { if (totalSize > SEGMENT_TYPE_SIZE_DANGER_THRESHOLD) {
logError(metadata, totalSize) logError(metadata, totalSize)
} else { } else {
extract(xmpBytes, metadata) extract(xmpBytes, metadata)
@ -111,7 +111,7 @@ class SafeXmpReader : XmpReader() {
val chunkOffset = reader.uInt32.toInt() val chunkOffset = reader.uInt32.toInt()
if (extendedXMPBuffer == null) { if (extendedXMPBuffer == null) {
// TLAD insert start // TLAD insert start
if (fullLength > segmentTypeSizeDangerThreshold) { if (fullLength > SEGMENT_TYPE_SIZE_DANGER_THRESHOLD) {
logError(metadata, fullLength) logError(metadata, fullLength)
return null return null
} }
@ -147,7 +147,7 @@ class SafeXmpReader : XmpReader() {
private val LOG_TAG = LogUtils.createTag<SafeXmpReader>() private val LOG_TAG = LogUtils.createTag<SafeXmpReader>()
// arbitrary size to detect extended XMP that may yield an OOM // arbitrary size to detect extended XMP that may yield an OOM
const val segmentTypeSizeDangerThreshold = 3 * (1 shl 20) // MB const val SEGMENT_TYPE_SIZE_DANGER_THRESHOLD = 3 * (1 shl 20) // MB
// tighter node limits for faster loading // tighter node limits for faster loading
val PARSE_OPTIONS: ParseOptions = ParseOptions().setXMPNodesToLimit( val PARSE_OPTIONS: ParseOptions = ParseOptions().setXMPNodesToLimit(

View file

@ -14,6 +14,9 @@ object BitmapUtils {
private val LOG_TAG = LogUtils.createTag<BitmapUtils>() private val LOG_TAG = LogUtils.createTag<BitmapUtils>()
private const val INITIAL_BUFFER_SIZE = 2 shl 17 // 256kB private const val INITIAL_BUFFER_SIZE = 2 shl 17 // 256kB
// arbitrary size to detect buffer that may yield an OOM
private const val BUFFER_SIZE_DANGER_THRESHOLD = 10 * (1 shl 20) // MB
private val freeBaos = ArrayList<ByteArrayOutputStream>() private val freeBaos = ArrayList<ByteArrayOutputStream>()
private val mutex = Mutex() private val mutex = Mutex()
@ -39,6 +42,15 @@ object BitmapUtils {
this.compress(Bitmap.CompressFormat.JPEG, quality, stream) this.compress(Bitmap.CompressFormat.JPEG, quality, stream)
} }
if (recycle) this.recycle() if (recycle) this.recycle()
val bufferSize = stream.size()
if (bufferSize > BUFFER_SIZE_DANGER_THRESHOLD) {
val availHeapSize = Runtime.getRuntime().let { it.maxMemory() - (it.totalMemory() - it.freeMemory()) }
if (bufferSize > availHeapSize) {
throw Exception("compressed bitmap to $bufferSize bytes, which cannot be allocated to a new byte array, with only $availHeapSize free bytes")
}
}
val byteArray = stream.toByteArray() val byteArray = stream.toByteArray()
stream.reset() stream.reset()
mutex.withLock { mutex.withLock {
@ -59,7 +71,7 @@ object BitmapUtils {
} }
fun centerSquareCrop(context: Context, bitmap: Bitmap?, size: Int): Bitmap? { fun centerSquareCrop(context: Context, bitmap: Bitmap?, size: Int): Bitmap? {
bitmap ?: return bitmap bitmap ?: return null
return TransformationUtils.centerCrop(getBitmapPool(context), bitmap, size, size) return TransformationUtils.centerCrop(getBitmapPool(context), bitmap, size, size)
} }

View file

@ -24,7 +24,7 @@ object ContextUtils {
} }
fun Context.isMyServiceRunning(serviceClass: Class<out Service>): Boolean { fun Context.isMyServiceRunning(serviceClass: Class<out Service>): Boolean {
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager? val am = this.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
am ?: return false am ?: return false
@Suppress("deprecation") @Suppress("deprecation")
return am.getRunningServices(Integer.MAX_VALUE).any { it.service.className == serviceClass.name } return am.getRunningServices(Integer.MAX_VALUE).any { it.service.className == serviceClass.name }