Use the private MediaScanner API to load cover art

This commit is contained in:
jfim 2010-04-20 20:48:58 -04:00 committed by Christopher Eby
parent 05e4abed9c
commit fc7aae9d81

View File

@ -18,9 +18,6 @@
package org.kreed.vanilla;
import java.io.FileNotFoundException;
import java.io.IOException;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
@ -33,6 +30,12 @@ import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.provider.MediaStore;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Song implements Parcelable {
public static final int FLAG_RANDOM = 0x1;
@ -212,7 +215,7 @@ public class Song implements Parcelable {
private static final Uri ARTWORK_URI = Uri.parse("content://media/external/audio/albumart");
private static final BitmapFactory.Options BITMAP_OPTIONS = new BitmapFactory.Options();
static {
BITMAP_OPTIONS.inPreferredConfig = Bitmap.Config.RGB_565;
BITMAP_OPTIONS.inDither = false;
@ -226,42 +229,125 @@ public class Song implements Parcelable {
Context context = ContextApplication.getContext();
ContentResolver res = context.getContentResolver();
Uri uri;
if (albumId < 1)
uri = ContentUris.withAppendedId(ARTWORK_URI, albumId);
else
uri = Uri.parse("content://media/external/audio/media/" + id + "/albumart");
Bitmap cover;
Bitmap bitmap = null;
ParcelFileDescriptor parcel = null;
// If we have a valid album, try to get the album art from the media store cache
if (0 <= albumId) {
cover = getCoverFromMediaStoreCache(res);
try {
parcel = res.openFileDescriptor(uri, "r");
if (parcel != null)
bitmap = BitmapFactory.decodeFileDescriptor(parcel.getFileDescriptor(), null, BITMAP_OPTIONS);
} catch (IllegalStateException e) {
// The query failed, probably due to the lack of this content provider
// in Android 1.5; fall back to (delayed) MediaStore cache
Uri media = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
String[] albumProjection = { MediaStore.Audio.Albums.ALBUM_ART };
String albumSelection = MediaStore.Audio.Albums._ID + '=' + albumId;
Cursor cursor = res.query(media, albumProjection, albumSelection, null, null);
if (cursor != null) {
if (cursor.moveToNext())
bitmap = BitmapFactory.decodeFile(cursor.getString(0), BITMAP_OPTIONS);
cursor.close();
}
} catch (FileNotFoundException ex) {
} finally {
if (parcel != null) {
try {
parcel.close();
} catch (IOException e) {
}
if (cover == null) {
// Perhaps it hasn't been loaded yet, try to extract it directly from the file
cover = getCoverFromMediaFile(res);
}
} else {
// The android API doesn't allow files without albums to have their album art cached, so get the cover directly from the media file
cover = getCoverFromMediaFile(res);
}
return bitmap;
// Load cover data from an alternate source here!
return cover;
}
}
/**
* Attempts to read the album art directly from a media file.
*
* @param resolver An instance of the content resolver for this app
* @return The album art or null if no album art could be found
*/
private Bitmap getCoverFromMediaFile(ContentResolver resolver)
{
// Use undocumented API to extract the cover from the media file from Eclair
// See http://android.git.kernel.org/?p=platform/packages/apps/Music.git;a=blob;f=src/com/android/music/MusicUtils.java;h=d1aea0660009940a0160cb981f381e2115768845;hb=0749a3f1c11e052f97a3ba60fd624c9283ee7331#l986
Bitmap cover = null;
try {
Uri uri;
if (albumId < 1) {
uri = Uri.parse("content://media/external/audio/media/" + id + "/albumart");
} else {
uri = ContentUris.withAppendedId(ARTWORK_URI, albumId);
}
ParcelFileDescriptor parcelFileDescriptor = resolver.openFileDescriptor(uri, "r");
if (parcelFileDescriptor != null) {
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
cover = BitmapFactory.decodeFileDescriptor(fileDescriptor);
}
// If we couldn't get the cover, try loading it directly using the MediaScanner private API
if (cover == null) {
cover = getCoverFromMediaUsingMediaScanner(resolver);
}
} catch (IllegalStateException e) {
// Apparently we're on 1.5 and the Eclair way of doing things is unsupported, so use another undocumented API to do it
cover = getCoverFromMediaUsingMediaScanner(resolver);
} catch (FileNotFoundException e) {
// Couldn't fetch the cover, try fetching it using the private MediaScanner API
cover = getCoverFromMediaUsingMediaScanner(resolver);
}
return cover;
}
/**
* Obtain the cover from a media file using the private MediaScanner API
*
* @param resolver An instance of the content resolver for this app
* @return The cover or null if there is no cover in the file or the API is unavailable
*/
private Bitmap getCoverFromMediaUsingMediaScanner(ContentResolver resolver)
{
Bitmap cover = null;
// This is a private API, so do everything using reflection
// see http://android.git.kernel.org/?p=platform/packages/apps/Music.git;a=blob;f=src/com/android/music/MusicUtils.java;h=ea2079435ca5e2c6834c9f6f02d07fe7621e0fd9;hb=aae2791ffdd8923d99242f2cf453eb66116fd6b6#l1044
try {
// Attempt to open the media file in read-only mode
Uri uri = Uri.fromFile(new File(path));
FileDescriptor fileDescriptor = resolver.openFileDescriptor(uri, "r").getFileDescriptor();
if (fileDescriptor != null) {
// Construct a MediaScanner
Class mediaScannerClass = Class.forName("android.media.MediaScanner");
Constructor mediaScannerConstructor = mediaScannerClass.getDeclaredConstructor(Context.class);
Object mediaScanner = mediaScannerConstructor.newInstance(ContextApplication.getContext());
// Call extractAlbumArt(fileDescriptor)
Method method = mediaScannerClass.getDeclaredMethod("extractAlbumArt", FileDescriptor.class);
byte[] artBinary = (byte[]) method.invoke(mediaScanner, fileDescriptor);
// Convert the album art to a bitmap
if (artBinary != null)
cover = BitmapFactory.decodeByteArray(artBinary, 0, artBinary.length, BITMAP_OPTIONS);
}
} catch (Exception e) {
// Swallow every exception and return an empty cover if we can't do it due to the API not being there anymore
}
return cover;
}
/**
* Get the cover from the media store cache(this is the official way of doing things)
*
* @param resolver An instance of the content resolver for this app
* @return The cover or null if there is no cover or the media scanner hasn't cached the file yet
*/
private Bitmap getCoverFromMediaStoreCache(ContentResolver resolver)
{
Uri media = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
String[] albumProjection = {MediaStore.Audio.Albums.ALBUM_ART};
String albumSelection = MediaStore.Audio.Albums._ID + '=' + albumId;
Cursor cursor = resolver.query(media, albumProjection, albumSelection, null, null);
if (cursor != null) {
if (cursor.moveToNext())
return BitmapFactory.decodeFile(cursor.getString(0), BITMAP_OPTIONS);
cursor.close();
}
return null;
}
}