Skip to content

Commit 4fe603f

Browse files
committed
Do not use local files for ParcelFileDescriptor (#3351)
1 parent 49375d5 commit 4fe603f

File tree

1 file changed

+60
-27
lines changed

1 file changed

+60
-27
lines changed

modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/playback/auto/AlbumArtContentProvider.kt

+60-27
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,20 @@ import android.database.Cursor
77
import android.net.Uri
88
import android.os.ParcelFileDescriptor
99
import au.com.shiftyjelly.pocketcasts.servers.di.Downloads
10+
import au.com.shiftyjelly.pocketcasts.utils.log.LogBuffer
1011
import dagger.hilt.EntryPoint
1112
import dagger.hilt.InstallIn
1213
import dagger.hilt.android.EntryPointAccessors
1314
import dagger.hilt.components.SingletonComponent
1415
import java.io.File
15-
import java.io.IOException
16+
import okhttp3.HttpUrl
17+
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
1618
import okhttp3.OkHttpClient
1719
import okhttp3.Request
20+
import okhttp3.Response
21+
import okio.buffer
1822
import okio.sink
23+
import okio.use
1924
import timber.log.Timber
2025

2126
class AlbumArtContentProvider : ContentProvider() {
@@ -29,35 +34,63 @@ class AlbumArtContentProvider : ContentProvider() {
2934
override fun onCreate() = true
3035

3136
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
32-
val context = this.context ?: return null
33-
Timber.tag("AlbumArtProvider").d("Content uri received ${uri.lastPathSegment}")
34-
35-
// Extract the image uri e.g. https://static.pocketcasts.net/discover/images/webp/480/220e7cc0-d53e-0133-2e9f-6dc413d6d41d.webp
36-
val imageUri = Uri.parse(uri.lastPathSegment)
37-
// Create the cache file name e.g. static.pocketcasts.net:discover:images:webp:480:220e7cc0-d53e-0133-2e9f-6dc413d6d41d.webp
38-
val cachePath = "${imageUri.host}${imageUri.encodedPath}".replace(oldChar = '/', newChar = ':')
39-
// Create the cache file path e.g. /data/user/0/au.com.shiftyjelly.pocketcasts.debug/cache/static.pocketcasts.net:discover:images:webp:480:220e7cc0-d53e-0133-2e9f-6dc413d6d41d.webp
40-
val file = File(context.cacheDir, cachePath)
41-
// Cache the image
42-
if (!file.exists()) {
43-
Timber.tag("AlbumArtProvider").d("Executing call for $imageUri")
44-
val appContext = context.applicationContext
45-
val entryPoint = EntryPointAccessors.fromApplication(appContext, AlbumArtEntryPoint::class.java)
46-
val client = entryPoint.okHttpClient()
47-
val request = Request.Builder().url(imageUri.toString()).build()
48-
try {
49-
client.newCall(request).execute().use { response ->
50-
if (response.isSuccessful) {
51-
file.sink().use { sink ->
52-
response.body?.source()?.readAll(sink)
53-
}
54-
}
37+
val result = runCatching {
38+
Timber.tag("AlbumArtProvider").d("Content uri received ${uri.lastPathSegment}")
39+
40+
val applicationContext = context?.applicationContext ?: return@runCatching null
41+
val entryPoint = getEntryPoint(applicationContext)
42+
43+
val imageUrl = uri.toImageUrl()
44+
val artworkFile = if (imageUrl != null) {
45+
val cacheFile = imageUrl.toCachedArtworkFile(applicationContext)
46+
if (cacheFile.exists()) {
47+
cacheFile
48+
} else {
49+
entryPoint.okHttpClient().fetchAndSaveArtwork(imageUrl, cacheFile)
5550
}
56-
} catch (e: IOException) {
57-
Timber.tag("AlbumArtProvider").d(e, "Failed to load image $imageUri")
51+
} else {
52+
uri.path?.let(::File)
53+
}
54+
55+
artworkFile?.let { ParcelFileDescriptor.open(it, ParcelFileDescriptor.MODE_READ_ONLY) }
56+
}
57+
return result
58+
.onFailure { exception ->
59+
val message = "Failed to provide artwork file descriptor for $uri"
60+
Timber.tag("AlbumArtProvider").e(exception, message)
61+
LogBuffer.e("AlbumArtProvider", exception, message)
62+
}
63+
.getOrNull()
64+
}
65+
66+
private fun getEntryPoint(context: Context): AlbumArtEntryPoint {
67+
return EntryPointAccessors.fromApplication(context, AlbumArtEntryPoint::class.java)
68+
}
69+
70+
private fun Uri.toImageUrl() = lastPathSegment?.toHttpUrlOrNull()
71+
72+
private fun HttpUrl.toCachedArtworkFile(context: Context): File {
73+
val fileName = "${host}$encodedPath".replace(oldChar = '/', newChar = ':')
74+
return context.cacheDir.resolve(fileName)
75+
}
76+
77+
private fun OkHttpClient.fetchAndSaveArtwork(imageUrl: HttpUrl, file: File): File? {
78+
Timber.tag("AlbumArtProvider").d("Executing call for $imageUrl")
79+
val request = Request.Builder().url(imageUrl).build()
80+
val response = newCall(request).execute()
81+
return response.use { it.writeSuccessBody(file) }
82+
}
83+
84+
private fun Response.writeSuccessBody(file: File): File? {
85+
val responseBody = body
86+
return if (isSuccessful && responseBody != null) {
87+
file.sink().buffer().use { sink ->
88+
sink.writeAll(responseBody.source())
5889
}
90+
file
91+
} else {
92+
null
5993
}
60-
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
6194
}
6295

6396
override fun insert(uri: Uri, values: ContentValues?): Uri? = null

0 commit comments

Comments
 (0)