/* * 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(); } } } }