You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds an opt-in "Use folder cover images" setting (Settings → Library → Sync & Scanning). When enabled, a cover image sitting next to the audio file - cover.jpg, folder.jpg, albumart.png, front.jpg and the other names already listed in AlbumArtUtils.commonArtworkFileNames - is used as album art.
Why
Album art currently shows only when it is embedded in the audio file's tags. Users who keep one cover.jpg per album folder - the storage-efficient layout, and the same convention the app already follows for sibling .lrc lyrics files - get no artwork at all.
Folder scanning used to happen automatically and was removed on purpose, because it picked up unrelated images (Gallery, DCIM, Downloads) for music stored in mixed directories. findExternalAlbumArtFile and shouldTrustDirectoryArtwork survived that removal, documented as being "for explicit, controlled callers only". This PR adds that explicit caller - a setting that is off by default, so nothing changes for existing users and the original problem cannot come back unasked.
How it works
New readExternalAlbumArtBytes in AlbumArtUtils is wired into ensureAlbumArtCachedFile and hasLocalAlbumArt. Every local artwork consumer - lists, full player, media notification, Android Auto, widgets, palette extraction - resolves through those two, so no changes were needed in SyncWorker, the Room entities, or any UI component.
Folder art takes precedence over embedded art when the setting is on: a cover.jpg is usually the full-resolution original, while embedded art is often a downscaled copy.
The directory exclusion list is unchanged, so Music/, Download/, DCIM/, Pictures/ and friends are still ignored even when the setting is on. Only dedicated album folders qualify.
Folder art is copied into the existing per-song cache rather than referenced in place, so it reuses boundArtworkForCache (1536 px / JPEG q90) and stays under AlbumArtCacheManager's LRU accounting. Covers larger than 20 MB are skipped rather than read into memory.
The app records the effective state each cached cover was built under - the opt-in and the image permission - and reconciles it on launch, on returning to the foreground, and whenever the preference changes. That state is persisted because the cache is: covers live in filesDir and outlive the process, and a permission granted back in system settings changes what should be cached without the preference ever changing.
Toggling the setting either way clears the album-art cache, the "no art" markers and Coil's caches, then triggers a rescan - every cached entry was resolved under the old precedence. ImageCacheManager.clearAllCoverArtCaches() is new for this; the existing per-URI invalidation can only guess at Coil's size-suffixed keys, which is not good enough for a library-wide change.
Permission
Reading a non-audio file next to a track needs READ_MEDIA_IMAGES on API 33+ - READ_MEDIA_AUDIO does not cover image files, so without it cover.jpg is simply invisible and the feature would silently do nothing on every modern device. The permission is now declared and requested only when the user turns the setting on. Below API 33 nothing extra is requested, since READ_EXTERNAL_STORAGE is already granted during setup.
A partial "Select photos" grant on Android 14+ is deliberately treated as not granted: it gives no access to arbitrary music folders. In that case the setting stays off and an explanatory toast is shown. This is what the new SelectedPhotoAccess lint warning refers to - see the note below.
Settings search
Registered in SettingsRegistry as NAVIGABLE_CARD rather than SWITCH. A SWITCH spec flips the preference straight from the search results, which would bypass the permission prompt and persist the setting with no image access - silently doing nothing. As a navigable card, search jumps to the Library screen and highlights the real, permission-gated toggle.
Translations
Four new strings, added to values/ and all 11 locale folders (ar, de, es, fr, in, it, ko, nb, ru, tr, zh-rCN), so this adds no new MissingTranslation findings.
Local checks
Run with JDK 21 (JAVA_HOME=/opt/homebrew/opt/openjdk@21, OpenJDK 21.0.12.1) after ./gradlew clean:
BUILD FAILED - 94 errors, unchanged from the base commit
lintDebug already fails on the base commit db8cd3b, which I linted separately for comparison:
Base db8cd3b
This branch
Errors
94
94
Warnings
477
478
Hints
36
36
The error count is identical - those are MissingTranslation across the existing string catalogue plus media3 UnsafeOptInUsageError, and none of them are in files this PR touches. The one added warning is the SelectedPhotoAccess entry described below.
Eight new unit tests. Four in AlbumArtUtilsTest cover the new seam: bytes returned when enabled, null when disabled (the opt-in gate), null when the cover exceeds the size limit, and null in an excluded directory even when enabled. Four more in FolderAlbumArtUpdateTest cover the cache-state reconciler: first record, unchanged state, a preference change, and a permission re-grant with no preference change. The four existing findExternalAlbumArtFile tests are unchanged.
Notes for reviewers
One new lint warning is introduced, at AndroidManifest.xml:17: Your app is currently not handling Selected Photos Access introduced in Android 14+ [SelectedPhotoAccess]. This is expected - handling partial photo access would mean accepting a grant that cannot read arbitrary music folders, which is exactly the case this feature must reject. Happy to add a baseline entry or an explicit suppression if you'd prefer the warning silenced.
Verified on a physical device. The permission prompt, the notification artwork path and toggling the setting back off have been exercised on an Android 17.
The PR adds an opt-in folder-artwork source and coordinates its permission-dependent state across startup, restore, foreground transitions, persistent artwork caches, artist images, and settings.
Adds permission-gated folder cover and artist image discovery.
Reconciles the effective preference and permission state with persistent caches.
Invalidates local artwork, Coil, palette, and artist-image caches when effective access changes.
Adds localized settings UI, search registration, and coverage for cache and permission transitions.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Preference or image-permission change] --> B[FolderArtworkSettingsCoordinator]
B --> C{Effective state changed?}
C -->|No| D[Keep existing cache state]
C -->|Yes| E[Invalidate local artwork and no-art markers]
E --> F[Clear Coil, palette, and artist-image caches]
F --> G[Request full library sync]
G --> H[Resolve folder or embedded artwork under current state]
Could you please extend this option to include artist images located inside the folder? You could use filenames like artist.jpg, band.jpg (and .png). Unfortunately, many third-party artist images are incorrect or of low quality; this option would fix that issue!
Could you please extend this option to include artist images located inside the folder? You could use filenames like artist.jpg or band.jpg (and .png). Unfortunately, many third-party artist images are incorrect or of low quality; this option would fix that issue!
I think this behavior isn't as common as having the cover.jpg file in the same folder as the album (many players scan it). You can change the artist's image using the “Change Photo” button.
Could you please extend this option to include artist images located inside the folder? You could use filenames like artist.jpg or band.jpg (and .png). Unfortunately, many third-party artist images are incorrect or of low quality; this option would fix that issue!
I think this behavior isn't as common as having the cover.jpg file in the same folder as the album (many players scan it). You can change the artist's image using the “Change Photo” button.
I meant in the Artist folder. I suggested this idea because many people have huge libraries, and doing it automatically would be more practical...
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an opt-in "Use folder cover images" setting (Settings → Library → Sync & Scanning). When enabled, a cover image sitting next to the audio file -
cover.jpg,folder.jpg,albumart.png,front.jpgand the other names already listed inAlbumArtUtils.commonArtworkFileNames- is used as album art.Why
Album art currently shows only when it is embedded in the audio file's tags. Users who keep one
cover.jpgper album folder - the storage-efficient layout, and the same convention the app already follows for sibling.lrclyrics files - get no artwork at all.Folder scanning used to happen automatically and was removed on purpose, because it picked up unrelated images (Gallery, DCIM, Downloads) for music stored in mixed directories.
findExternalAlbumArtFileandshouldTrustDirectoryArtworksurvived that removal, documented as being "for explicit, controlled callers only". This PR adds that explicit caller - a setting that is off by default, so nothing changes for existing users and the original problem cannot come back unasked.How it works
readExternalAlbumArtBytesinAlbumArtUtilsis wired intoensureAlbumArtCachedFileandhasLocalAlbumArt. Every local artwork consumer - lists, full player, media notification, Android Auto, widgets, palette extraction - resolves through those two, so no changes were needed inSyncWorker, the Room entities, or any UI component.cover.jpgis usually the full-resolution original, while embedded art is often a downscaled copy.Music/,Download/,DCIM/,Pictures/and friends are still ignored even when the setting is on. Only dedicated album folders qualify.boundArtworkForCache(1536 px / JPEG q90) and stays underAlbumArtCacheManager's LRU accounting. Covers larger than 20 MB are skipped rather than read into memory.filesDirand outlive the process, and a permission granted back in system settings changes what should be cached without the preference ever changing.ImageCacheManager.clearAllCoverArtCaches()is new for this; the existing per-URI invalidation can only guess at Coil's size-suffixed keys, which is not good enough for a library-wide change.Permission
Reading a non-audio file next to a track needs
READ_MEDIA_IMAGESon API 33+ -READ_MEDIA_AUDIOdoes not cover image files, so without itcover.jpgis simply invisible and the feature would silently do nothing on every modern device. The permission is now declared and requested only when the user turns the setting on. Below API 33 nothing extra is requested, sinceREAD_EXTERNAL_STORAGEis already granted during setup.A partial "Select photos" grant on Android 14+ is deliberately treated as not granted: it gives no access to arbitrary music folders. In that case the setting stays off and an explanatory toast is shown. This is what the new
SelectedPhotoAccesslint warning refers to - see the note below.Settings search
Registered in
SettingsRegistryasNAVIGABLE_CARDrather thanSWITCH. ASWITCHspec flips the preference straight from the search results, which would bypass the permission prompt and persist the setting with no image access - silently doing nothing. As a navigable card, search jumps to the Library screen and highlights the real, permission-gated toggle.Translations
Four new strings, added to
values/and all 11 locale folders (ar,de,es,fr,in,it,ko,nb,ru,tr,zh-rCN), so this adds no newMissingTranslationfindings.Local checks
Run with JDK 21 (
JAVA_HOME=/opt/homebrew/opt/openjdk@21, OpenJDK 21.0.12.1) after./gradlew clean::app:compileDebugKotlin:app:testDebugUnitTest:app:lintDebuglintDebugalready fails on the base commitdb8cd3b, which I linted separately for comparison:db8cd3bThe error count is identical - those are
MissingTranslationacross the existing string catalogue plus media3UnsafeOptInUsageError, and none of them are in files this PR touches. The one added warning is theSelectedPhotoAccessentry described below.Eight new unit tests. Four in
AlbumArtUtilsTestcover the new seam: bytes returned when enabled,nullwhen disabled (the opt-in gate),nullwhen the cover exceeds the size limit, andnullin an excluded directory even when enabled. Four more inFolderAlbumArtUpdateTestcover the cache-state reconciler: first record, unchanged state, a preference change, and a permission re-grant with no preference change. The four existingfindExternalAlbumArtFiletests are unchanged.Notes for reviewers
AndroidManifest.xml:17:Your app is currently not handling Selected Photos Access introduced in Android 14+ [SelectedPhotoAccess]. This is expected - handling partial photo access would mean accepting a grant that cannot read arbitrary music folders, which is exactly the case this feature must reject. Happy to add a baseline entry or an explicit suppression if you'd prefer the warning silenced.