OSDN Git Service

Only log the sd card status when it changed from last time.
[android-x86/packages-apps-Music.git] / src / com / android / music / MusicUtils.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.music;
18
19 import android.app.Activity;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.ServiceConnection;
27 import android.content.SharedPreferences;
28 import android.content.SharedPreferences.Editor;
29 import android.content.res.Resources;
30 import android.database.Cursor;
31 import android.graphics.Bitmap;
32 import android.graphics.BitmapFactory;
33 import android.graphics.Canvas;
34 import android.graphics.ColorFilter;
35 import android.graphics.PixelFormat;
36 import android.graphics.drawable.BitmapDrawable;
37 import android.graphics.drawable.Drawable;
38 import android.net.Uri;
39 import android.os.Environment;
40 import android.os.ParcelFileDescriptor;
41 import android.os.RemoteException;
42 import android.provider.MediaStore;
43 import android.provider.Settings;
44 import android.text.TextUtils;
45 import android.util.Log;
46 import android.view.SubMenu;
47 import android.view.View;
48 import android.view.Window;
49 import android.widget.TextView;
50 import android.widget.Toast;
51
52 import java.io.File;
53 import java.io.FileDescriptor;
54 import java.io.FileNotFoundException;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.util.Arrays;
58 import java.util.Formatter;
59 import java.util.HashMap;
60 import java.util.Locale;
61
62 public class MusicUtils {
63
64     private static final String TAG = "MusicUtils";
65
66     public interface Defs {
67         public final static int OPEN_URL = 0;
68         public final static int ADD_TO_PLAYLIST = 1;
69         public final static int USE_AS_RINGTONE = 2;
70         public final static int PLAYLIST_SELECTED = 3;
71         public final static int NEW_PLAYLIST = 4;
72         public final static int PLAY_SELECTION = 5;
73         public final static int GOTO_START = 6;
74         public final static int GOTO_PLAYBACK = 7;
75         public final static int PARTY_SHUFFLE = 8;
76         public final static int SHUFFLE_ALL = 9;
77         public final static int DELETE_ITEM = 10;
78         public final static int SCAN_DONE = 11;
79         public final static int QUEUE = 12;
80         public final static int CHILD_MENU_BASE = 13; // this should be the last item
81     }
82
83     public static String makeAlbumsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) {
84         // There are two formats for the albums/songs information:
85         // "N Song(s)"  - used for unknown artist/album
86         // "N Album(s)" - used for known albums
87         
88         StringBuilder songs_albums = new StringBuilder();
89
90         Resources r = context.getResources();
91         if (isUnknown) {
92             if (numsongs == 1) {
93                 songs_albums.append(context.getString(R.string.onesong));
94             } else {
95                 String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
96                 sFormatBuilder.setLength(0);
97                 sFormatter.format(f, Integer.valueOf(numsongs));
98                 songs_albums.append(sFormatBuilder);
99             }
100         } else {
101             String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
102             sFormatBuilder.setLength(0);
103             sFormatter.format(f, Integer.valueOf(numalbums));
104             songs_albums.append(sFormatBuilder);
105             songs_albums.append(context.getString(R.string.albumsongseparator));
106         }
107         return songs_albums.toString();
108     }
109
110     /**
111      * This is now only used for the query screen
112      */
113     public static String makeAlbumsSongsLabel(Context context, int numalbums, int numsongs, boolean isUnknown) {
114         // There are several formats for the albums/songs information:
115         // "1 Song"   - used if there is only 1 song
116         // "N Songs" - used for the "unknown artist" item
117         // "1 Album"/"N Songs" 
118         // "N Album"/"M Songs"
119         // Depending on locale, these may need to be further subdivided
120         
121         StringBuilder songs_albums = new StringBuilder();
122
123         if (numsongs == 1) {
124             songs_albums.append(context.getString(R.string.onesong));
125         } else {
126             Resources r = context.getResources();
127             if (! isUnknown) {
128                 String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
129                 sFormatBuilder.setLength(0);
130                 sFormatter.format(f, Integer.valueOf(numalbums));
131                 songs_albums.append(sFormatBuilder);
132                 songs_albums.append(context.getString(R.string.albumsongseparator));
133             }
134             String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
135             sFormatBuilder.setLength(0);
136             sFormatter.format(f, Integer.valueOf(numsongs));
137             songs_albums.append(sFormatBuilder);
138         }
139         return songs_albums.toString();
140     }
141     
142     public static IMediaPlaybackService sService = null;
143     private static HashMap<Context, ServiceBinder> sConnectionMap = new HashMap<Context, ServiceBinder>();
144
145     public static boolean bindToService(Context context) {
146         return bindToService(context, null);
147     }
148
149     public static boolean bindToService(Context context, ServiceConnection callback) {
150         context.startService(new Intent(context, MediaPlaybackService.class));
151         ServiceBinder sb = new ServiceBinder(callback);
152         sConnectionMap.put(context, sb);
153         return context.bindService((new Intent()).setClass(context,
154                 MediaPlaybackService.class), sb, 0);
155     }
156     
157     public static void unbindFromService(Context context) {
158         ServiceBinder sb = (ServiceBinder) sConnectionMap.remove(context);
159         if (sb == null) {
160             Log.e("MusicUtils", "Trying to unbind for unknown Context");
161             return;
162         }
163         context.unbindService(sb);
164         if (sConnectionMap.isEmpty()) {
165             // presumably there is nobody interested in the service at this point,
166             // so don't hang on to the ServiceConnection
167             sService = null;
168         }
169     }
170
171     private static class ServiceBinder implements ServiceConnection {
172         ServiceConnection mCallback;
173         ServiceBinder(ServiceConnection callback) {
174             mCallback = callback;
175         }
176         
177         public void onServiceConnected(ComponentName className, android.os.IBinder service) {
178             sService = IMediaPlaybackService.Stub.asInterface(service);
179             initAlbumArtCache();
180             if (mCallback != null) {
181                 mCallback.onServiceConnected(className, service);
182             }
183         }
184         
185         public void onServiceDisconnected(ComponentName className) {
186             if (mCallback != null) {
187                 mCallback.onServiceDisconnected(className);
188             }
189             sService = null;
190         }
191     }
192     
193     public static long getCurrentAlbumId() {
194         if (sService != null) {
195             try {
196                 return sService.getAlbumId();
197             } catch (RemoteException ex) {
198             }
199         }
200         return -1;
201     }
202
203     public static long getCurrentArtistId() {
204         if (MusicUtils.sService != null) {
205             try {
206                 return sService.getArtistId();
207             } catch (RemoteException ex) {
208             }
209         }
210         return -1;
211     }
212
213     public static long getCurrentAudioId() {
214         if (MusicUtils.sService != null) {
215             try {
216                 return sService.getAudioId();
217             } catch (RemoteException ex) {
218             }
219         }
220         return -1;
221     }
222     
223     public static int getCurrentShuffleMode() {
224         int mode = MediaPlaybackService.SHUFFLE_NONE;
225         if (sService != null) {
226             try {
227                 mode = sService.getShuffleMode();
228             } catch (RemoteException ex) {
229             }
230         }
231         return mode;
232     }
233     
234     /*
235      * Returns true if a file is currently opened for playback (regardless
236      * of whether it's playing or paused).
237      */
238     public static boolean isMusicLoaded() {
239         if (MusicUtils.sService != null) {
240             try {
241                 return sService.getPath() != null;
242             } catch (RemoteException ex) {
243             }
244         }
245         return false;
246     }
247
248     private final static long [] sEmptyList = new long[0];
249     
250     public static long [] getSongListForCursor(Cursor cursor) {
251         if (cursor == null) {
252             return sEmptyList;
253         }
254         int len = cursor.getCount();
255         long [] list = new long[len];
256         cursor.moveToFirst();
257         int colidx = -1;
258         try {
259             colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
260         } catch (IllegalArgumentException ex) {
261             colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
262         }
263         for (int i = 0; i < len; i++) {
264             list[i] = cursor.getLong(colidx);
265             cursor.moveToNext();
266         }
267         return list;
268     }
269
270     public static long [] getSongListForArtist(Context context, long id) {
271         final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
272         String where = MediaStore.Audio.Media.ARTIST_ID + "=" + id + " AND " + 
273         MediaStore.Audio.Media.IS_MUSIC + "=1";
274         Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
275                 ccols, where, null,
276                 MediaStore.Audio.Media.ALBUM_KEY + ","  + MediaStore.Audio.Media.TRACK);
277         
278         if (cursor != null) {
279             long [] list = getSongListForCursor(cursor);
280             cursor.close();
281             return list;
282         }
283         return sEmptyList;
284     }
285
286     public static long [] getSongListForAlbum(Context context, long id) {
287         final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
288         String where = MediaStore.Audio.Media.ALBUM_ID + "=" + id + " AND " + 
289                 MediaStore.Audio.Media.IS_MUSIC + "=1";
290         Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
291                 ccols, where, null, MediaStore.Audio.Media.TRACK);
292
293         if (cursor != null) {
294             long [] list = getSongListForCursor(cursor);
295             cursor.close();
296             return list;
297         }
298         return sEmptyList;
299     }
300
301     public static long [] getSongListForPlaylist(Context context, long plid) {
302         final String[] ccols = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID };
303         Cursor cursor = query(context, MediaStore.Audio.Playlists.Members.getContentUri("external", plid),
304                 ccols, null, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
305         
306         if (cursor != null) {
307             long [] list = getSongListForCursor(cursor);
308             cursor.close();
309             return list;
310         }
311         return sEmptyList;
312     }
313     
314     public static void playPlaylist(Context context, long plid) {
315         long [] list = getSongListForPlaylist(context, plid);
316         if (list != null) {
317             playAll(context, list, -1, false);
318         }
319     }
320
321     public static long [] getAllSongs(Context context) {
322         Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
323                 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
324                 null, null);
325         try {
326             if (c == null || c.getCount() == 0) {
327                 return null;
328             }
329             int len = c.getCount();
330             long [] list = new long[len];
331             for (int i = 0; i < len; i++) {
332                 c.moveToNext();
333                 list[i] = c.getLong(0);
334             }
335
336             return list;
337         } finally {
338             if (c != null) {
339                 c.close();
340             }
341         }
342     }
343
344     /**
345      * Fills out the given submenu with items for "new playlist" and
346      * any existing playlists. When the user selects an item, the
347      * application will receive PLAYLIST_SELECTED with the Uri of
348      * the selected playlist, NEW_PLAYLIST if a new playlist
349      * should be created, and QUEUE if the "current playlist" was
350      * selected.
351      * @param context The context to use for creating the menu items
352      * @param sub The submenu to add the items to.
353      */
354     public static void makePlaylistMenu(Context context, SubMenu sub) {
355         String[] cols = new String[] {
356                 MediaStore.Audio.Playlists._ID,
357                 MediaStore.Audio.Playlists.NAME
358         };
359         ContentResolver resolver = context.getContentResolver();
360         if (resolver == null) {
361             System.out.println("resolver = null");
362         } else {
363             String whereclause = MediaStore.Audio.Playlists.NAME + " != ''";
364             Cursor cur = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
365                 cols, whereclause, null,
366                 MediaStore.Audio.Playlists.NAME);
367             sub.clear();
368             sub.add(1, Defs.QUEUE, 0, R.string.queue);
369             sub.add(1, Defs.NEW_PLAYLIST, 0, R.string.new_playlist);
370             if (cur != null && cur.getCount() > 0) {
371                 //sub.addSeparator(1, 0);
372                 cur.moveToFirst();
373                 while (! cur.isAfterLast()) {
374                     Intent intent = new Intent();
375                     intent.putExtra("playlist", cur.getLong(0));
376 //                    if (cur.getInt(0) == mLastPlaylistSelected) {
377 //                        sub.add(0, MusicBaseActivity.PLAYLIST_SELECTED, cur.getString(1)).setIntent(intent);
378 //                    } else {
379                         sub.add(1, Defs.PLAYLIST_SELECTED, 0, cur.getString(1)).setIntent(intent);
380 //                    }
381                     cur.moveToNext();
382                 }
383             }
384             if (cur != null) {
385                 cur.close();
386             }
387         }
388     }
389
390     public static void clearPlaylist(Context context, int plid) {
391         
392         Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", plid);
393         context.getContentResolver().delete(uri, null, null);
394         return;
395     }
396     
397     public static void deleteTracks(Context context, long [] list) {
398         
399         String [] cols = new String [] { MediaStore.Audio.Media._ID, 
400                 MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM_ID };
401         StringBuilder where = new StringBuilder();
402         where.append(MediaStore.Audio.Media._ID + " IN (");
403         for (int i = 0; i < list.length; i++) {
404             where.append(list[i]);
405             if (i < list.length - 1) {
406                 where.append(",");
407             }
408         }
409         where.append(")");
410         Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols,
411                 where.toString(), null, null);
412
413         if (c != null) {
414
415             // step 1: remove selected tracks from the current playlist, as well
416             // as from the album art cache
417             try {
418                 c.moveToFirst();
419                 while (! c.isAfterLast()) {
420                     // remove from current playlist
421                     long id = c.getLong(0);
422                     sService.removeTrack(id);
423                     // remove from album art cache
424                     long artIndex = c.getLong(2);
425                     synchronized(sArtCache) {
426                         sArtCache.remove(artIndex);
427                     }
428                     c.moveToNext();
429                 }
430             } catch (RemoteException ex) {
431             }
432
433             // step 2: remove selected tracks from the database
434             context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, where.toString(), null);
435
436             // step 3: remove files from card
437             c.moveToFirst();
438             while (! c.isAfterLast()) {
439                 String name = c.getString(1);
440                 File f = new File(name);
441                 try {  // File.delete can throw a security exception
442                     if (!f.delete()) {
443                         // I'm not sure if we'd ever get here (deletion would
444                         // have to fail, but no exception thrown)
445                         Log.e("MusicUtils", "Failed to delete file " + name);
446                     }
447                     c.moveToNext();
448                 } catch (SecurityException ex) {
449                     c.moveToNext();
450                 }
451             }
452             c.close();
453         }
454
455         String message = context.getResources().getQuantityString(
456                 R.plurals.NNNtracksdeleted, list.length, Integer.valueOf(list.length));
457         
458         Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
459         // We deleted a number of tracks, which could affect any number of things
460         // in the media content domain, so update everything.
461         context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
462     }
463     
464     public static void addToCurrentPlaylist(Context context, long [] list) {
465         if (sService == null) {
466             return;
467         }
468         try {
469             sService.enqueue(list, MediaPlaybackService.LAST);
470             String message = context.getResources().getQuantityString(
471                     R.plurals.NNNtrackstoplaylist, list.length, Integer.valueOf(list.length));
472             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
473         } catch (RemoteException ex) {
474         }
475     }
476     
477     public static void addToPlaylist(Context context, long [] ids, long playlistid) {
478         if (ids == null) {
479             // this shouldn't happen (the menuitems shouldn't be visible
480             // unless the selected item represents something playable
481             Log.e("MusicBase", "ListSelection null");
482         } else {
483             int size = ids.length;
484             ContentValues values [] = new ContentValues[size];
485             ContentResolver resolver = context.getContentResolver();
486             // need to determine the number of items currently in the playlist,
487             // so the play_order field can be maintained.
488             String[] cols = new String[] {
489                     "count(*)"
490             };
491             Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid);
492             Cursor cur = resolver.query(uri, cols, null, null, null);
493             cur.moveToFirst();
494             int base = cur.getInt(0);
495             cur.close();
496
497             for (int i = 0; i < size; i++) {
498                 values[i] = new ContentValues();
499                 values[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + i));
500                 values[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[i]);
501             }
502             resolver.bulkInsert(uri, values);
503             String message = context.getResources().getQuantityString(
504                     R.plurals.NNNtrackstoplaylist, size, Integer.valueOf(size));
505             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
506             //mLastPlaylistSelected = playlistid;
507         }
508     }
509
510     public static Cursor query(Context context, Uri uri, String[] projection,
511             String selection, String[] selectionArgs, String sortOrder, int limit) {
512         try {
513             ContentResolver resolver = context.getContentResolver();
514             if (resolver == null) {
515                 return null;
516             }
517             if (limit > 0) {
518                 uri = uri.buildUpon().appendQueryParameter("limit", "" + limit).build();
519             }
520             return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
521          } catch (UnsupportedOperationException ex) {
522             return null;
523         }
524         
525     }
526     public static Cursor query(Context context, Uri uri, String[] projection,
527             String selection, String[] selectionArgs, String sortOrder) {
528         return query(context, uri, projection, selection, selectionArgs, sortOrder, 0);
529     }
530     
531     public static boolean isMediaScannerScanning(Context context) {
532         boolean result = false;
533         Cursor cursor = query(context, MediaStore.getMediaScannerUri(), 
534                 new String [] { MediaStore.MEDIA_SCANNER_VOLUME }, null, null, null);
535         if (cursor != null) {
536             if (cursor.getCount() == 1) {
537                 cursor.moveToFirst();
538                 result = "external".equals(cursor.getString(0));
539             }
540             cursor.close(); 
541         } 
542
543         return result;
544     }
545     
546     public static void setSpinnerState(Activity a) {
547         if (isMediaScannerScanning(a)) {
548             // start the progress spinner
549             a.getWindow().setFeatureInt(
550                     Window.FEATURE_INDETERMINATE_PROGRESS,
551                     Window.PROGRESS_INDETERMINATE_ON);
552
553             a.getWindow().setFeatureInt(
554                     Window.FEATURE_INDETERMINATE_PROGRESS,
555                     Window.PROGRESS_VISIBILITY_ON);
556         } else {
557             // stop the progress spinner
558             a.getWindow().setFeatureInt(
559                     Window.FEATURE_INDETERMINATE_PROGRESS,
560                     Window.PROGRESS_VISIBILITY_OFF);
561         }
562     }
563     
564     private static String mLastSdStatus;
565
566     public static void displayDatabaseError(Activity a) {
567         String status = Environment.getExternalStorageState();
568         int title = R.string.sdcard_error_title;
569         int message = R.string.sdcard_error_message;
570         
571         if (status.equals(Environment.MEDIA_SHARED) ||
572                 status.equals(Environment.MEDIA_UNMOUNTED)) {
573             title = R.string.sdcard_busy_title;
574             message = R.string.sdcard_busy_message;
575         } else if (status.equals(Environment.MEDIA_REMOVED)) {
576             title = R.string.sdcard_missing_title;
577             message = R.string.sdcard_missing_message;
578         } else if (status.equals(Environment.MEDIA_MOUNTED)){
579             // The card is mounted, but we didn't get a valid cursor.
580             // This probably means the mediascanner hasn't started scanning the
581             // card yet (there is a small window of time during boot where this
582             // will happen).
583             a.setTitle("");
584             Intent intent = new Intent();
585             intent.setClass(a, ScanningProgress.class);
586             a.startActivityForResult(intent, Defs.SCAN_DONE);
587         } else if (!TextUtils.equals(mLastSdStatus, status)) {
588             mLastSdStatus = status;
589             Log.d(TAG, "sd card: " + status);
590         }
591
592         a.setTitle(title);
593         View v = a.findViewById(R.id.sd_message);
594         if (v != null) {
595             v.setVisibility(View.VISIBLE);
596         }
597         v = a.findViewById(R.id.sd_icon);
598         if (v != null) {
599             v.setVisibility(View.VISIBLE);
600         }
601         v = a.findViewById(android.R.id.list);
602         if (v != null) {
603             v.setVisibility(View.GONE);
604         }
605         TextView tv = (TextView) a.findViewById(R.id.sd_message);
606         tv.setText(message);
607     }
608     
609     public static void hideDatabaseError(Activity a) {
610         View v = a.findViewById(R.id.sd_message);
611         if (v != null) {
612             v.setVisibility(View.GONE);
613         }
614         v = a.findViewById(R.id.sd_icon);
615         if (v != null) {
616             v.setVisibility(View.GONE);
617         }
618         v = a.findViewById(android.R.id.list);
619         if (v != null) {
620             v.setVisibility(View.VISIBLE);
621         }
622     }
623
624     static protected Uri getContentURIForPath(String path) {
625         return Uri.fromFile(new File(path));
626     }
627
628     
629     /*  Try to use String.format() as little as possible, because it creates a
630      *  new Formatter every time you call it, which is very inefficient.
631      *  Reusing an existing Formatter more than tripled the speed of
632      *  makeTimeString().
633      *  This Formatter/StringBuilder are also used by makeAlbumSongsLabel()
634      */
635     private static StringBuilder sFormatBuilder = new StringBuilder();
636     private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());
637     private static final Object[] sTimeArgs = new Object[5];
638
639     public static String makeTimeString(Context context, long secs) {
640         String durationformat = context.getString(
641                 secs < 3600 ? R.string.durationformatshort : R.string.durationformatlong);
642         
643         /* Provide multiple arguments so the format can be changed easily
644          * by modifying the xml.
645          */
646         sFormatBuilder.setLength(0);
647
648         final Object[] timeArgs = sTimeArgs;
649         timeArgs[0] = secs / 3600;
650         timeArgs[1] = secs / 60;
651         timeArgs[2] = (secs / 60) % 60;
652         timeArgs[3] = secs;
653         timeArgs[4] = secs % 60;
654
655         return sFormatter.format(durationformat, timeArgs).toString();
656     }
657     
658     public static void shuffleAll(Context context, Cursor cursor) {
659         playAll(context, cursor, 0, true);
660     }
661
662     public static void playAll(Context context, Cursor cursor) {
663         playAll(context, cursor, 0, false);
664     }
665     
666     public static void playAll(Context context, Cursor cursor, int position) {
667         playAll(context, cursor, position, false);
668     }
669     
670     public static void playAll(Context context, long [] list, int position) {
671         playAll(context, list, position, false);
672     }
673     
674     private static void playAll(Context context, Cursor cursor, int position, boolean force_shuffle) {
675     
676         long [] list = getSongListForCursor(cursor);
677         playAll(context, list, position, force_shuffle);
678     }
679     
680     private static void playAll(Context context, long [] list, int position, boolean force_shuffle) {
681         if (list.length == 0 || sService == null) {
682             Log.d("MusicUtils", "attempt to play empty song list");
683             // Don't try to play empty playlists. Nothing good will come of it.
684             String message = context.getString(R.string.emptyplaylist, list.length);
685             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
686             return;
687         }
688         try {
689             if (force_shuffle) {
690                 sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
691             }
692             long curid = sService.getAudioId();
693             int curpos = sService.getQueuePosition();
694             if (position != -1 && curpos == position && curid == list[position]) {
695                 // The selected file is the file that's currently playing;
696                 // figure out if we need to restart with a new playlist,
697                 // or just launch the playback activity.
698                 long [] playlist = sService.getQueue();
699                 if (Arrays.equals(list, playlist)) {
700                     // we don't need to set a new list, but we should resume playback if needed
701                     sService.play();
702                     return; // the 'finally' block will still run
703                 }
704             }
705             if (position < 0) {
706                 position = 0;
707             }
708             sService.open(list, force_shuffle ? -1 : position);
709             sService.play();
710         } catch (RemoteException ex) {
711         } finally {
712             Intent intent = new Intent("com.android.music.PLAYBACK_VIEWER")
713                 .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
714             context.startActivity(intent);
715         }
716     }
717     
718     public static void clearQueue() {
719         try {
720             sService.removeTracks(0, Integer.MAX_VALUE);
721         } catch (RemoteException ex) {
722         }
723     }
724     
725     // A really simple BitmapDrawable-like class, that doesn't do
726     // scaling, dithering or filtering.
727     private static class FastBitmapDrawable extends Drawable {
728         private Bitmap mBitmap;
729         public FastBitmapDrawable(Bitmap b) {
730             mBitmap = b;
731         }
732         @Override
733         public void draw(Canvas canvas) {
734             canvas.drawBitmap(mBitmap, 0, 0, null);
735         }
736         @Override
737         public int getOpacity() {
738             return PixelFormat.OPAQUE;
739         }
740         @Override
741         public void setAlpha(int alpha) {
742         }
743         @Override
744         public void setColorFilter(ColorFilter cf) {
745         }
746     }
747     
748     private static int sArtId = -2;
749     private static Bitmap mCachedBit = null;
750     private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options();
751     private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
752     private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
753     private static final HashMap<Long, Drawable> sArtCache = new HashMap<Long, Drawable>();
754     private static int sArtCacheId = -1;
755     
756     static {
757         // for the cache, 
758         // 565 is faster to decode and display
759         // and we don't want to dither here because the image will be scaled down later
760         sBitmapOptionsCache.inPreferredConfig = Bitmap.Config.RGB_565;
761         sBitmapOptionsCache.inDither = false;
762
763         sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
764         sBitmapOptions.inDither = false;
765     }
766
767     public static void initAlbumArtCache() {
768         try {
769             int id = sService.getMediaMountedCount();
770             if (id != sArtCacheId) {
771                 clearAlbumArtCache();
772                 sArtCacheId = id; 
773             }
774         } catch (RemoteException e) {
775             e.printStackTrace();
776         }
777     }
778
779     public static void clearAlbumArtCache() {
780         synchronized(sArtCache) {
781             sArtCache.clear();
782         }
783     }
784     
785     public static Drawable getCachedArtwork(Context context, long artIndex, BitmapDrawable defaultArtwork) {
786         Drawable d = null;
787         synchronized(sArtCache) {
788             d = sArtCache.get(artIndex);
789         }
790         if (d == null) {
791             d = defaultArtwork;
792             final Bitmap icon = defaultArtwork.getBitmap();
793             int w = icon.getWidth();
794             int h = icon.getHeight();
795             Bitmap b = MusicUtils.getArtworkQuick(context, artIndex, w, h);
796             if (b != null) {
797                 d = new FastBitmapDrawable(b);
798                 synchronized(sArtCache) {
799                     // the cache may have changed since we checked
800                     Drawable value = sArtCache.get(artIndex);
801                     if (value == null) {
802                         sArtCache.put(artIndex, d);
803                     } else {
804                         d = value;
805                     }
806                 }
807             }
808         }
809         return d;
810     }
811
812     // Get album art for specified album. This method will not try to
813     // fall back to getting artwork directly from the file, nor will
814     // it attempt to repair the database.
815     private static Bitmap getArtworkQuick(Context context, long album_id, int w, int h) {
816         // NOTE: There is in fact a 1 pixel border on the right side in the ImageView
817         // used to display this drawable. Take it into account now, so we don't have to
818         // scale later.
819         w -= 1;
820         ContentResolver res = context.getContentResolver();
821         Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
822         if (uri != null) {
823             ParcelFileDescriptor fd = null;
824             try {
825                 fd = res.openFileDescriptor(uri, "r");
826                 int sampleSize = 1;
827                 
828                 // Compute the closest power-of-two scale factor 
829                 // and pass that to sBitmapOptionsCache.inSampleSize, which will
830                 // result in faster decoding and better quality
831                 sBitmapOptionsCache.inJustDecodeBounds = true;
832                 BitmapFactory.decodeFileDescriptor(
833                         fd.getFileDescriptor(), null, sBitmapOptionsCache);
834                 int nextWidth = sBitmapOptionsCache.outWidth >> 1;
835                 int nextHeight = sBitmapOptionsCache.outHeight >> 1;
836                 while (nextWidth>w && nextHeight>h) {
837                     sampleSize <<= 1;
838                     nextWidth >>= 1;
839                     nextHeight >>= 1;
840                 }
841
842                 sBitmapOptionsCache.inSampleSize = sampleSize;
843                 sBitmapOptionsCache.inJustDecodeBounds = false;
844                 Bitmap b = BitmapFactory.decodeFileDescriptor(
845                         fd.getFileDescriptor(), null, sBitmapOptionsCache);
846
847                 if (b != null) {
848                     // finally rescale to exactly the size we need
849                     if (sBitmapOptionsCache.outWidth != w || sBitmapOptionsCache.outHeight != h) {
850                         Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true);
851                         // Bitmap.createScaledBitmap() can return the same bitmap
852                         if (tmp != b) b.recycle();
853                         b = tmp;
854                     }
855                 }
856                 
857                 return b;
858             } catch (FileNotFoundException e) {
859             } finally {
860                 try {
861                     if (fd != null)
862                         fd.close();
863                 } catch (IOException e) {
864                 }
865             }
866         }
867         return null;
868     }
869     
870     /** Get album art for specified album. You should not pass in the album id
871      * for the "unknown" album here (use -1 instead)
872      */
873     public static Bitmap getArtwork(Context context, long song_id, long album_id) {
874
875         if (album_id < 0) {
876             // This is something that is not in the database, so get the album art directly
877             // from the file.
878             if (song_id >= 0) {
879                 Bitmap bm = getArtworkFromFile(context, song_id, -1);
880                 if (bm != null) {
881                     return bm;
882                 }
883             }
884             return getDefaultArtwork(context);
885         }
886
887         ContentResolver res = context.getContentResolver();
888         Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
889         if (uri != null) {
890             InputStream in = null;
891             try {
892                 in = res.openInputStream(uri);
893                 return BitmapFactory.decodeStream(in, null, sBitmapOptions);
894             } catch (FileNotFoundException ex) {
895                 // The album art thumbnail does not actually exist. Maybe the user deleted it, or
896                 // maybe it never existed to begin with.
897                 Bitmap bm = getArtworkFromFile(context, song_id, album_id);
898                 if (bm != null) {
899                     if (bm.getConfig() == null) {
900                         bm = bm.copy(Bitmap.Config.RGB_565, false);
901                         if (bm == null) {
902                             return getDefaultArtwork(context);
903                         }
904                     }
905                 } else {
906                     bm = getDefaultArtwork(context);
907                 }
908                 return bm;
909             } finally {
910                 try {
911                     if (in != null) {
912                         in.close();
913                     }
914                 } catch (IOException ex) {
915                 }
916             }
917         }
918         
919         return null;
920     }
921     
922     // get album art for specified file
923     private static final String sExternalMediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString();
924     private static Bitmap getArtworkFromFile(Context context, long songid, long albumid) {
925         Bitmap bm = null;
926         byte [] art = null;
927         String path = null;
928
929         if (albumid < 0 && songid < 0) {
930             throw new IllegalArgumentException("Must specify an album or a song id");
931         }
932
933         try {
934             if (albumid < 0) {
935                 Uri uri = Uri.parse("content://media/external/audio/media/" + songid + "/albumart");
936                 ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
937                 if (pfd != null) {
938                     FileDescriptor fd = pfd.getFileDescriptor();
939                     bm = BitmapFactory.decodeFileDescriptor(fd);
940                 }
941             } else {
942                 Uri uri = ContentUris.withAppendedId(sArtworkUri, albumid);
943                 ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
944                 if (pfd != null) {
945                     FileDescriptor fd = pfd.getFileDescriptor();
946                     bm = BitmapFactory.decodeFileDescriptor(fd);
947                 }
948             }
949         } catch (FileNotFoundException ex) {
950             //
951         }
952         if (bm != null) {
953             mCachedBit = bm;
954         }
955         return bm;
956     }
957     
958     private static Bitmap getDefaultArtwork(Context context) {
959         BitmapFactory.Options opts = new BitmapFactory.Options();
960         opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
961         return BitmapFactory.decodeStream(
962                 context.getResources().openRawResource(R.drawable.albumart_mp_unknown), null, opts);
963     }
964     
965     static int getIntPref(Context context, String name, int def) {
966         SharedPreferences prefs =
967             context.getSharedPreferences("com.android.music", Context.MODE_PRIVATE);
968         return prefs.getInt(name, def);
969     }
970     
971     static void setIntPref(Context context, String name, int value) {
972         SharedPreferences prefs =
973             context.getSharedPreferences("com.android.music", Context.MODE_PRIVATE);
974         Editor ed = prefs.edit();
975         ed.putInt(name, value);
976         ed.commit();
977     }
978
979     static void setRingtone(Context context, long id) {
980         ContentResolver resolver = context.getContentResolver();
981         // Set the flag in the database to mark this as a ringtone
982         Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
983         try {
984             ContentValues values = new ContentValues(2);
985             values.put(MediaStore.Audio.Media.IS_RINGTONE, "1");
986             values.put(MediaStore.Audio.Media.IS_ALARM, "1");
987             resolver.update(ringUri, values, null, null);
988         } catch (UnsupportedOperationException ex) {
989             // most likely the card just got unmounted
990             Log.e(TAG, "couldn't set ringtone flag for id " + id);
991             return;
992         }
993
994         String[] cols = new String[] {
995                 MediaStore.Audio.Media._ID,
996                 MediaStore.Audio.Media.DATA,
997                 MediaStore.Audio.Media.TITLE
998         };
999
1000         String where = MediaStore.Audio.Media._ID + "=" + id;
1001         Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1002                 cols, where , null, null);
1003         try {
1004             if (cursor != null && cursor.getCount() == 1) {
1005                 // Set the system setting to make this the current ringtone
1006                 cursor.moveToFirst();
1007                 Settings.System.putString(resolver, Settings.System.RINGTONE, ringUri.toString());
1008                 String message = context.getString(R.string.ringtone_set, cursor.getString(2));
1009                 Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
1010             }
1011         } finally {
1012             if (cursor != null) {
1013                 cursor.close();
1014             }
1015         }
1016     }
1017 }