diff --git a/CHANGELOG.md b/CHANGELOG.md index f484b7284..6004e65a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ ## 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 -- 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 - New translations [Fjuro -> Czech] diff --git a/app/src/main/java/org/oxycblt/auxio/music/IndexerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/IndexerViewModel.kt index 42a56c569..bbd0a90de 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/IndexerViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/IndexerViewModel.kt @@ -21,7 +21,10 @@ import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow 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 { private val indexer = Indexer.getInstance() diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt index 6402976a0..8f8c1bca4 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt @@ -25,6 +25,7 @@ import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.core.database.getIntOrNull import androidx.core.database.getStringOrNull +import java.io.File import org.oxycblt.auxio.music.Indexer import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.albumCoverUri @@ -104,14 +105,13 @@ import org.oxycblt.auxio.util.logW abstract class MediaStoreBackend : Indexer.Backend { private var idIndex = -1 private var titleIndex = -1 - private var fileIndex = -1 + private var displayNameIndex = -1 private var durationIndex = -1 private var yearIndex = -1 private var albumIndex = -1 private var albumIdIndex = -1 private var artistIndex = -1 private var albumArtistIndex = -1 - private var dataIndex = -1 override fun query(context: Context): Cursor { val settingsManager = SettingsManager.getInstance() @@ -193,14 +193,14 @@ abstract class MediaStoreBackend : Indexer.Backend { // We need to initialize the cursor indices. idIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns._ID) 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) yearIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.YEAR) albumIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM) albumIdIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ALBUM_ID) artistIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.ARTIST) albumArtistIndex = cursor.getColumnIndexOrThrow(AUDIO_COLUMN_ALBUM_ARTIST) - dataIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.DATA) } 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 // this field isn't available on some platforms. In that case, see if we can // grok a file name from the DATA field. - audio.displayName = - cursor.getStringOrNull(fileIndex) - ?: cursor - .getStringOrNull(dataIndex) - ?.substringAfterLast('/', MediaStore.UNKNOWN_STRING) - ?: MediaStore.UNKNOWN_STRING + audio.displayName = cursor.getStringOrNull(displayNameIndex) audio.duration = cursor.getLong(durationIndex) audio.year = cursor.getIntOrNull(yearIndex) @@ -317,8 +312,7 @@ abstract class MediaStoreBackend : Indexer.Backend { MediaStore.Audio.AudioColumns.ALBUM, MediaStore.Audio.AudioColumns.ALBUM_ID, MediaStore.Audio.AudioColumns.ARTIST, - AUDIO_COLUMN_ALBUM_ARTIST, - MediaStore.Audio.AudioColumns.DATA) + AUDIO_COLUMN_ALBUM_ARTIST) /** * 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() { private var trackIndex = -1 + private var dataIndex = -1 override val projection: Array - 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): Selector { val base = Environment.getExternalStorageDirectory().absolutePath @@ -363,6 +360,7 @@ class Api21MediaStoreBackend : MediaStoreBackend() { // Initialize the TRACK index if we have not already. if (trackIndex == -1) { 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. @@ -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 } } @@ -390,12 +396,10 @@ class Api21MediaStoreBackend : MediaStoreBackend() { */ @RequiresApi(Build.VERSION_CODES.Q) open class Api29MediaStoreBackend : MediaStoreBackend() { + private var relativePathIndex = -1 + override val projection: Array - get() = - super.projection + - arrayOf( - MediaStore.Audio.AudioColumns.VOLUME_NAME, - MediaStore.Audio.AudioColumns.RELATIVE_PATH) + get() = super.projection + arrayOf(MediaStore.Audio.AudioColumns.RELATIVE_PATH) override fun buildExcludedSelector(dirs: List): Selector { var selector = BASE_SELECTOR @@ -422,6 +426,25 @@ open class Api29MediaStoreBackend : MediaStoreBackend() { 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 + } } /** diff --git a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDirectory.kt b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDirectory.kt index 85985f92d..f068f8724 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDirectory.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/excluded/ExcludedDirectory.kt @@ -18,6 +18,7 @@ package org.oxycblt.auxio.music.excluded import android.os.Build +import java.io.File import org.oxycblt.auxio.util.logW data class ExcludedDirectory(val volume: Volume, val relativePath: String) { @@ -44,10 +45,9 @@ data class ExcludedDirectory(val volume: Volume, val relativePath: String) { } companion object { - private const val VOLUME_SEPARATOR = ':' - 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) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && volume is Volume.Secondary) { 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 + return ExcludedDirectory(volume, relativePath) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index aa6f660b3..6faf73ec6 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -139,9 +139,6 @@ class PlaybackService : // --- PLAYBACKSTATEMANAGER SETUP --- playbackManager.addCallback(this) - - // --- SETTINGSMANAGER SETUP --- - settingsManager.addCallback(this) logD("Service created") diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt index af14ea7b5..93ae2f008 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt @@ -24,6 +24,7 @@ import android.database.sqlite.SQLiteOpenHelper import android.os.Build import android.os.Environment import androidx.core.content.edit +import java.io.File import org.oxycblt.auxio.music.excluded.ExcludedDirectory import org.oxycblt.auxio.ui.accent.Accent import org.oxycblt.auxio.util.logD @@ -90,7 +91,7 @@ fun handleAccentCompat(prefs: SharedPreferences): Accent { */ fun handleExcludedCompat(context: Context): List { val db = LegacyExcludedDatabase(context) - val primaryPrefix = Environment.getExternalStorageDirectory().absolutePath + '/' + val primaryPrefix = Environment.getExternalStorageDirectory().absolutePath + File.separatorChar return db.readPaths().map { path -> val relativePath = path.removePrefix(primaryPrefix) ExcludedDirectory(ExcludedDirectory.Volume.Primary, relativePath)