/*
* 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.markzhai.lyrichere.utils;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
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.Drawable;
import android.net.Uri;
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.Toast;
import com.markzhai.lyrichere.R;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class MusicUtils {
private static final String TAG = "MusicUtils";
public interface Defs {
int PLAYLIST_SELECTED = 3;
int NEW_PLAYLIST = 4;
int QUEUE = 12;
}
private final static long[] sEmptyList = new long[0];
public static long[] getSongListForCursor(Cursor cursor) {
if (cursor == null) {
return sEmptyList;
}
int len = cursor.getCount();
long[] list = new long[len];
cursor.moveToFirst();
int colidx = -1;
try {
colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
} catch (IllegalArgumentException ex) {
colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
}
for (int i = 0; i < len; i++) {
list[i] = cursor.getLong(colidx);
cursor.moveToNext();
}
return list;
}
public static long[] getSongListForArtist(Context context, long 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) {
long[] list = getSongListForCursor(cursor);
cursor.close();
return list;
}
return sEmptyList;
}
public static long[] getSongListForAlbum(Context context, long 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) {
long[] list = getSongListForCursor(cursor);
cursor.close();
return list;
}
return sEmptyList;
}
public static long[] 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) {
long[] list = getSongListForCursor(cursor);
cursor.close();
return list;
}
return sEmptyList;
}
public static Cursor getAllSongsCursor(Context context) {
Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.DURATION,
MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.TRACK,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.DISPLAY_NAME},
MediaStore.Audio.Media.IS_MUSIC + "=1",
null, null);
return c;
}
public static long[] 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();
long[] list = new long[len];
for (int i = 0; i < len; i++) {
c.moveToNext();
list[i] = c.getLong(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.getLong(0));
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) {
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", plid);
context.getContentResolver().delete(uri, null, null);
return;
}
private static ContentValues[] sContentValuesCache = null;
/**
* @param ids The source array containing all the ids to be added to the playlist
* @param offset Where in the 'ids' array we start reading
* @param len How many items to copy during this pass
* @param base The play order offset to use for this pass
*/
private static void makeInsertItems(long[] ids, int offset, int len, int base) {
// adjust 'len' if would extend beyond the end of the source array
if (offset + len > ids.length) {
len = ids.length - offset;
}
// allocate the ContentValues array, or reallocate if it is the wrong size
if (sContentValuesCache == null || sContentValuesCache.length != len) {
sContentValuesCache = new ContentValues[len];
}
// fill in the ContentValues array with the right values for this pass
for (int i = 0; i < len; i++) {
if (sContentValuesCache[i] == null) {
sContentValuesCache[i] = new ContentValues();
}
sContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i);
sContentValuesCache[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[offset + i]);
}
}
public static void addToPlaylist(Context context, long[] 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;
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();
int numinserted = 0;
for (int i = 0; i < size; i += 1000) {
makeInsertItems(ids, i, 1000, base);
numinserted += resolver.bulkInsert(uri, sContentValuesCache);
}
String message = context.getResources().getQuantityString(
R.plurals.NNNtrackstoplaylist, numinserted, numinserted);
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, int limit) {
try {
ContentResolver resolver = context.getContentResolver();
if (resolver == null) {
return null;
}
if (limit > 0) {
uri = uri.buildUpon().appendQueryParameter("limit", "" + limit).build();
}
return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
} catch (UnsupportedOperationException ex) {
return null;
}
}
public static Cursor query(Context context, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {
return query(context, uri, projection, selection, selectionArgs, sortOrder, 0);
}
public static boolean isMediaScannerScanning(Context context) {
boolean result = false;
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);
}
}
static protected Uri getContentURIForPath(String path) {
return Uri.fromFile(new File(path));
}
// 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 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");
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;
}
// 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, long album_id, int w, int h) {
// NOTE: There is in fact a 1 pixel border on the right side in the ImageView
// used to display this drawable. Take it into account now, so we don't have to
// scale later.
w -= 1;
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);
// Bitmap.createScaledBitmap() can return the same bitmap
if (tmp != b) 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)
* This method always returns the default album art icon when no album art is found.
*/
public static Bitmap getArtwork(Context context, long song_id, long album_id) {
return getArtwork(context, song_id, album_id, true);
}
/**
* 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, long song_id, long album_id,
boolean allowdefault) {
if (album_id < 0) {
// This is something that is not in the database, so get the album art directly
// from the file.
if (song_id >= 0) {
Bitmap bm = getArtworkFromFile(context, song_id, -1);
if (bm != null) {
return bm;
}
}
if (allowdefault) {
return getDefaultArtwork(context);
}
return null;
}
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, song_id, album_id);
if (bm != null) {
if (bm.getConfig() == null) {
bm = bm.copy(Bitmap.Config.RGB_565, false);
if (bm == null && allowdefault) {
return getDefaultArtwork(context);
}
}
} else if (allowdefault) {
bm = getDefaultArtwork(context);
}
return bm;
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException ex) {
}
}
}
return null;
}
// get album art for specified file
private static final String sExternalMediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString();
public static Uri getAlbumArtworkUri(long albumId) {
return ContentUris.withAppendedId(sArtworkUri, albumId);
}
private static Bitmap getArtworkFromFile(Context context, long songid, long albumid) {
Bitmap bm = null;
byte[] art = null;
String path = null;
if (albumid < 0 && songid < 0) {
throw new IllegalArgumentException("Must specify an album or a song id");
}
try {
if (albumid < 0) {
Uri uri = Uri.parse("content://media/external/audio/media/" + songid + "/albumart");
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
if (pfd != null) {
FileDescriptor fd = pfd.getFileDescriptor();
bm = BitmapFactory.decodeFileDescriptor(fd);
}
} else {
Uri uri = ContentUris.withAppendedId(sArtworkUri, albumid);
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
if (pfd != null) {
FileDescriptor fd = pfd.getFileDescriptor();
bm = BitmapFactory.decodeFileDescriptor(fd);
}
}
} catch (IllegalStateException ex) {
} catch (FileNotFoundException ex) {
}
return bm;
}
private static Bitmap getDefaultArtwork(Context context) {
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_default_art, opts);
}
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();
}
}
}
}