Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.lagradost.cloudstream3.utils

internal object HardwareImageFailure {
fun isRecoverable(error: Throwable): Boolean {
val visited = HashSet<Throwable>()
var current: Throwable? = error
var recoverable = false
while (current != null && visited.add(current)) {
if (current is Error || current is java.util.concurrent.CancellationException) return false
val message = current.message.orEmpty().lowercase()
if (("hardware" in message && "bitmap" in message) ||
"ahardwarebuffer" in message ||
("graphicbuffer" in message && "allocat" in message)) recoverable = true
current = current.cause
}
return recoverable
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.lagradost.cloudstream3.utils

import android.graphics.Bitmap
import coil3.intercept.Interceptor
import coil3.request.ErrorResult
import coil3.request.ImageResult
import coil3.request.allowHardware
import coil3.request.bitmapConfig
import java.util.concurrent.atomic.AtomicBoolean

internal class ImageCompatibilityInterceptor : Interceptor {
private val softwareRequired = AtomicBoolean(false)

override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val request = chain.request
if (request.allowHardware && softwareRequired.get()) {
return chain.withRequest(request.newBuilder().allowHardware(false)
.bitmapConfig(Bitmap.Config.ARGB_8888).build()).proceed()
}
val result = chain.proceed()
if (request.allowHardware && result is ErrorResult &&
HardwareImageFailure.isRecoverable(result.throwable)) {
softwareRequired.set(true)
return chain.withRequest(request.newBuilder().allowHardware(false)
.bitmapConfig(Bitmap.Config.ARGB_8888).build()).proceed()
}
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ object ImageLoader {
/** Pass interceptors with care, unnecessary passing tokens to servers
or image hosting services causes unauthorized exceptions **/
.components {
add(ImageCompatibilityInterceptor())
add(OkHttpNetworkFetcherFactory(callFactory = { buildDefaultClient(context) }))
if (isBrokenHardware) {
add(BitmapFactoryDecoder.Factory())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.lagradost.cloudstream3.utils

import java.io.IOException
import java.util.concurrent.CancellationException
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class HardwareImageFailureTest {
@Test fun recoversAllocationFailureWithoutModelDetection() {
assertTrue(HardwareImageFailure.isRecoverable(IllegalStateException("failed to allocate hardware Bitmap!")))
}

@Test fun recoversWrappedSoftwareCanvasFailure() {
assertTrue(HardwareImageFailure.isRecoverable(IOException("decode failed",
IllegalArgumentException("Software rendering does not support hardware bitmaps"))))
}

@Test fun doesNotRetryHttpFailuresAsDecodeFailures() {
assertFalse(HardwareImageFailure.isRecoverable(IOException("HTTP 403")))
}

@Test fun doesNotRetryOutOfMemoryEvenWhenWrappedByHardwareError() {
assertFalse(HardwareImageFailure.isRecoverable(IllegalStateException("hardware bitmap",
OutOfMemoryError("heap exhausted"))))
}

@Test fun doesNotRetryCancellation() {
assertFalse(HardwareImageFailure.isRecoverable(CancellationException("hardware bitmap request cancelled")))
}

@Test fun recoversGraphicBufferAllocationFailure() {
assertTrue(HardwareImageFailure.isRecoverable(IllegalStateException("GraphicBuffer allocation failed")))
}

@Test fun recoversAHardwareBufferFailure() {
assertTrue(HardwareImageFailure.isRecoverable(IllegalStateException("AHardwareBuffer allocation failed")))
}

@Test fun rejectsUnrelatedDecodeFailure() {
assertFalse(HardwareImageFailure.isRecoverable(IllegalArgumentException("Invalid image data")))
}

@Test fun doesNotRetryFatalErrors() {
assertFalse(HardwareImageFailure.isRecoverable(StackOverflowError("hardware bitmap")))
}

@Test fun terminatesForCyclicCauseChains() {
val first = IllegalStateException("hardware bitmap allocation failed")
val second = IllegalArgumentException("decode failed")
first.initCause(second)
second.initCause(first)
assertTrue(HardwareImageFailure.isRecoverable(first))
}
}
Loading