Last active
January 12, 2024 03:46
-
-
Save dzwillpower/5866205 to your computer and use it in GitHub Desktop.
MusicUtils.java
http://stackoverflow.com/questions/3438809/how-do-i-get-album-thumbnails-in-android
http://www.netmite.com/android/mydroid/packages/apps/Music/src/com/android/music/MusicUtils.java
This file contains 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
/* | |
* Copyright (C) 2008 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.android.music; | |
import java.io.File; | |
import java.io.FileDescriptor; | |
import java.io.FileInputStream; | |
import java.io.FileNotFoundException; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.util.Arrays; | |
import java.util.Formatter; | |
import java.util.HashMap; | |
import java.util.Locale; | |
import android.app.Activity; | |
import android.app.ExpandableListActivity; | |
import android.content.ComponentName; | |
import android.content.ContentResolver; | |
import android.content.ContentUris; | |
import android.content.ContentValues; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.ServiceConnection; | |
import android.content.SharedPreferences; | |
import android.content.SharedPreferences.Editor; | |
import android.content.res.Resources; | |
import android.database.Cursor; | |
import android.graphics.Bitmap; | |
import android.graphics.BitmapFactory; | |
import android.graphics.Canvas; | |
import android.graphics.ColorFilter; | |
import android.graphics.PixelFormat; | |
import android.graphics.drawable.BitmapDrawable; | |
import android.graphics.drawable.Drawable; | |
import android.media.MediaFile; | |
import android.media.MediaScanner; | |
import android.net.Uri; | |
import android.os.RemoteException; | |
import android.os.Environment; | |
import android.os.ParcelFileDescriptor; | |
import android.provider.MediaStore; | |
import android.provider.Settings; | |
import android.util.Log; | |
import android.view.SubMenu; | |
import android.view.Window; | |
import android.widget.TextView; | |
import android.widget.Toast; | |
public class MusicUtils { | |
private static final String TAG = "MusicUtils"; | |
public interface Defs { | |
public final static int OPEN_URL = 0; | |
public final static int ADD_TO_PLAYLIST = 1; | |
public final static int USE_AS_RINGTONE = 2; | |
public final static int PLAYLIST_SELECTED = 3; | |
public final static int NEW_PLAYLIST = 4; | |
public final static int PLAY_SELECTION = 5; | |
public final static int GOTO_START = 6; | |
public final static int GOTO_PLAYBACK = 7; | |
public final static int PARTY_SHUFFLE = 8; | |
public final static int SHUFFLE_ALL = 9; | |
public final static int DELETE_ITEM = 10; | |
public final static int SCAN_DONE = 11; | |
public final static int CHILD_MENU_BASE = 12; | |
public final static int QUEUE = 13; | |
} | |
public static String makeAlbumsSongsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) { | |
// There are several formats for the albums/songs information: | |
// "1 Song" - used if there is only 1 song | |
// "N Songs" - used for the "unknown artist" item | |
// "1 Album"/"N Songs" | |
// "N Album"/"M Songs" | |
// Depending on locale, these may need to be further subdivided | |
StringBuilder songs_albums = new StringBuilder(); | |
if (numsongs == 1) { | |
songs_albums.append(context.getString(R.string.onesong)); | |
} else { | |
Resources r = context.getResources(); | |
if (! isUnknown) { | |
String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString(); | |
sFormatBuilder.setLength(0); | |
sFormatter.format(f, Integer.valueOf(numalbums)); | |
songs_albums.append(sFormatBuilder); | |
songs_albums.append(context.getString(R.string.albumsongseparator)); | |
} | |
String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString(); | |
sFormatBuilder.setLength(0); | |
sFormatter.format(f, Integer.valueOf(numsongs)); | |
songs_albums.append(sFormatBuilder); | |
} | |
return songs_albums.toString(); | |
} | |
public static IMediaPlaybackService sService = null; | |
private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>(); | |
public static boolean bindToService(Context context) { | |
return bindToService(context, null); | |
} | |
public static boolean bindToService(Context context, ServiceConnection callback) { | |
context.startService(new Intent(context, MediaPlaybackService.class)); | |
ServiceBinder sb = new ServiceBinder(callback); | |
sConnectionMap.put(context, sb); | |
return context.bindService((new Intent()).setClass(context, | |
MediaPlaybackService.class), sb, 0); | |
} | |
public static void unbindFromService(Context context) { | |
ServiceBinder sb = (ServiceBinder) sConnectionMap.remove(context); | |
if (sb == null) { | |
Log.e("MusicUtils", "Trying to unbind for unknown Context"); | |
return; | |
} | |
context.unbindService(sb); | |
} | |
private static class ServiceBinder implements ServiceConnection { | |
ServiceConnection mCallback; | |
ServiceBinder(ServiceConnection callback) { | |
mCallback = callback; | |
} | |
public void onServiceConnected(ComponentName className, android.os.IBinder service) { | |
sService = IMediaPlaybackService.Stub.asInterface(service); | |
initAlbumArtCache(); | |
if (mCallback != null) { | |
mCallback.onServiceConnected(className, service); | |
} | |
} | |
public void onServiceDisconnected(ComponentName className) { | |
if (mCallback != null) { | |
mCallback.onServiceDisconnected(className); | |
} | |
sService = null; | |
} | |
} | |
public static int getCurrentAlbumId() { | |
if (sService != null) { | |
try { | |
return sService.getAlbumId(); | |
} catch (RemoteException ex) { | |
} | |
} | |
return -1; | |
} | |
public static int getCurrentArtistId() { | |
if (MusicUtils.sService != null) { | |
try { | |
return sService.getArtistId(); | |
} catch (RemoteException ex) { | |
} | |
} | |
return -1; | |
} | |
public static int getCurrentAudioId() { | |
if (MusicUtils.sService != null) { | |
try { | |
return sService.getAudioId(); | |
} catch (RemoteException ex) { | |
} | |
} | |
return -1; | |
} | |
public static int getCurrentShuffleMode() { | |
int mode = MediaPlaybackService.SHUFFLE_NONE; | |
if (sService != null) { | |
try { | |
mode = sService.getShuffleMode(); | |
} catch (RemoteException ex) { | |
} | |
} | |
return mode; | |
} | |
/* | |
* Returns true if a file is currently opened for playback (regardless | |
* of whether it's playing or paused). | |
*/ | |
public static boolean isMusicLoaded() { | |
if (MusicUtils.sService != null) { | |
try { | |
return sService.getPath() != null; | |
} catch (RemoteException ex) { | |
} | |
} | |
return false; | |
} | |
private final static int [] sEmptyList = new int[0]; | |
public static int [] getSongListForCursor(Cursor cursor) { | |
if (cursor == null) { | |
return sEmptyList; | |
} | |
int len = cursor.getCount(); | |
int [] list = new int[len]; | |
cursor.moveToFirst(); | |
int colidx = cursor.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID); | |
if (colidx < 0) { | |
colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID); | |
} | |
for (int i = 0; i < len; i++) { | |
list[i] = cursor.getInt(colidx); | |
cursor.moveToNext(); | |
} | |
return list; | |
} | |
public static int [] getSongListForArtist(Context context, int id) { | |
final String[] ccols = new String[] { MediaStore.Audio.Media._ID }; | |
String where = MediaStore.Audio.Media.ARTIST_ID + "=" + id + " AND " + | |
MediaStore.Audio.Media.IS_MUSIC + "=1"; | |
Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, | |
ccols, where, null, | |
MediaStore.Audio.Media.ALBUM_KEY + "," + MediaStore.Audio.Media.TRACK); | |
if (cursor != null) { | |
int [] list = getSongListForCursor(cursor); | |
cursor.close(); | |
return list; | |
} | |
return sEmptyList; | |
} | |
public static int [] getSongListForAlbum(Context context, int id) { | |
final String[] ccols = new String[] { MediaStore.Audio.Media._ID }; | |
String where = MediaStore.Audio.Media.ALBUM_ID + "=" + id + " AND " + | |
MediaStore.Audio.Media.IS_MUSIC + "=1"; | |
Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, | |
ccols, where, null, MediaStore.Audio.Media.TRACK); | |
if (cursor != null) { | |
int [] list = getSongListForCursor(cursor); | |
cursor.close(); | |
return list; | |
} | |
return sEmptyList; | |
} | |
public static int [] getSongListForPlaylist(Context context, long plid) { | |
final String[] ccols = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID }; | |
Cursor cursor = query(context, MediaStore.Audio.Playlists.Members.getContentUri("external", plid), | |
ccols, null, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); | |
if (cursor != null) { | |
int [] list = getSongListForCursor(cursor); | |
cursor.close(); | |
return list; | |
} | |
return sEmptyList; | |
} | |
public static void playPlaylist(Context context, long plid) { | |
int [] list = getSongListForPlaylist(context, plid); | |
if (list != null) { | |
playAll(context, list, -1, false); | |
} | |
} | |
public static int [] getAllSongs(Context context) { | |
Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, | |
new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1", | |
null, null); | |
try { | |
if (c == null || c.getCount() == 0) { | |
return null; | |
} | |
int len = c.getCount(); | |
int[] list = new int[len]; | |
for (int i = 0; i < len; i++) { | |
c.moveToNext(); | |
list[i] = c.getInt(0); | |
} | |
return list; | |
} finally { | |
if (c != null) { | |
c.close(); | |
} | |
} | |
} | |
/** | |
* Fills out the given submenu with items for "new playlist" and | |
* any existing playlists. When the user selects an item, the | |
* application will receive PLAYLIST_SELECTED with the Uri of | |
* the selected playlist, NEW_PLAYLIST if a new playlist | |
* should be created, and QUEUE if the "current playlist" was | |
* selected. | |
* @param context The context to use for creating the menu items | |
* @param sub The submenu to add the items to. | |
*/ | |
public static void makePlaylistMenu(Context context, SubMenu sub) { | |
String[] cols = new String[] { | |
MediaStore.Audio.Playlists._ID, | |
MediaStore.Audio.Playlists.NAME | |
}; | |
ContentResolver resolver = context.getContentResolver(); | |
if (resolver == null) { | |
System.out.println("resolver = null"); | |
} else { | |
String whereclause = MediaStore.Audio.Playlists.NAME + " != ''"; | |
Cursor cur = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, | |
cols, whereclause, null, | |
MediaStore.Audio.Playlists.NAME); | |
sub.clear(); | |
sub.add(1, Defs.QUEUE, 0, R.string.queue); | |
sub.add(1, Defs.NEW_PLAYLIST, 0, R.string.new_playlist); | |
if (cur != null && cur.getCount() > 0) { | |
//sub.addSeparator(1, 0); | |
cur.moveToFirst(); | |
while (! cur.isAfterLast()) { | |
Intent intent = new Intent(); | |
intent.putExtra("playlist", cur.getInt(0)); | |
// if (cur.getInt(0) == mLastPlaylistSelected) { | |
// sub.add(0, MusicBaseActivity.PLAYLIST_SELECTED, cur.getString(1)).setIntent(intent); | |
// } else { | |
sub.add(1, Defs.PLAYLIST_SELECTED, 0, cur.getString(1)).setIntent(intent); | |
// } | |
cur.moveToNext(); | |
} | |
} | |
if (cur != null) { | |
cur.close(); | |
} | |
} | |
} | |
public static void clearPlaylist(Context context, int plid) { | |
final String[] ccols = new String[] { MediaStore.Audio.Playlists.Members._ID }; | |
Cursor cursor = query(context, MediaStore.Audio.Playlists.Members.getContentUri("external", plid), | |
ccols, null, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); | |
if (cursor == null) { | |
return; | |
} | |
cursor.moveToFirst(); | |
while (!cursor.isAfterLast()) { | |
cursor.deleteRow(); | |
} | |
cursor.commitUpdates(); | |
cursor.close(); | |
return; | |
} | |
public static void deleteTracks(Context context, int [] list) { | |
String [] cols = new String [] { MediaStore.Audio.Media._ID, | |
MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM_ID }; | |
StringBuilder where = new StringBuilder(); | |
where.append(MediaStore.Audio.Media._ID + " IN ("); | |
for (int i = 0; i < list.length; i++) { | |
where.append(list[i]); | |
if (i < list.length - 1) { | |
where.append(","); | |
} | |
} | |
where.append(")"); | |
Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols, | |
where.toString(), null, null); | |
if (c != null) { | |
// step 1: remove selected tracks from the current playlist, as well | |
// as from the album art cache | |
try { | |
c.moveToFirst(); | |
while (! c.isAfterLast()) { | |
// remove from current playlist | |
int id = c.getInt(0); | |
sService.removeTrack(id); | |
// remove from album art cache | |
int artIndex = c.getInt(2); | |
synchronized(sArtCache) { | |
sArtCache.remove(artIndex); | |
} | |
c.moveToNext(); | |
} | |
} catch (RemoteException ex) { | |
} | |
// step 2: remove selected tracks from the database | |
context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, where.toString(), null); | |
// step 3: remove files from card | |
c.moveToFirst(); | |
while (! c.isAfterLast()) { | |
String name = c.getString(1); | |
File f = new File(name); | |
try { // File.delete can throw a security exception | |
if (!f.delete()) { | |
// I'm not sure if we'd ever get here (deletion would | |
// have to fail, but no exception thrown) | |
Log.e("MusicUtils", "Failed to delete file " + name); | |
} | |
c.moveToNext(); | |
} catch (SecurityException ex) { | |
c.moveToNext(); | |
} | |
} | |
c.commitUpdates(); | |
c.close(); | |
} | |
String message = context.getResources().getQuantityString( | |
R.plurals.NNNtracksdeleted, list.length, Integer.valueOf(list.length)); | |
Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); | |
// We deleted a number of tracks, which could affect any number of things | |
// in the media content domain, so update everything. | |
context.getContentResolver().notifyChange(Uri.parse("content://media"), null); | |
} | |
public static void addToCurrentPlaylist(Context context, int [] list) { | |
if (sService == null) { | |
return; | |
} | |
try { | |
sService.enqueue(list, MediaPlaybackService.LAST); | |
String message = context.getResources().getQuantityString( | |
R.plurals.NNNtrackstoplaylist, list.length, Integer.valueOf(list.length)); | |
Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); | |
} catch (RemoteException ex) { | |
} | |
} | |
public static void addToPlaylist(Context context, int [] ids, long playlistid) { | |
if (ids == null) { | |
// this shouldn't happen (the menuitems shouldn't be visible | |
// unless the selected item represents something playable | |
Log.e("MusicBase", "ListSelection null"); | |
} else { | |
int size = ids.length; | |
ContentValues values [] = new ContentValues[size]; | |
ContentResolver resolver = context.getContentResolver(); | |
// need to determine the number of items currently in the playlist, | |
// so the play_order field can be maintained. | |
String[] cols = new String[] { | |
"count(*)" | |
}; | |
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid); | |
Cursor cur = resolver.query(uri, cols, null, null, null); | |
cur.moveToFirst(); | |
int base = cur.getInt(0); | |
cur.close(); | |
for (int i = 0; i < size; i++) { | |
values[i] = new ContentValues(); | |
values[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + i)); | |
values[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[i]); | |
} | |
resolver.bulkInsert(uri, values); | |
String message = context.getResources().getQuantityString( | |
R.plurals.NNNtrackstoplaylist, size, Integer.valueOf(size)); | |
Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); | |
//mLastPlaylistSelected = playlistid; | |
} | |
} | |
public static Cursor query(Context context, Uri uri, String[] projection, | |
String selection, String[] selectionArgs, String sortOrder) { | |
try { | |
ContentResolver resolver = context.getContentResolver(); | |
if (resolver == null) { | |
return null; | |
} | |
return resolver.query(uri, projection, selection, selectionArgs, sortOrder); | |
} catch (UnsupportedOperationException ex) { | |
return null; | |
} | |
} | |
public static boolean isMediaScannerScanning(Context context) { | |
boolean result = false; | |
Uri uri = MediaStore.getMediaScannerUri(); | |
Cursor cursor = query(context, MediaStore.getMediaScannerUri(), | |
new String [] { MediaStore.MEDIA_SCANNER_VOLUME }, null, null, null); | |
if (cursor != null) { | |
if (cursor.getCount() == 1) { | |
cursor.moveToFirst(); | |
result = "external".equals(cursor.getString(0)); | |
} | |
cursor.close(); | |
} | |
return result; | |
} | |
public static void setSpinnerState(Activity a) { | |
if (isMediaScannerScanning(a)) { | |
// start the progress spinner | |
a.getWindow().setFeatureInt( | |
Window.FEATURE_INDETERMINATE_PROGRESS, | |
Window.PROGRESS_INDETERMINATE_ON); | |
a.getWindow().setFeatureInt( | |
Window.FEATURE_INDETERMINATE_PROGRESS, | |
Window.PROGRESS_VISIBILITY_ON); | |
} else { | |
// stop the progress spinner | |
a.getWindow().setFeatureInt( | |
Window.FEATURE_INDETERMINATE_PROGRESS, | |
Window.PROGRESS_VISIBILITY_OFF); | |
} | |
} | |
public static void displayDatabaseError(Activity a) { | |
String status = Environment.getExternalStorageState(); | |
int title = R.string.sdcard_error_title; | |
int message = R.string.sdcard_error_message; | |
if (status.equals(Environment.MEDIA_SHARED)) { | |
title = R.string.sdcard_busy_title; | |
message = R.string.sdcard_busy_message; | |
} else if (status.equals(Environment.MEDIA_REMOVED)) { | |
title = R.string.sdcard_missing_title; | |
message = R.string.sdcard_missing_message; | |
} else if (status.equals(Environment.MEDIA_MOUNTED)){ | |
// The card is mounted, but we didn't get a valid cursor. | |
// This probably means the mediascanner hasn't started scanning the | |
// card yet (there is a small window of time during boot where this | |
// will happen). | |
a.setTitle(""); | |
Intent intent = new Intent(); | |
intent.setClass(a, ScanningProgress.class); | |
a.startActivityForResult(intent, Defs.SCAN_DONE); | |
} else { | |
Log.d(TAG, "sd card: " + status); | |
} | |
a.setTitle(title); | |
if (a instanceof ExpandableListActivity) { | |
a.setContentView(R.layout.no_sd_card_expanding); | |
} else { | |
a.setContentView(R.layout.no_sd_card); | |
} | |
TextView tv = (TextView) a.findViewById(R.id.sd_message); | |
tv.setText(message); | |
} | |
static protected Uri getContentURIForPath(String path) { | |
return Uri.fromFile(new File(path)); | |
} | |
/* Try to use String.format() as little as possible, because it creates a | |
* new Formatter every time you call it, which is very inefficient. | |
* Reusing an existing Formatter more than tripled the speed of | |
* makeTimeString(). | |
* This Formatter/StringBuilder are also used by makeAlbumSongsLabel() | |
*/ | |
private static StringBuilder sFormatBuilder = new StringBuilder(); | |
private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault()); | |
private static final Object[] sTimeArgs = new Object[5]; | |
public static String makeTimeString(Context context, long secs) { | |
String durationformat = context.getString(R.string.durationformat); | |
/* Provide multiple arguments so the format can be changed easily | |
* by modifying the xml. | |
*/ | |
sFormatBuilder.setLength(0); | |
final Object[] timeArgs = sTimeArgs; | |
timeArgs[0] = secs / 3600; | |
timeArgs[1] = secs / 60; | |
timeArgs[2] = (secs / 60) % 60; | |
timeArgs[3] = secs; | |
timeArgs[4] = secs % 60; | |
return sFormatter.format(durationformat, timeArgs).toString(); | |
} | |
public static void shuffleAll(Context context, Cursor cursor) { | |
playAll(context, cursor, 0, true); | |
} | |
public static void playAll(Context context, Cursor cursor) { | |
playAll(context, cursor, 0, false); | |
} | |
public static void playAll(Context context, Cursor cursor, int position) { | |
playAll(context, cursor, position, false); | |
} | |
public static void playAll(Context context, int [] list, int position) { | |
playAll(context, list, position, false); | |
} | |
private static void playAll(Context context, Cursor cursor, int position, boolean force_shuffle) { | |
int [] list = getSongListForCursor(cursor); | |
playAll(context, list, position, force_shuffle); | |
} | |
private static void playAll(Context context, int [] list, int position, boolean force_shuffle) { | |
if (list.length == 0 || sService == null) { | |
Log.d("MusicUtils", "attempt to play empty song list"); | |
// Don't try to play empty playlists. Nothing good will come of it. | |
String message = context.getString(R.string.emptyplaylist, list.length); | |
Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); | |
return; | |
} | |
try { | |
if (force_shuffle) { | |
sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL); | |
} | |
int curid = sService.getAudioId(); | |
int curpos = sService.getQueuePosition(); | |
if (position != -1 && curpos == position && curid == list[position]) { | |
// The selected file is the file that's currently playing; | |
// figure out if we need to restart with a new playlist, | |
// or just launch the playback activity. | |
int [] playlist = sService.getQueue(); | |
if (Arrays.equals(list, playlist)) { | |
// we don't need to set a new list, but we should resume playback if needed | |
sService.play(); | |
return; // the 'finally' block will still run | |
} | |
} | |
if (position < 0) { | |
position = 0; | |
} | |
sService.open(list, position); | |
sService.play(); | |
} catch (RemoteException ex) { | |
} finally { | |
Intent intent = new Intent("com.android.music.PLAYBACK_VIEWER") | |
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); | |
context.startActivity(intent); | |
} | |
} | |
public static void clearQueue() { | |
try { | |
sService.removeTracks(0, Integer.MAX_VALUE); | |
} catch (RemoteException ex) { | |
} | |
} | |
// A really simple BitmapDrawable-like class, that doesn't do | |
// scaling, dithering or filtering. | |
private static class FastBitmapDrawable extends Drawable { | |
private Bitmap mBitmap; | |
public FastBitmapDrawable(Bitmap b) { | |
mBitmap = b; | |
} | |
@Override | |
public void draw(Canvas canvas) { | |
canvas.drawBitmap(mBitmap, 0, 0, null); | |
} | |
@Override | |
public int getOpacity() { | |
return PixelFormat.OPAQUE; | |
} | |
@Override | |
public void setAlpha(int alpha) { | |
} | |
@Override | |
public void setColorFilter(ColorFilter cf) { | |
} | |
} | |
private static int sArtId = -2; | |
private static byte [] mCachedArt; | |
private static Bitmap mCachedBit = null; | |
private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options(); | |
private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options(); | |
private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart"); | |
private static final HashMap<Integer, Drawable> sArtCache = new HashMap<Integer, Drawable>(); | |
private static int sArtCacheId = -1; | |
static { | |
// for the cache, | |
// 565 is faster to decode and display | |
// and we don't want to dither here because the image will be scaled down later | |
sBitmapOptionsCache.inPreferredConfig = Bitmap.Config.RGB_565; | |
sBitmapOptionsCache.inDither = false; | |
sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565; | |
sBitmapOptions.inDither = false; | |
} | |
public static void initAlbumArtCache() { | |
try { | |
int id = sService.getMediaMountedCount(); | |
if (id != sArtCacheId) { | |
clearAlbumArtCache(); | |
sArtCacheId = id; | |
} | |
} catch (RemoteException e) { | |
e.printStackTrace(); | |
} | |
} | |
public static void clearAlbumArtCache() { | |
synchronized(sArtCache) { | |
sArtCache.clear(); | |
} | |
} | |
public static Drawable getCachedArtwork(Context context, int artIndex, BitmapDrawable defaultArtwork) { | |
Drawable d = null; | |
synchronized(sArtCache) { | |
d = sArtCache.get(artIndex); | |
} | |
if (d == null) { | |
d = defaultArtwork; | |
final Bitmap icon = defaultArtwork.getBitmap(); | |
int w = icon.getWidth(); | |
int h = icon.getHeight(); | |
Bitmap b = MusicUtils.getArtworkQuick(context, artIndex, w, h); | |
if (b != null) { | |
d = new FastBitmapDrawable(b); | |
synchronized(sArtCache) { | |
// the cache may have changed since we checked | |
Drawable value = sArtCache.get(artIndex); | |
if (value == null) { | |
sArtCache.put(artIndex, d); | |
} else { | |
d = value; | |
} | |
} | |
} | |
} | |
return d; | |
} | |
// Get album art for specified album. This method will not try to | |
// fall back to getting artwork directly from the file, nor will | |
// it attempt to repair the database. | |
private static Bitmap getArtworkQuick(Context context, int album_id, int w, int h) { | |
// NOTE: There is in fact a 1 pixel frame in the ImageView used to | |
// display this drawable. Take it into account now, so we don't have to | |
// scale later. | |
w -= 2; | |
h -= 2; | |
ContentResolver res = context.getContentResolver(); | |
Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id); | |
if (uri != null) { | |
ParcelFileDescriptor fd = null; | |
try { | |
fd = res.openFileDescriptor(uri, "r"); | |
int sampleSize = 1; | |
// Compute the closest power-of-two scale factor | |
// and pass that to sBitmapOptionsCache.inSampleSize, which will | |
// result in faster decoding and better quality | |
sBitmapOptionsCache.inJustDecodeBounds = true; | |
BitmapFactory.decodeFileDescriptor( | |
fd.getFileDescriptor(), null, sBitmapOptionsCache); | |
int nextWidth = sBitmapOptionsCache.outWidth >> 1; | |
int nextHeight = sBitmapOptionsCache.outHeight >> 1; | |
while (nextWidth>w && nextHeight>h) { | |
sampleSize <<= 1; | |
nextWidth >>= 1; | |
nextHeight >>= 1; | |
} | |
sBitmapOptionsCache.inSampleSize = sampleSize; | |
sBitmapOptionsCache.inJustDecodeBounds = false; | |
Bitmap b = BitmapFactory.decodeFileDescriptor( | |
fd.getFileDescriptor(), null, sBitmapOptionsCache); | |
if (b != null) { | |
// finally rescale to exactly the size we need | |
if (sBitmapOptionsCache.outWidth != w || sBitmapOptionsCache.outHeight != h) { | |
Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true); | |
b.recycle(); | |
b = tmp; | |
} | |
} | |
return b; | |
} catch (FileNotFoundException e) { | |
} finally { | |
try { | |
if (fd != null) | |
fd.close(); | |
} catch (IOException e) { | |
} | |
} | |
} | |
return null; | |
} | |
// Get album art for specified album. You should not pass in the album id | |
// for the "unknown" album here (use -1 instead) | |
public static Bitmap getArtwork(Context context, int album_id) { | |
if (album_id < 0) { | |
// This is something that is not in the database, so get the album art directly | |
// from the file. | |
Bitmap bm = getArtworkFromFile(context, null, -1); | |
if (bm != null) { | |
return bm; | |
} | |
return getDefaultArtwork(context); | |
} | |
ContentResolver res = context.getContentResolver(); | |
Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id); | |
if (uri != null) { | |
InputStream in = null; | |
try { | |
in = res.openInputStream(uri); | |
return BitmapFactory.decodeStream(in, null, sBitmapOptions); | |
} catch (FileNotFoundException ex) { | |
// The album art thumbnail does not actually exist. Maybe the user deleted it, or | |
// maybe it never existed to begin with. | |
Bitmap bm = getArtworkFromFile(context, null, album_id); | |
if (bm != null) { | |
// Put the newly found artwork in the database. | |
// Note that this shouldn't be done for the "unknown" album, | |
// but if this method is called correctly, that won't happen. | |
// first write it somewhere | |
String file = Environment.getExternalStorageDirectory() | |
+ "/albumthumbs/" + String.valueOf(System.currentTimeMillis()); | |
if (ensureFileExists(file)) { | |
try { | |
OutputStream outstream = new FileOutputStream(file); | |
if (bm.getConfig() == null) { | |
bm = bm.copy(Bitmap.Config.RGB_565, false); | |
if (bm == null) { | |
return getDefaultArtwork(context); | |
} | |
} | |
boolean success = bm.compress(Bitmap.CompressFormat.JPEG, 75, outstream); | |
outstream.close(); | |
if (success) { | |
ContentValues values = new ContentValues(); | |
values.put("album_id", album_id); | |
values.put("_data", file); | |
Uri newuri = res.insert(sArtworkUri, values); | |
if (newuri == null) { | |
// Failed to insert in to the database. The most likely | |
// cause of this is that the item already existed in the | |
// database, and the most likely cause of that is that | |
// the album was scanned before, but the user deleted the | |
// album art from the sd card. | |
// We can ignore that case here, since the media provider | |
// will regenerate the album art for those entries when | |
// it detects this. | |
success = false; | |
} | |
} | |
if (!success) { | |
File f = new File(file); | |
f.delete(); | |
} | |
} catch (FileNotFoundException e) { | |
Log.e(TAG, "error creating file", e); | |
} catch (IOException e) { | |
Log.e(TAG, "error creating file", e); | |
} | |
} | |
} else { | |
bm = getDefaultArtwork(context); | |
} | |
return bm; | |
} finally { | |
try { | |
if (in != null) { | |
in.close(); | |
} | |
} catch (IOException ex) { | |
} | |
} | |
} | |
return null; | |
} | |
// copied from MediaProvider | |
private static boolean ensureFileExists(String path) { | |
File file = new File(path); | |
if (file.exists()) { | |
return true; | |
} else { | |
// we will not attempt to create the first directory in the path | |
// (for example, do not create /sdcard if the SD card is not mounted) | |
int secondSlash = path.indexOf('/', 1); | |
if (secondSlash < 1) return false; | |
String directoryPath = path.substring(0, secondSlash); | |
File directory = new File(directoryPath); | |
if (!directory.exists()) | |
return false; | |
file.getParentFile().mkdirs(); | |
try { | |
return file.createNewFile(); | |
} catch(IOException ioe) { | |
Log.e(TAG, "File creation failed", ioe); | |
} | |
return false; | |
} | |
} | |
// get album art for specified file | |
private static final String sExternalMediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString(); | |
private static Bitmap getArtworkFromFile(Context context, Uri uri, int albumid) { | |
Bitmap bm = null; | |
byte [] art = null; | |
String path = null; | |
if (sArtId == albumid) { | |
//Log.i("@@@@@@ ", "reusing cached data", new Exception()); | |
if (mCachedBit != null) { | |
return mCachedBit; | |
} | |
art = mCachedArt; | |
} else { | |
// try reading embedded artwork | |
if (uri == null) { | |
try { | |
int curalbum = sService.getAlbumId(); | |
if (curalbum == albumid || albumid < 0) { | |
path = sService.getPath(); | |
if (path != null) { | |
uri = Uri.parse(path); | |
} | |
} | |
} catch (RemoteException ex) { | |
} | |
} | |
if (uri == null) { | |
if (albumid >= 0) { | |
Cursor c = query(context,MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, | |
new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.ALBUM }, | |
MediaStore.Audio.Media.ALBUM_ID + "=?", new String [] {String.valueOf(albumid)}, | |
null); | |
if (c != null) { | |
c.moveToFirst(); | |
if (!c.isAfterLast()) { | |
int trackid = c.getInt(0); | |
uri = ContentUris.withAppendedId( | |
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, trackid); | |
} | |
if (c.getString(1).equals(MediaFile.UNKNOWN_STRING)) { | |
albumid = -1; | |
} | |
c.close(); | |
} | |
} | |
} | |
if (uri != null) { | |
MediaScanner scanner = new MediaScanner(context); | |
ParcelFileDescriptor pfd = null; | |
try { | |
pfd = context.getContentResolver().openFileDescriptor(uri, "r"); | |
if (pfd != null) { | |
FileDescriptor fd = pfd.getFileDescriptor(); | |
art = scanner.extractAlbumArt(fd); | |
} | |
} catch (IOException ex) { | |
} catch (SecurityException ex) { | |
} finally { | |
try { | |
if (pfd != null) { | |
pfd.close(); | |
} | |
} catch (IOException ex) { | |
} | |
} | |
} | |
} | |
// if no embedded art exists, look for AlbumArt.jpg in same directory as the media file | |
if (art == null && path != null) { | |
if (path.startsWith(sExternalMediaUri)) { | |
// get the real path | |
Cursor c = query(context,Uri.parse(path), | |
new String[] { MediaStore.Audio.Media.DATA}, | |
null, null, null); | |
if (c != null) { | |
c.moveToFirst(); | |
if (!c.isAfterLast()) { | |
path = c.getString(0); | |
} | |
c.close(); | |
} | |
} | |
int lastSlash = path.lastIndexOf('/'); | |
if (lastSlash > 0) { | |
String artPath = path.substring(0, lastSlash + 1) + "AlbumArt.jpg"; | |
File file = new File(artPath); | |
if (file.exists()) { | |
art = new byte[(int)file.length()]; | |
FileInputStream stream = null; | |
try { | |
stream = new FileInputStream(file); | |
stream.read(art); | |
} catch (IOException ex) { | |
art = null; | |
} finally { | |
try { | |
if (stream != null) { | |
stream.close(); | |
} | |
} catch (IOException ex) { | |
} | |
} | |
} else { | |
// TODO: try getting album art from the web | |
} | |
} | |
} | |
if (art != null) { | |
try { | |
// get the size of the bitmap | |
BitmapFactory.Options opts = new BitmapFactory.Options(); | |
opts.inJustDecodeBounds = true; | |
opts.inSampleSize = 1; | |
BitmapFactory.decodeByteArray(art, 0, art.length, opts); | |
// request a reasonably sized output image | |
// TODO: don't hardcode the size | |
while (opts.outHeight > 320 || opts.outWidth > 320) { | |
opts.outHeight /= 2; | |
opts.outWidth /= 2; | |
opts.inSampleSize *= 2; | |
} | |
// get the image for real now | |
opts.inJustDecodeBounds = false; | |
bm = BitmapFactory.decodeByteArray(art, 0, art.length, opts); | |
if (albumid != -1) { | |
sArtId = albumid; | |
} | |
mCachedArt = art; | |
mCachedBit = bm; | |
} catch (Exception e) { | |
} | |
} | |
return bm; | |
} | |
private static Bitmap getDefaultArtwork(Context context) { | |
BitmapFactory.Options opts = new BitmapFactory.Options(); | |
opts.inPreferredConfig = Bitmap.Config.ARGB_8888; | |
return BitmapFactory.decodeStream( | |
context.getResources().openRawResource(R.drawable.albumart_mp_unknown), null, opts); | |
} | |
static int getIntPref(Context context, String name, int def) { | |
SharedPreferences prefs = | |
context.getSharedPreferences("com.android.music", Context.MODE_PRIVATE); | |
return prefs.getInt(name, def); | |
} | |
static void setIntPref(Context context, String name, int value) { | |
SharedPreferences prefs = | |
context.getSharedPreferences("com.android.music", Context.MODE_PRIVATE); | |
Editor ed = prefs.edit(); | |
ed.putInt(name, value); | |
ed.commit(); | |
} | |
static void setRingtone(Context context, long id) { | |
ContentResolver resolver = context.getContentResolver(); | |
// Set the flag in the database to mark this as a ringtone | |
Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id); | |
try { | |
ContentValues values = new ContentValues(2); | |
values.put(MediaStore.Audio.Media.IS_RINGTONE, "1"); | |
values.put(MediaStore.Audio.Media.IS_ALARM, "1"); | |
resolver.update(ringUri, values, null, null); | |
} catch (UnsupportedOperationException ex) { | |
// most likely the card just got unmounted | |
Log.e(TAG, "couldn't set ringtone flag for id " + id); | |
return; | |
} | |
String[] cols = new String[] { | |
MediaStore.Audio.Media._ID, | |
MediaStore.Audio.Media.DATA, | |
MediaStore.Audio.Media.TITLE | |
}; | |
String where = MediaStore.Audio.Media._ID + "=" + id; | |
Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, | |
cols, where , null, null); | |
try { | |
if (cursor != null && cursor.getCount() == 1) { | |
// Set the system setting to make this the current ringtone | |
cursor.moveToFirst(); | |
Settings.System.putString(resolver, Settings.System.RINGTONE, ringUri.toString()); | |
String message = context.getString(R.string.ringtone_set, cursor.getString(2)); | |
Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); | |
} | |
} finally { | |
if (cursor != null) { | |
cursor.close(); | |
} | |
} | |
} | |
} | |
syntax highlighted by Code2HTML, v. 0.9.1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment