11

We are currently obtaining the path of album art using: MediaStore.Audio.AlbumColumns.ALBUM_ART, and is successfully obtaining the path, except on pixel 3a (Android 10). After some research, the ALBUM_ART became deprecated API 29 and over as shown: Here

In this link it says: "Apps may not have file system permissions to directly access this path. Instead of trying to open this path directly, apps should use ContentResolver#loadThumbnail to gain access."

My questions are:
1) I'm already stating on the application manifest the permissions for external storage access (READ_EXTERNAL_STORAGE) and is requesting permission while navigating in-app. Which permissions do i have to provide to allow access to album art in order to obtain the path?

2) I can't seem to find any content on loadThumbnail online (and not even on ContentResolver class through code, while i am using target and compile SDK 29), if 1) can't be done, then how do i use loadThumbnail and why it's not showing on code?

Thanks in advance.

PrakashG
  • 1,642
  • 5
  • 20
  • 30
Serge
  • 143
  • 1
  • 7
  • 1
    As part of the scoped-storage feature on Android Q, Google announced that SAF (storage access framework) will replace the normal storage permissions. This means that even if you will try to use storage permissions, it will only grant to access to specific types of files for File and file-path to be used – Rohit Chauhan Sep 20 '19 at 15:13

3 Answers3

6

In order to use the method of ContentResolver, make sure you have the latest SDK and relevant tools installed, and in your code first instantiate a ContentResolver object and then use it accordingly:

public class MainActivity extends AppCompatActivity {
    public ContentResolver resolver;
    Bitmap albumArt;
    Size size;
    Uri uriOfItem;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        resolver = new ContentResolver(this) {
            @NonNull
            @Override
            public Bitmap loadThumbnail(@NonNull Uri uri, @NonNull Size size, @Nullable CancellationSignal signal) throws IOException {
                return super.loadThumbnail(uri, size, signal);
            }
        };
        //uriOfItem = uri of your file
        size = new Size(100, 100);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            try {
                albumArt = resolver.loadThumbnail(uriOfItem, size, null);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

EDIT: when it comes to your first question if @Rj_Innocent_Coder doesn't mind me including his comment here:

As part of the scoped-storage feature on Android Q, Google announced that SAF (storage access framework) will replace the normal storage permissions. This means that even if you will try to use storage permissions, it will only grant to access to specific types of files for File and file-path to be used

EDIT 2: after @hetoan2 's comment I check the documentation again and I noticed that ContentResolver is abstract hence not being able to use ContentResolver.loadThumbnail() as a method call. That means that within an activity you could simply use the following as well:

Bitmap albumArt = getContentResolver().loadThumbnail(uriOfFile, sizeOfAreaThatDisplaysThumbnail, cancellationSignalOrNull);
Nikos Hidalgo
  • 3,666
  • 9
  • 25
  • 39
  • I don't think that overloading the loadThumbnail method in the ContentResolver class is necessary. Additionally it is worth noting that the Size object passed to the loadThumbnail method should be the desired output size of the Album Art bitmap (matching whatever view you are loading it into). Additionally, fallback code should be present for < Q that uses the MediaStore as previously mentioned by the OP – hetoan2 Sep 20 '19 at 15:16
  • @hetoan2 it seemed weird to me too, but that was the only way I managed to see the method in my Android studio. As for the parameters, I didn't bother explaining them much, as the OP already has the link to the documentation where they're best explained. – Nikos Hidalgo Sep 20 '19 at 15:18
  • Isn't there other way without using loadThumbnail? What if i'm in build tools 28 (where there's no loadThumbnail), using an api 29 phone? (Android 10) – Serge Sep 25 '19 at 16:21
  • @Serge you could still use MediaStore if the Build version is lower than 29 – Nikos Hidalgo Sep 25 '19 at 16:28
  • what do you mean? i am using MediaStore.Audio.AlbumColumns.ALBUM_ART, but using this on an api 29 phone android 10, it does not work. – Serge Sep 26 '19 at 08:11
  • @Serge if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //use . loadThumbnail} else {// use MediaStore.Audio.AlbumColumns.ALBUM_ART} – Nikos Hidalgo Sep 26 '19 at 09:00
  • Yes, that should be done in case you're using api 29 in your application, but what if you're using API 28 in your app and want to obtain the path for ALBUM_ART on a api 29 phone? Let me rephrase my problem: I can obtain obtain the ALBUM_ART path on any phone as long as the phone has android version less than 29, however it does not obtain the path (returns null) only when using a phone with android version 29. So my question is, how can i make it work also in a version 29 phone, without having to use api 29 on my application. – Serge Sep 26 '19 at 11:18
  • I am doing a little workaround where i can obtain the image doing the following: Using this uri:"content://media/external/audio/albumart" and appending it the albumId, then obtaining the stream with contentResolver.openInputStream(uri), and finnaly using BitmapFactory.decodeStream(stream). This works and i can obtain the Bitmap, but by using Bitmap i would have to change alot in the application, and what would really be useful is to use the path that ALBUM_ART should provide. – Serge Sep 26 '19 at 11:40
  • @Serge I'm not aware of any other method that would work on API29. That said, I haven't encountered a scenario yet where I would need to use anything else. – Nikos Hidalgo Sep 26 '19 at 11:48
  • so there's no other way of obtaining the path? loadThumbnail does not return a path, it returns a Bitmap, shouldn't there be a way of obtaining the path even for 29+? – Serge Sep 26 '19 at 13:38
  • @Serge not sure how to have the path instead of the bitmap. Why do you specifically want the path, how are you using it? maybe there's a simpler implementation without you require to make many changes – Nikos Hidalgo Sep 26 '19 at 16:13
  • 1
    I need the path because it's being sent to some speakers ( which in turns sends back all of its metadata periodically, including this path) the app then uses this path to get the album art. There's possible workarounds, but these would require big changes on the application. The ideal would be to have that path! it's weird that you cannot get this path 29+, if it's because of permissions (as they say in the doc i posted above), then android should have some way to allow this in manifest so we can obtain that path.. – Serge Sep 27 '19 at 17:30
  • This is weird. My project is in kotlin and I cannot override loadthumbnail because there is no loadthumbnail to override. – KMC Oct 09 '19 at 03:25
  • @KMC have you tried calling it from the ContentResolver (i.e getContentResolver().loadThumbnail....)? – Nikos Hidalgo Oct 09 '19 at 09:02
  • @NikosHidalgo I did and there's no loadThumbnail. – KMC Oct 09 '19 at 10:15
1

For someone else who is having issues here, this is the solution that worked for me:

if(android.os.Build.VERSION.SDK_INT >= 29)
{
    val album = "Name Of Album"
    val artist = "Name of Artist"
    // Determine album ID first
    val cursor = context.contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
                MediaStore.Audio.Albums.ALBUM_ID,
                "${MediaStore.Audio.Albums.ALBUM} = '$album' AND 
                 ${MediaStore.Audio.Albums.ARTIST} = '$artist'"
                 ,null,null)
    val uri = if(cursor != null && cursor.count > 0)
    {
        cursor.moveToFirst()
        ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, cursor.getString(0).toLong())
    }
    else
    {
        // Dummy URI that will not return an image
        // If you end up here, the album is not in the DataStore
        MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI
    }
    val bm = try
    {
        // Set size based on size of bitmap you want returned
        context.contentResolver.loadThumbnail(uri, Size(50,50), null)
    }
    catch(e: java.lang.Exception)
    {
        // Return default image indicating no image available from DataStore
        BitmapFactory.decodeResource(context.resources, R.drawable.no_image)
    }
}
Rob
  • 126
  • 6
1

try this it will work and load with glide imageView

int thumbColumn = audioCursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID);
   int _thumpId = audioCursor.getInt(thumbColumn);
   imgFilePath = "content://media/external/audio/albumart/"+_thumpId;  
   audioCursor.moveToPosition(i);
  Glide.with(getContext()).load(imgFilePath).placeholder(R.drawable.missed).into(tracksAlbumArt);

Update Andriod Studio latest 4.2.X and targetSdkVersion to 30