music: split display name fix across versions

When having to fall back to an alternative displayName field, use
the version-specific RELATIVE_PATH and DATA fields when necessary.
This commit is contained in:
OxygenCobalt 2022-06-05 19:11:58 -06:00
parent dd00c70488
commit fb3c32b14c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
6 changed files with 55 additions and 27 deletions

View file

@ -2,8 +2,11 @@
## dev [v2.3.2, v2.4.0, or v3.0.0] ## dev [v2.3.2, v2.4.0, or v3.0.0]
#### What's New
- Folders on external drives can now be excluded on Android Q+ [#134]
#### What's Improved #### What's Improved
- Genre parsing now handles multiple integer values and cover/remix indicators - Genre parsing now handles multiple integer values and cover/remix indicators (May wipe playback state)
#### Dev/Meta #### Dev/Meta
- New translations [Fjuro -> Czech] - New translations [Fjuro -> Czech]

View file

@ -21,7 +21,10 @@ import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
/** A ViewModel representing the current music indexing state. */ /**
* A ViewModel representing the current music indexing state.
* @author OxygenCobalt
*/
class IndexerViewModel : ViewModel(), Indexer.Callback { class IndexerViewModel : ViewModel(), Indexer.Callback {
private val indexer = Indexer.getInstance() private val indexer = Indexer.getInstance()

View file

@ -25,6 +25,7 @@ import android.provider.MediaStore
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.database.getIntOrNull import androidx.core.database.getIntOrNull
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import java.io.File
import org.oxycblt.auxio.music.Indexer import org.oxycblt.auxio.music.Indexer
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.albumCoverUri import org.oxycblt.auxio.music.albumCoverUri
@ -104,14 +105,13 @@ import org.oxycblt.auxio.util.logW
abstract class MediaStoreBackend : Indexer.Backend { abstract class MediaStoreBackend : Indexer.Backend {
private var idIndex = -1 private var idIndex = -1
private var titleIndex = -1 private var titleIndex = -1
private var fileIndex = -1 private var displayNameIndex = -1
private var durationIndex = -1 private var durationIndex = -1
private var yearIndex = -1 private var yearIndex = -1
private var albumIndex = -1 private var albumIndex = -1
private var albumIdIndex = -1 private var albumIdIndex = -1
private var artistIndex = -1 private var artistIndex = -1
private var albumArtistIndex = -1 private var albumArtistIndex = -1
private var dataIndex = -1
override fun query(context: Context): Cursor { override fun query(context: Context): Cursor {
val settingsManager = SettingsManager.getInstance() val settingsManager = SettingsManager.getInstance()
@ -193,14 +193,14 @@ abstract class MediaStoreBackend : Indexer.Backend {
// We need to initialize the cursor indices. // We need to initialize the cursor indices.
idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID) idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID)
titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE) titleIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TITLE)
fileIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME) displayNameIndex =
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DISPLAY_NAME)
durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DURATION) durationIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DURATION)
yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR) yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR)
albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM) albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM)
albumIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM_ID) albumIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM_ID)
artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST) artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST)
albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST) albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST)
dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA)
} }
val audio = Audio() val audio = Audio()
@ -212,12 +212,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
// from the android system. Once again though, OEM issues get in our way and // from the android system. Once again though, OEM issues get in our way and
// this field isn't available on some platforms. In that case, see if we can // this field isn't available on some platforms. In that case, see if we can
// grok a file name from the DATA field. // grok a file name from the DATA field.
audio.displayName = audio.displayName = cursor.getStringOrNull(displayNameIndex)
cursor.getStringOrNull(fileIndex)
?: cursor
.getStringOrNull(dataIndex)
?.substringAfterLast('/', MediaStore.UNKNOWN_STRING)
?: MediaStore.UNKNOWN_STRING
audio.duration = cursor.getLong(durationIndex) audio.duration = cursor.getLong(durationIndex)
audio.year = cursor.getIntOrNull(yearIndex) audio.year = cursor.getIntOrNull(yearIndex)
@ -317,8 +312,7 @@ abstract class MediaStoreBackend : Indexer.Backend {
MediaStore.Audio.AudioColumns.ALBUM, MediaStore.Audio.AudioColumns.ALBUM,
MediaStore.Audio.AudioColumns.ALBUM_ID, MediaStore.Audio.AudioColumns.ALBUM_ID,
MediaStore.Audio.AudioColumns.ARTIST, MediaStore.Audio.AudioColumns.ARTIST,
AUDIO_COLUMN_ALBUM_ARTIST, AUDIO_COLUMN_ALBUM_ARTIST)
MediaStore.Audio.AudioColumns.DATA)
/** /**
* The base selector that works across all versions of android. Does not exclude * The base selector that works across all versions of android. Does not exclude
@ -334,9 +328,12 @@ abstract class MediaStoreBackend : Indexer.Backend {
*/ */
class Api21MediaStoreBackend : MediaStoreBackend() { class Api21MediaStoreBackend : MediaStoreBackend() {
private var trackIndex = -1 private var trackIndex = -1
private var dataIndex = -1
override val projection: Array<String> override val projection: Array<String>
get() = super.projection + arrayOf(MediaStore.Audio.AudioColumns.TRACK) get() =
super.projection +
arrayOf(MediaStore.Audio.AudioColumns.TRACK, MediaStore.Audio.AudioColumns.DATA)
override fun buildExcludedSelector(dirs: List<ExcludedDirectory>): Selector { override fun buildExcludedSelector(dirs: List<ExcludedDirectory>): Selector {
val base = Environment.getExternalStorageDirectory().absolutePath val base = Environment.getExternalStorageDirectory().absolutePath
@ -363,6 +360,7 @@ class Api21MediaStoreBackend : MediaStoreBackend() {
// Initialize the TRACK index if we have not already. // Initialize the TRACK index if we have not already.
if (trackIndex == -1) { if (trackIndex == -1) {
trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK) trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK)
dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA)
} }
// TRACK is formatted as DTTT where D is the disc number and T is the track number. // TRACK is formatted as DTTT where D is the disc number and T is the track number.
@ -379,6 +377,14 @@ class Api21MediaStoreBackend : MediaStoreBackend() {
} }
} }
if (audio.displayName == null) {
audio.displayName =
cursor
.getStringOrNull(dataIndex)
?.substringAfterLast(File.separatorChar, MediaStore.UNKNOWN_STRING)
?: MediaStore.UNKNOWN_STRING
}
return audio return audio
} }
} }
@ -390,12 +396,10 @@ class Api21MediaStoreBackend : MediaStoreBackend() {
*/ */
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
open class Api29MediaStoreBackend : MediaStoreBackend() { open class Api29MediaStoreBackend : MediaStoreBackend() {
private var relativePathIndex = -1
override val projection: Array<String> override val projection: Array<String>
get() = get() = super.projection + arrayOf(MediaStore.Audio.AudioColumns.RELATIVE_PATH)
super.projection +
arrayOf(
MediaStore.Audio.AudioColumns.VOLUME_NAME,
MediaStore.Audio.AudioColumns.RELATIVE_PATH)
override fun buildExcludedSelector(dirs: List<ExcludedDirectory>): Selector { override fun buildExcludedSelector(dirs: List<ExcludedDirectory>): Selector {
var selector = BASE_SELECTOR var selector = BASE_SELECTOR
@ -422,6 +426,25 @@ open class Api29MediaStoreBackend : MediaStoreBackend() {
return Selector(selector, args) return Selector(selector, args)
} }
override fun buildAudio(context: Context, cursor: Cursor): Audio {
val audio = super.buildAudio(context, cursor)
if (relativePathIndex != -1) {
relativePathIndex =
cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.RELATIVE_PATH)
}
if (audio.displayName == null) {
audio.displayName =
cursor
.getStringOrNull(relativePathIndex)
?.substringAfterLast(File.separatorChar, MediaStore.UNKNOWN_STRING)
?: MediaStore.UNKNOWN_STRING
}
return audio
}
} }
/** /**

View file

@ -18,6 +18,7 @@
package org.oxycblt.auxio.music.excluded package org.oxycblt.auxio.music.excluded
import android.os.Build import android.os.Build
import java.io.File
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
data class ExcludedDirectory(val volume: Volume, val relativePath: String) { data class ExcludedDirectory(val volume: Volume, val relativePath: String) {
@ -44,10 +45,9 @@ data class ExcludedDirectory(val volume: Volume, val relativePath: String) {
} }
companion object { companion object {
private const val VOLUME_SEPARATOR = ':'
fun fromString(dir: String): ExcludedDirectory? { fun fromString(dir: String): ExcludedDirectory? {
val split = dir.split(VOLUME_SEPARATOR, limit = 2) val split = dir.split(File.pathSeparator, limit = 2)
val volume = Volume.fromString(split.getOrNull(0) ?: return null) val volume = Volume.fromString(split.getOrNull(0) ?: return null)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && volume is Volume.Secondary) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && volume is Volume.Secondary) {
logW("Cannot use secondary volumes below API 29") logW("Cannot use secondary volumes below API 29")
@ -55,6 +55,7 @@ data class ExcludedDirectory(val volume: Volume, val relativePath: String) {
} }
val relativePath = split.getOrNull(1) ?: return null val relativePath = split.getOrNull(1) ?: return null
return ExcludedDirectory(volume, relativePath) return ExcludedDirectory(volume, relativePath)
} }
} }

View file

@ -139,9 +139,6 @@ class PlaybackService :
// --- PLAYBACKSTATEMANAGER SETUP --- // --- PLAYBACKSTATEMANAGER SETUP ---
playbackManager.addCallback(this) playbackManager.addCallback(this)
// --- SETTINGSMANAGER SETUP ---
settingsManager.addCallback(this) settingsManager.addCallback(this)
logD("Service created") logD("Service created")

View file

@ -24,6 +24,7 @@ import android.database.sqlite.SQLiteOpenHelper
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import androidx.core.content.edit import androidx.core.content.edit
import java.io.File
import org.oxycblt.auxio.music.excluded.ExcludedDirectory import org.oxycblt.auxio.music.excluded.ExcludedDirectory
import org.oxycblt.auxio.ui.accent.Accent import org.oxycblt.auxio.ui.accent.Accent
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -90,7 +91,7 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent {
*/ */
fun handleExcludedCompat(context: Context): List<ExcludedDirectory> { fun handleExcludedCompat(context: Context): List<ExcludedDirectory> {
val db = LegacyExcludedDatabase(context) val db = LegacyExcludedDatabase(context)
val primaryPrefix = Environment.getExternalStorageDirectory().absolutePath + '/' val primaryPrefix = Environment.getExternalStorageDirectory().absolutePath + File.separatorChar
return db.readPaths().map { path -> return db.readPaths().map { path ->
val relativePath = path.removePrefix(primaryPrefix) val relativePath = path.removePrefix(primaryPrefix)
ExcludedDirectory(ExcludedDirectory.Volume.Primary, relativePath) ExcludedDirectory(ExcludedDirectory.Volume.Primary, relativePath)