diff --git a/coil-base/src/test/java/coil/fetch/UriFetcherTest.kt b/coil-base/src/androidTest/java/coil/fetch/UriFetcherTest.kt similarity index 59% rename from coil-base/src/test/java/coil/fetch/UriFetcherTest.kt rename to coil-base/src/androidTest/java/coil/fetch/UriFetcherTest.kt index 7445cb5634..7457ba3eae 100644 --- a/coil-base/src/test/java/coil/fetch/UriFetcherTest.kt +++ b/coil-base/src/androidTest/java/coil/fetch/UriFetcherTest.kt @@ -9,52 +9,32 @@ import coil.bitmappool.BitmapPool import coil.bitmappool.FakeBitmapPool import coil.size.PixelSize import coil.util.createOptions -import coil.util.createTestMainDispatcher -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineDispatcher -import kotlinx.coroutines.test.resetMain -import org.junit.After import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.shadow.api.Shadow -import org.robolectric.shadows.ShadowContentResolver import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue -@RunWith(RobolectricTestRunner::class) class UriFetcherTest { private val context: Context = ApplicationProvider.getApplicationContext() - private lateinit var mainDispatcher: TestCoroutineDispatcher private lateinit var loader: UriFetcher private lateinit var pool: BitmapPool @Before fun before() { - mainDispatcher = createTestMainDispatcher() loader = UriFetcher(context) pool = FakeBitmapPool() } - @After - fun after() { - Dispatchers.resetMain() - } - @Test - fun `basic asset load`() { + fun basicAssetLoad() { val uri = Uri.parse("file:///android_asset/normal.jpg") assertTrue(loader.handles(uri)) assertEquals(uri.toString(), loader.key(uri)) - val contentResolver: ShadowContentResolver = Shadow.extract(context.contentResolver) - contentResolver.registerInputStream(uri, context.assets.open("normal.jpg")) - val result = runBlocking { loader.fetch(pool, uri, PixelSize(100, 100), createOptions()) } @@ -62,4 +42,25 @@ class UriFetcherTest { assertTrue(result is SourceResult) assertFalse(result.source.exhausted()) } + + @Test + fun basicExtractFileName() { + val uri = Uri.parse("file:///android_asset/something.jpg") + val result = loader.extractAssetFileName(uri) + assertEquals("something.jpg", result) + } + + @Test + fun emptyExtractFileName() { + val uri = Uri.parse("file:///android_asset/") + val result = loader.extractAssetFileName(uri) + assertEquals(null, result) + } + + @Test + fun nonAssetUriExtractFileName() { + val uri = Uri.parse("file:///fake/file/path") + val result = loader.extractAssetFileName(uri) + assertEquals(null, result) + } } diff --git a/coil-base/src/main/java/coil/fetch/UriFetcher.kt b/coil-base/src/main/java/coil/fetch/UriFetcher.kt index cab999ecc5..49652a9995 100644 --- a/coil-base/src/main/java/coil/fetch/UriFetcher.kt +++ b/coil-base/src/main/java/coil/fetch/UriFetcher.kt @@ -2,7 +2,9 @@ package coil.fetch import android.content.ContentResolver import android.content.Context +import android.content.res.AssetManager import android.net.Uri +import androidx.annotation.VisibleForTesting import androidx.collection.arraySetOf import coil.bitmappool.BitmapPool import coil.decode.DataSource @@ -16,6 +18,8 @@ internal class UriFetcher( ) : Fetcher { companion object { + private const val ASSET_FILE_PATH_SEGMENT = "android_asset" + private val SUPPORTED_SCHEMES = arraySetOf( ContentResolver.SCHEME_ANDROID_RESOURCE, ContentResolver.SCHEME_CONTENT, @@ -33,10 +37,34 @@ internal class UriFetcher( size: Size, options: Options ): FetchResult { + val assetFileName = extractAssetFileName(data) + val inputStream = if (assetFileName != null) { + context.assets.open(assetFileName) + } else { + checkNotNull(context.contentResolver.openInputStream(data)) + } + return SourceResult( - source = checkNotNull(context.contentResolver.openInputStream(data)).source().buffer(), + source = inputStream.source().buffer(), mimeType = context.contentResolver.getType(data), dataSource = DataSource.DISK ) } + + /** Return the asset's filename if [uri] must be handled by [AssetManager]. Else, return null. */ + @VisibleForTesting + internal fun extractAssetFileName(uri: Uri): String? { + if (uri.scheme != ContentResolver.SCHEME_FILE) { + return null + } + + val segments = uri.pathSegments + return if (segments.count() == 2 && + segments[0] == ASSET_FILE_PATH_SEGMENT && + segments[1].isNotBlank()) { + segments[1] + } else { + null + } + } }