From ab1ff416e17457c5e8f7fea391ed50abe0cbd919 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Mon, 13 Jun 2022 14:34:08 -0600 Subject: [PATCH] music: use storage volume Leverage StorageVolume when working with file paths. StorageVolume is android's navive API for handling external volumes. Ideally, we would want to replace our built-in volume class with this new API, however doing so is somewhat complicated as some methods only exist on newer API levels. This is only the first step until we are able to migrate the excluded directory system to this as well. --- .../oxycblt/auxio/detail/DetailFragment.kt | 3 +- .../oxycblt/auxio/detail/SongDetailDialog.kt | 4 +- .../java/org/oxycblt/auxio/music/Music.kt | 2 +- ...SystemFramework.kt => StorageFramework.kt} | 71 ++++++++++++++++--- .../auxio/music/backend/MediaStoreBackend.kt | 47 +++++++----- .../auxio/settings/SettingsListFragment.kt | 4 +- .../{pref => ui}/IntListPrefDialog.kt | 2 +- .../{pref => ui}/IntListPreference.kt | 2 +- .../{pref => ui}/M3SwitchPreference.kt | 2 +- .../org/oxycblt/auxio/util/FrameworkUtil.kt | 5 +- app/src/main/res/values/donottranslate.xml | 5 +- app/src/main/res/xml/prefs_main.xml | 24 +++---- 12 files changed, 119 insertions(+), 52 deletions(-) rename app/src/main/java/org/oxycblt/auxio/music/{FileSystemFramework.kt => StorageFramework.kt} (71%) rename app/src/main/java/org/oxycblt/auxio/settings/{pref => ui}/IntListPrefDialog.kt (98%) rename app/src/main/java/org/oxycblt/auxio/settings/{pref => ui}/IntListPreference.kt (98%) rename app/src/main/java/org/oxycblt/auxio/settings/{pref => ui}/M3SwitchPreference.kt (98%) diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt index 890e48e24..fad2ab790 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailFragment.kt @@ -33,6 +33,7 @@ import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.NavigationViewModel import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.ViewBindingFragment +import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.unlikelyToBeNull @@ -42,7 +43,7 @@ import org.oxycblt.auxio.util.unlikelyToBeNull */ abstract class DetailFragment : ViewBindingFragment(), Toolbar.OnMenuItemClickListener { - protected val detailModel: DetailViewModel by activityViewModels() + protected val detailModel: DetailViewModel by androidActivityViewModels() protected val navModel: NavigationViewModel by activityViewModels() protected val playbackModel: PlaybackViewModel by activityViewModels() diff --git a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt index bb3aa8c1a..d11b50d90 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/SongDetailDialog.kt @@ -22,17 +22,17 @@ import android.text.format.Formatter import android.view.LayoutInflater import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone -import androidx.fragment.app.activityViewModels import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogSongDetailBinding import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.ui.ViewBindingDialogFragment +import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.formatDuration import org.oxycblt.auxio.util.launch class SongDetailDialog : ViewBindingDialogFragment() { - private val detailModel: DetailViewModel by activityViewModels() + private val detailModel: DetailViewModel by androidActivityViewModels() override fun onCreateBinding(inflater: LayoutInflater) = DialogSongDetailBinding.inflate(inflater) diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index 3038f2263..5017bbf78 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -60,7 +60,7 @@ sealed class MusicParent : Music() { data class Song( override val rawName: String, /** The path of this song. */ - val path: Path, + val path: NeoPath, /** The URI linking to this song's file. */ val uri: Uri, /** The mime type of this song. */ diff --git a/app/src/main/java/org/oxycblt/auxio/music/FileSystemFramework.kt b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt similarity index 71% rename from app/src/main/java/org/oxycblt/auxio/music/FileSystemFramework.kt rename to app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt index 3381e420f..79961732c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/FileSystemFramework.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt @@ -17,7 +17,13 @@ package org.oxycblt.auxio.music +import android.annotation.SuppressLint import android.content.Context +import android.os.Build +import android.os.Environment +import android.os.storage.StorageManager +import android.os.storage.StorageVolume +import android.provider.MediaStore import android.webkit.MimeTypeMap import com.google.android.exoplayer2.util.MimeTypes import org.oxycblt.auxio.R @@ -28,19 +34,18 @@ import org.oxycblt.auxio.R */ data class Path(val name: String, val parent: Dir) +data class NeoPath(val name: String, val parent: NeoDir) + +data class NeoDir(val volume: StorageVolume, val relativePath: String) { + fun resolveName(context: Context) = + context.getString(R.string.fmt_path, volume.getDescriptionCompat(context), relativePath) +} + /** * Represents a directory from the android file-system. Intentionally designed to be * version-agnostic and follow modern storage recommendations. */ sealed class Dir { - /** - * An absolute path. - * - * This is only used with [Song] instances on pre-Q android versions. This should be avoided in - * most cases for [Relative]. - */ - data class Absolute(val path: String) : Dir() - /** * A directory with a volume. * @@ -58,7 +63,6 @@ sealed class Dir { fun resolveName(context: Context) = when (this) { - is Absolute -> path is Relative -> when (volume) { is Volume.Primary -> context.getString(R.string.fmt_primary_path, relativePath) @@ -137,3 +141,52 @@ data class MimeType(val fromExtension: String, val fromFormat: String?) { } } } + +val StorageManager.storageVolumesCompat: List + get() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + storageVolumes.toList() + } else { + @Suppress("UNCHECKED_CAST") + (StorageManager::class.java.getDeclaredMethod("getVolumeList").invoke(this) + as Array) + .toList() + } + +val StorageVolume.directoryCompat: String? + @SuppressLint("NewApi") + get() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + directory?.absolutePath + } else { + when (stateCompat) { + Environment.MEDIA_MOUNTED, + Environment.MEDIA_MOUNTED_READ_ONLY -> + StorageVolume::class.java.getDeclaredMethod("getPath").invoke(this) as String + else -> null + } + } + +@SuppressLint("NewApi") +fun StorageVolume.getDescriptionCompat(context: Context): String = getDescription(context) + +val StorageVolume.isPrimaryCompat: Boolean + @SuppressLint("NewApi") get() = isPrimary + +val StorageVolume.uuidCompat: String? + @SuppressLint("NewApi") get() = uuid + +val StorageVolume.stateCompat: String + @SuppressLint("NewApi") get() = state + +val StorageVolume.mediaStoreVolumeNameCompat: String? + get() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + mediaStoreVolumeName + } else { + if (isPrimaryCompat) { + MediaStore.VOLUME_EXTERNAL_PRIMARY + } else { + uuid?.lowercase() + } + } 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 a1c6a6bc8..4c01d1e1f 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 @@ -21,6 +21,8 @@ import android.content.Context import android.database.Cursor import android.os.Build import android.os.Environment +import android.os.storage.StorageManager +import android.os.storage.StorageVolume import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.core.database.getIntOrNull @@ -29,17 +31,22 @@ import java.io.File import org.oxycblt.auxio.music.Dir import org.oxycblt.auxio.music.Indexer import org.oxycblt.auxio.music.MimeType -import org.oxycblt.auxio.music.Path +import org.oxycblt.auxio.music.NeoDir +import org.oxycblt.auxio.music.NeoPath import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.albumCoverUri import org.oxycblt.auxio.music.audioUri +import org.oxycblt.auxio.music.directoryCompat import org.oxycblt.auxio.music.dirs.MusicDirs import org.oxycblt.auxio.music.id3GenreName +import org.oxycblt.auxio.music.mediaStoreVolumeNameCompat import org.oxycblt.auxio.music.no import org.oxycblt.auxio.music.queryCursor +import org.oxycblt.auxio.music.storageVolumesCompat import org.oxycblt.auxio.music.useQuery import org.oxycblt.auxio.settings.SettingsManager import org.oxycblt.auxio.util.contentResolverSafe +import org.oxycblt.auxio.util.getSystemServiceSafe /* * This file acts as the base for most the black magic required to get a remotely sensible music @@ -120,8 +127,13 @@ abstract class MediaStoreBackend : Indexer.Backend { private var albumArtistIndex = -1 private var dataIndex = -1 + private val _volumes = mutableListOf() + protected val volumes = _volumes + override fun query(context: Context): Cursor { val settingsManager = SettingsManager.getInstance() + val storageManager = context.getSystemServiceSafe(StorageManager::class) + _volumes.addAll(storageManager.storageVolumesCompat) val selector = buildMusicDirsSelector(settingsManager.musicDirs) return requireNotNull( @@ -266,7 +278,7 @@ abstract class MediaStoreBackend : Indexer.Backend { var id: Long? = null, var title: String? = null, var displayName: String? = null, - var dir: Dir? = null, + var dir: NeoDir? = null, var extensionMimeType: String? = null, var formatMimeType: String? = null, var size: Long? = null, @@ -286,7 +298,7 @@ abstract class MediaStoreBackend : Indexer.Backend { // every device provides these fields, but it seems likely that they do. rawName = requireNotNull(title) { "Malformed audio: No title" }, path = - Path( + NeoPath( name = requireNotNull(displayName) { "Malformed audio: No display name" }, parent = requireNotNull(dir) { "Malformed audio: No parent directory" }), uri = requireNotNull(id) { "Malformed audio: No id" }.audioUri, @@ -421,10 +433,16 @@ open class Api21MediaStoreBackend : MediaStoreBackend() { audio.displayName = data.substringAfterLast(File.separatorChar, "").ifEmpty { null } } - audio.dir = - data.substringBeforeLast(File.separatorChar, "").let { dir -> - if (dir.isNotEmpty()) Dir.Absolute(dir) else null + val rawPath = data.substringBeforeLast(File.separatorChar) + + for (volume in volumes) { + val volumePath = volume.directoryCompat ?: continue + val strippedPath = rawPath.removePrefix(volumePath + File.separatorChar) + if (strippedPath != rawPath) { + audio.dir = NeoDir(volume, strippedPath + File.separatorChar) + break } + } } return audio @@ -488,18 +506,15 @@ open class Api29MediaStoreBackend : Api21MediaStoreBackend() { cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.RELATIVE_PATH) } - val volume = cursor.getStringOrNull(volumeIndex) + val volumeName = cursor.getStringOrNull(volumeIndex) val relativePath = cursor.getStringOrNull(relativePathIndex) - if (volume != null && relativePath != null) { - audio.dir = - Dir.Relative( - volume = - when (volume) { - MediaStore.VOLUME_EXTERNAL_PRIMARY -> Dir.Volume.Primary - else -> Dir.Volume.Secondary(volume) - }, - relativePath = relativePath) + if (volumeName != null && relativePath != null) { + val volume = volumes.find { it.mediaStoreVolumeNameCompat == volumeName } + + if (volume != null) { + audio.dir = NeoDir(volume, relativePath) + } } return audio diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt index 080a7c640..ebb27193c 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -35,8 +35,8 @@ import org.oxycblt.auxio.music.dirs.MusicDirsDialog import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog import org.oxycblt.auxio.playback.replaygain.ReplayGainMode -import org.oxycblt.auxio.settings.pref.IntListPreference -import org.oxycblt.auxio.settings.pref.IntListPreferenceDialog +import org.oxycblt.auxio.settings.ui.IntListPreference +import org.oxycblt.auxio.settings.ui.IntListPreferenceDialog import org.oxycblt.auxio.ui.accent.AccentDialog import org.oxycblt.auxio.util.getSystemBarInsetsCompat import org.oxycblt.auxio.util.hardRestart diff --git a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPrefDialog.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPrefDialog.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPrefDialog.kt rename to app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPrefDialog.kt index 80b4cd516..0d1864680 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPrefDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPrefDialog.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.pref +package org.oxycblt.auxio.settings.ui import android.app.Dialog import android.os.Bundle diff --git a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreference.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt rename to app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreference.kt index 778c5689a..f0a0fa23d 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/pref/IntListPreference.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/IntListPreference.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.pref +package org.oxycblt.auxio.settings.ui import android.content.Context import android.content.res.TypedArray diff --git a/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt b/app/src/main/java/org/oxycblt/auxio/settings/ui/M3SwitchPreference.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt rename to app/src/main/java/org/oxycblt/auxio/settings/ui/M3SwitchPreference.kt index 6ddc0d22a..a6f92214f 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/pref/M3SwitchPreference.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/ui/M3SwitchPreference.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.settings.pref +package org.oxycblt.auxio.settings.ui import android.content.Context import android.os.Build diff --git a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt index 126392280..5497d5020 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/FrameworkUtil.kt @@ -173,13 +173,10 @@ fun Fragment.launch( viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(state, block) } } -fun Fragment.androidViewModelFactory() = - ViewModelProvider.AndroidViewModelFactory(requireContext().applicationContext as Application) - inline fun Fragment.androidViewModels() = viewModels { ViewModelProvider.AndroidViewModelFactory(requireActivity().application) } -inline fun Fragment.activityAndroidViewModels() = +inline fun Fragment.androidActivityViewModels() = activityViewModels { ViewModelProvider.AndroidViewModelFactory(requireActivity().application) } diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index e67fe1771..7056ada17 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -14,6 +14,7 @@ Microsoft WAVE - Internal/%s - SDCARD/%s + %1$s:%2$s + Internal:%s + SDCARD:%s \ No newline at end of file diff --git a/app/src/main/res/xml/prefs_main.xml b/app/src/main/res/xml/prefs_main.xml index cabaf24aa..098f373db 100644 --- a/app/src/main/res/xml/prefs_main.xml +++ b/app/src/main/res/xml/prefs_main.xml @@ -4,7 +4,7 @@ app:layout="@layout/item_header" app:title="@string/set_ui"> - - - - - - - - - - - -