music: make compat more menu

This way we can make sure that external providers never truncate our
MediaItem count.
This commit is contained in:
Alexander Capehart 2024-08-28 10:11:30 -06:00
parent e23ac33b85
commit f1e1152e21
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 139 additions and 54 deletions

View file

@ -30,6 +30,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.ServiceCompat
import androidx.media.MediaBrowserServiceCompat
import androidx.media.utils.MediaConstants
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.oxycblt.auxio.music.service.MusicServiceFragment
@ -83,7 +84,12 @@ class AuxioService :
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): BrowserRoot = musicFragment.getRoot()
): BrowserRoot {
val maximumRootChildLimit =
rootHints?.getInt(
MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT, 4) ?: 4
return musicFragment.getRoot(maximumRootChildLimit)
}
override fun onLoadItem(itemId: String, result: Result<MediaItem>) {
musicFragment.getItem(itemId, result)

View file

@ -0,0 +1,94 @@
package org.oxycblt.auxio.music.service
import org.oxycblt.auxio.R
sealed interface Category {
val id: String
val nameRes: Int
val bitmapRes: Int?
data class Root(val amount: Int) : Category {
override val id = "root/$amount"
override val nameRes = R.string.info_app_name
override val bitmapRes = null
companion object {
const val ID_PREFIX = "root"
fun fromString(str: String): Root? {
val split = str.split("/", limit = 2)
if (split.size != 2) {
return null
}
val limit = split[1].toIntOrNull() ?: return null
return Root(limit)
}
}
}
data class More(val remainder: Int) : Category {
override val id = "more/$remainder"
override val nameRes = R.string.lbl_more
override val bitmapRes = null
companion object {
const val ID_PREFIX = "more"
fun fromString(str: String): More? {
val split = str.split("/", limit = 2)
if (split.size != 2) {
return null
}
val remainder = split[1].toIntOrNull() ?: return null
return More(remainder)
}
}
}
data object Songs : Category {
override val id = "songs"
override val nameRes = R.string.lbl_songs
override val bitmapRes = R.drawable.ic_song_bitmap_24
}
data object Albums : Category {
override val id = "albums"
override val nameRes = R.string.lbl_albums
override val bitmapRes = R.drawable.ic_album_bitmap_24
}
data object Artists : Category {
override val id = "artists"
override val nameRes = R.string.lbl_artists
override val bitmapRes = R.drawable.ic_artist_bitmap_24
}
data object Genres : Category {
override val id = "genres"
override val nameRes = R.string.lbl_genres
override val bitmapRes = R.drawable.ic_genre_bitmap_24
}
data object Playlists : Category {
override val id = "playlists"
override val nameRes = R.string.lbl_playlists
override val bitmapRes = R.drawable.ic_playlist_bitmap_24
}
companion object {
val MUSIC = arrayOf(Songs, Albums, Artists, Genres, Playlists)
val DEVICE_MUSIC = arrayOf(Songs, Albums, Artists, Genres)
val USER_MUSIC = arrayOf(Playlists)
fun fromString(str: String): Category? =
when {
str.startsWith(Root.ID_PREFIX) -> Root.fromString(str)
str.startsWith(More.ID_PREFIX) -> More.fromString(str)
str == Songs.id -> Songs
str == Albums.id -> Albums
str == Artists.id -> Artists
str == Genres.id -> Genres
str == Playlists.id -> Playlists
else -> null
}
}
}

View file

@ -23,7 +23,6 @@ import android.graphics.BitmapFactory
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat.MediaItem
import android.support.v4.media.MediaDescriptionCompat
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.media.utils.MediaConstants
import org.oxycblt.auxio.BuildConfig
@ -38,22 +37,6 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.util.getPlural
enum class Category(val id: String, @StringRes val nameRes: Int, @DrawableRes val bitmapRes: Int?) {
ROOT("root", R.string.info_app_name, null),
MORE("more", R.string.lbl_more, R.drawable.ic_more_24),
SONGS("songs", R.string.lbl_songs, R.drawable.ic_song_bitmap_24),
ALBUMS("albums", R.string.lbl_albums, R.drawable.ic_album_bitmap_24),
ARTISTS("artists", R.string.lbl_artists, R.drawable.ic_artist_bitmap_24),
GENRES("genres", R.string.lbl_genres, R.drawable.ic_genre_bitmap_24),
PLAYLISTS("playlists", R.string.lbl_playlists, R.drawable.ic_playlist_bitmap_24);
companion object {
val DEVICE_MUSIC = listOf(ROOT, SONGS, ALBUMS, ARTISTS, GENRES)
val USER_MUSIC = listOf(ROOT, PLAYLISTS)
val IMPORTANT = listOf(SONGS, ALBUMS, ARTISTS, GENRES, PLAYLISTS)
}
}
sealed interface MediaSessionUID {
data class CategoryItem(val category: Category) : MediaSessionUID {
override fun toString() = "$ID_CATEGORY:$category"
@ -78,17 +61,7 @@ sealed interface MediaSessionUID {
}
return when (parts[0]) {
ID_CATEGORY ->
CategoryItem(
when (parts[1]) {
Category.ROOT.id -> Category.ROOT
Category.MORE.id -> Category.MORE
Category.SONGS.id -> Category.SONGS
Category.ALBUMS.id -> Category.ALBUMS
Category.ARTISTS.id -> Category.ARTISTS
Category.GENRES.id -> Category.GENRES
Category.PLAYLISTS.id -> Category.PLAYLISTS
else -> return null
})
CategoryItem(Category.fromString(parts[1]) ?: return null)
ID_ITEM -> {
val uids = parts[1].split(">", limit = 2)
if (uids.size == 1) {
@ -113,7 +86,6 @@ fun header(@StringRes nameRes: Int): Sugar = {
}
fun Category.toMediaItem(context: Context): MediaItem {
// TODO: Make custom overflow menu for compat
val extras =
Bundle().apply {
putInt(
@ -126,8 +98,8 @@ fun Category.toMediaItem(context: Context): MediaItem {
.setMediaId(mediaSessionUID.toString())
.setTitle(context.getString(nameRes))
.setExtras(extras)
if (bitmapRes != null) {
val bitmap = BitmapFactory.decodeResource(context.resources, bitmapRes)
bitmapRes?.let { res ->
val bitmap = BitmapFactory.decodeResource(context.resources, res)
description.setIconBitmap(bitmap)
}
return MediaItem(description.build(), MediaItem.FLAG_BROWSABLE)

View file

@ -172,27 +172,7 @@ constructor(
): List<MediaItem>? {
return when (val mediaSessionUID = MediaSessionUID.fromString(id)) {
is MediaSessionUID.CategoryItem -> {
when (mediaSessionUID.category) {
Category.ROOT -> Category.IMPORTANT.map { it.toMediaItem(context) }
Category.MORE -> TODO()
Category.SONGS ->
listSettings.songSort.songs(deviceLibrary.songs).map {
it.toMediaItem(context, null)
}
Category.ALBUMS ->
listSettings.albumSort.albums(deviceLibrary.albums).map {
it.toMediaItem(context)
}
Category.ARTISTS ->
listSettings.artistSort.artists(deviceLibrary.artists).map {
it.toMediaItem(context)
}
Category.GENRES ->
listSettings.genreSort.genres(deviceLibrary.genres).map {
it.toMediaItem(context)
}
Category.PLAYLISTS -> userLibrary.playlists.map { it.toMediaItem(context) }
}
getCategoryMediaItems(mediaSessionUID.category, deviceLibrary, userLibrary)
}
is MediaSessionUID.SingleItem -> {
getChildMediaItems(mediaSessionUID.uid)
@ -206,6 +186,38 @@ constructor(
}
}
private fun getCategoryMediaItems(category: Category, deviceLibrary: DeviceLibrary, userLibrary: UserLibrary) =
when (category) {
is Category.Root -> {
val base = Category.MUSIC.take(category.amount)
if (base.size < Category.MUSIC.size) {
base + Category.More(Category.MUSIC.size - base.size)
} else {
base
}.map { it.toMediaItem(context) }
}
is Category.More -> Category.MUSIC.takeLast(category.remainder).map {
it.toMediaItem(context)
}
is Category.Songs ->
listSettings.songSort.songs(deviceLibrary.songs).map {
it.toMediaItem(context, null)
}
is Category.Albums ->
listSettings.albumSort.albums(deviceLibrary.albums).map {
it.toMediaItem(context)
}
is Category.Artists ->
listSettings.artistSort.artists(deviceLibrary.artists).map {
it.toMediaItem(context)
}
is Category.Genres ->
listSettings.genreSort.genres(deviceLibrary.genres).map {
it.toMediaItem(context)
}
is Category.Playlists -> userLibrary.playlists.map { it.toMediaItem(context) }
}
private fun getChildMediaItems(uid: Music.UID): List<MediaItem>? {
return when (val item = musicRepository.find(uid)) {
is Album -> {

View file

@ -76,7 +76,8 @@ constructor(
indexer.createNotification(post)
}
fun getRoot() = BrowserRoot(Category.ROOT.id, null)
fun getRoot(maxItems: Int) =
BrowserRoot(MediaSessionUID.CategoryItem(Category.Root(maxItems)).toString(), null)
fun getItem(mediaId: String, result: Result<MediaItem>) =
result.dispatch { musicBrowser.getItem(mediaId) }