image: introduce cover provider

This will be used to expose image data to android auto.
This commit is contained in:
Alexander Capehart 2025-01-01 14:21:44 -07:00
parent 62e214039f
commit 194e6b1574
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 102 additions and 5 deletions

View file

@ -100,6 +100,15 @@
</intent-filter>
</service>
<!--
Expose Auxio's cover data to the android system
-->
<provider
android:name=".image.CoverProvider"
android:authorities="org.oxycblt.auxio.image"
android:exported="true"
tools:ignore="ExportedContentProvider" />
<!--
Work around apps that blindly query for ACTION_MEDIA_BUTTON working.
See the class for more info.

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2025 Auxio Project
* CoverProvider.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.image
import android.content.ContentProvider
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import kotlinx.coroutines.runBlocking
import org.oxycblt.auxio.image.covers.MutableSiloedCovers
import org.oxycblt.auxio.image.covers.SiloedCoverId
import org.oxycblt.musikr.cover.CoverIdentifier
import org.oxycblt.musikr.cover.ObtainResult
// AndroidManifest.xml addition
// ImageProvider.java
class CoverProvider : ContentProvider() {
override fun onCreate(): Boolean = true
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
check(mode == "r") { "Unsupported mode: $mode" }
check(uriMatcher.match(uri) == 1) { "Unknown URI: $uri" }
val id = requireNotNull(uri.lastPathSegment) { "No ID in URI: $uri" }
val coverId = requireNotNull(SiloedCoverId.parse(id)) { "Invalid ID: $id" }
return runBlocking {
val siloedCovers =
MutableSiloedCovers.from(requireContext(), coverId.silo, CoverIdentifier.md5())
when (val res = siloedCovers.obtain(coverId.id)) {
is ObtainResult.Hit -> res.cover.fd()
is ObtainResult.Miss -> null
}
}
}
override fun getType(uri: Uri): String {
check(uriMatcher.match(uri) == 1) { "Unknown URI: $uri" }
return "image/*"
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor = throw UnsupportedOperationException()
override fun insert(uri: Uri, values: ContentValues?): Uri =
throw UnsupportedOperationException()
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int =
throw UnsupportedOperationException()
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int = throw UnsupportedOperationException()
companion object {
private const val AUTHORITY = "org.oxycblt.auxio.image"
private const val IMAGES_PATH = "covers"
private val uriMatcher =
UriMatcher(UriMatcher.NO_MATCH).apply { addURI(AUTHORITY, "$IMAGES_PATH/*", 1) }
val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/$IMAGES_PATH")
}
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* SiloedCovers.kt is part of Auxio.
* MutableSiloedCovers.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -31,7 +31,7 @@ import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.cover.ObtainResult
import org.oxycblt.musikr.fs.app.AppFiles
class SiloedCovers
class MutableSiloedCovers
private constructor(
private val rootDir: File,
private val silo: CoverSilo,
@ -63,7 +63,7 @@ private constructor(
context: Context,
silo: CoverSilo,
identifier: CoverIdentifier
): SiloedCovers {
): MutableSiloedCovers {
val rootDir: File
val revisionDir: File
withContext(Dispatchers.IO) {
@ -72,7 +72,7 @@ private constructor(
}
val files = AppFiles.at(revisionDir)
val format = CoverFormat.jpeg(silo.params)
return SiloedCovers(rootDir, silo, FileCovers(files, format, identifier))
return MutableSiloedCovers(rootDir, silo, FileCovers(files, format, identifier))
}
}
}

View file

@ -44,5 +44,5 @@ constructor(private val imageSettings: ImageSettings, private val identifier: Co
}
private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams) =
SiloedCovers.from(context, CoverSilo(revision, with), identifier)
MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier)
}