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.ContextWrapper;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.content.SharedPreferences;
29 import android.content.SharedPreferences.Editor;
30 import android.content.res.Resources;
31 import android.database.Cursor;
32 import android.graphics.Bitmap;
33 import android.graphics.BitmapFactory;
34 import android.graphics.Canvas;
35 import android.graphics.ColorFilter;
36 import android.graphics.ColorMatrix;
37 import android.graphics.ColorMatrixColorFilter;
38 import android.graphics.Matrix;
39 import android.graphics.Paint;
40 import android.graphics.PixelFormat;
41 import android.graphics.drawable.BitmapDrawable;
42 import android.graphics.drawable.Drawable;
43 import android.net.Uri;
44 import android.os.Environment;
45 import android.os.ParcelFileDescriptor;
46 import android.os.RemoteException;
47 import android.provider.MediaStore;
48 import android.provider.Settings;
49 import android.text.TextUtils;
50 import android.text.format.Time;
51 import android.util.Log;
52 import android.view.Menu;
53 import android.view.MenuItem;
54 import android.view.SubMenu;
55 import android.view.View;
56 import android.view.Window;
57 import android.widget.TabWidget;
58 import android.widget.TextView;
59 import android.widget.Toast;
60 
61 import java.io.File;
62 import java.io.FileDescriptor;
63 import java.io.FileNotFoundException;
64 import java.io.IOException;
65 import java.io.InputStream;
66 import java.io.PrintWriter;
67 import java.util.Arrays;
68 import java.util.Formatter;
69 import java.util.HashMap;
70 import java.util.Locale;
71 
72 public class MusicUtils {
73     private static final String TAG = "MusicUtils";
74 
75     public interface Defs {
76         public final static int OPEN_URL = 0;
77         public final static int ADD_TO_PLAYLIST = 1;
78         public final static int USE_AS_RINGTONE = 2;
79         public final static int PLAYLIST_SELECTED = 3;
80         public final static int NEW_PLAYLIST = 4;
81         public final static int PLAY_SELECTION = 5;
82         public final static int GOTO_START = 6;
83         public final static int GOTO_PLAYBACK = 7;
84         public final static int PARTY_SHUFFLE = 8;
85         public final static int SHUFFLE_ALL = 9;
86         public final static int DELETE_ITEM = 10;
87         public final static int SCAN_DONE = 11;
88         public final static int QUEUE = 12;
89         public final static int EFFECTS_PANEL = 13;
90         public final static int CHILD_MENU_BASE = 14; // this should be the last item
91     }
92 
makeAlbumsLabel( Context context, int numalbums, int numsongs, boolean isUnknown)93     public static String makeAlbumsLabel(
94             Context context, int numalbums, int numsongs, boolean isUnknown) {
95         // There are two formats for the albums/songs information:
96         // "N Song(s)"  - used for unknown artist/album
97         // "N Album(s)" - used for known albums
98 
99         StringBuilder songs_albums = new StringBuilder();
100 
101         Resources r = context.getResources();
102         if (isUnknown) {
103             if (numsongs == 1) {
104                 songs_albums.append(context.getString(R.string.onesong));
105             } else {
106                 String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
107                 sFormatBuilder.setLength(0);
108                 sFormatter.format(f, Integer.valueOf(numsongs));
109                 songs_albums.append(sFormatBuilder);
110             }
111         } else {
112             String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
113             sFormatBuilder.setLength(0);
114             sFormatter.format(f, Integer.valueOf(numalbums));
115             songs_albums.append(sFormatBuilder);
116             songs_albums.append(context.getString(R.string.albumsongseparator));
117         }
118         return songs_albums.toString();
119     }
120 
121     /**
122      * This is now only used for the query screen
123      */
makeAlbumsSongsLabel( Context context, int numalbums, int numsongs, boolean isUnknown)124     public static String makeAlbumsSongsLabel(
125             Context context, int numalbums, int numsongs, boolean isUnknown) {
126         // There are several formats for the albums/songs information:
127         // "1 Song"   - used if there is only 1 song
128         // "N Songs" - used for the "unknown artist" item
129         // "1 Album"/"N Songs"
130         // "N Album"/"M Songs"
131         // Depending on locale, these may need to be further subdivided
132 
133         StringBuilder songs_albums = new StringBuilder();
134 
135         if (numsongs == 1) {
136             songs_albums.append(context.getString(R.string.onesong));
137         } else {
138             Resources r = context.getResources();
139             if (!isUnknown) {
140                 String f = r.getQuantityText(R.plurals.Nalbums, numalbums).toString();
141                 sFormatBuilder.setLength(0);
142                 sFormatter.format(f, Integer.valueOf(numalbums));
143                 songs_albums.append(sFormatBuilder);
144                 songs_albums.append(context.getString(R.string.albumsongseparator));
145             }
146             String f = r.getQuantityText(R.plurals.Nsongs, numsongs).toString();
147             sFormatBuilder.setLength(0);
148             sFormatter.format(f, Integer.valueOf(numsongs));
149             songs_albums.append(sFormatBuilder);
150         }
151         return songs_albums.toString();
152     }
153 
154     public static IMediaPlaybackService sService = null;
155     private static HashMap<Context, ServiceBinder> sConnectionMap =
156             new HashMap<Context, ServiceBinder>();
157 
158     public static class ServiceToken {
159         ContextWrapper mWrappedContext;
ServiceToken(ContextWrapper context)160         ServiceToken(ContextWrapper context) {
161             mWrappedContext = context;
162         }
163     }
164 
bindToService(Activity context)165     public static ServiceToken bindToService(Activity context) {
166         return bindToService(context, null);
167     }
168 
bindToService(Activity context, ServiceConnection callback)169     public static ServiceToken bindToService(Activity context, ServiceConnection callback) {
170         Activity realActivity = context.getParent();
171         if (realActivity == null) {
172             realActivity = context;
173         }
174         ContextWrapper cw = new ContextWrapper(realActivity);
175         cw.startService(new Intent(cw, MediaPlaybackService.class));
176         ServiceBinder sb = new ServiceBinder(callback);
177         if (cw.bindService((new Intent()).setClass(cw, MediaPlaybackService.class), sb, 0)) {
178             sConnectionMap.put(cw, sb);
179             return new ServiceToken(cw);
180         }
181         Log.e("Music", "Failed to bind to service");
182         return null;
183     }
184 
unbindFromService(ServiceToken token)185     public static void unbindFromService(ServiceToken token) {
186         if (token == null) {
187             Log.e("MusicUtils", "Trying to unbind with null token");
188             return;
189         }
190         ContextWrapper cw = token.mWrappedContext;
191         ServiceBinder sb = sConnectionMap.remove(cw);
192         if (sb == null) {
193             Log.e("MusicUtils", "Trying to unbind for unknown Context");
194             return;
195         }
196         cw.unbindService(sb);
197         if (sConnectionMap.isEmpty()) {
198             // presumably there is nobody interested in the service at this point,
199             // so don't hang on to the ServiceConnection
200             sService = null;
201         }
202     }
203 
204     private static class ServiceBinder implements ServiceConnection {
205         ServiceConnection mCallback;
ServiceBinder(ServiceConnection callback)206         ServiceBinder(ServiceConnection callback) {
207             mCallback = callback;
208         }
209 
onServiceConnected(ComponentName className, android.os.IBinder service)210         public void onServiceConnected(ComponentName className, android.os.IBinder service) {
211             sService = IMediaPlaybackService.Stub.asInterface(service);
212             initAlbumArtCache();
213             if (mCallback != null) {
214                 mCallback.onServiceConnected(className, service);
215             }
216         }
217 
onServiceDisconnected(ComponentName className)218         public void onServiceDisconnected(ComponentName className) {
219             if (mCallback != null) {
220                 mCallback.onServiceDisconnected(className);
221             }
222             sService = null;
223         }
224     }
225 
getCurrentAlbumId()226     public static long getCurrentAlbumId() {
227         if (sService != null) {
228             try {
229                 return sService.getAlbumId();
230             } catch (RemoteException ex) {
231             }
232         }
233         return -1;
234     }
235 
getCurrentArtistId()236     public static long getCurrentArtistId() {
237         if (MusicUtils.sService != null) {
238             try {
239                 return sService.getArtistId();
240             } catch (RemoteException ex) {
241             }
242         }
243         return -1;
244     }
245 
getCurrentAudioId()246     public static long getCurrentAudioId() {
247         if (MusicUtils.sService != null) {
248             try {
249                 return sService.getAudioId();
250             } catch (RemoteException ex) {
251             }
252         }
253         return -1;
254     }
255 
getCurrentShuffleMode()256     public static int getCurrentShuffleMode() {
257         int mode = MediaPlaybackService.SHUFFLE_NONE;
258         if (sService != null) {
259             try {
260                 mode = sService.getShuffleMode();
261             } catch (RemoteException ex) {
262             }
263         }
264         return mode;
265     }
266 
togglePartyShuffle()267     public static void togglePartyShuffle() {
268         if (sService != null) {
269             int shuffle = getCurrentShuffleMode();
270             try {
271                 if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
272                     sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NONE);
273                 } else {
274                     sService.setShuffleMode(MediaPlaybackService.SHUFFLE_AUTO);
275                 }
276             } catch (RemoteException ex) {
277             }
278         }
279     }
280 
setPartyShuffleMenuIcon(Menu menu)281     public static void setPartyShuffleMenuIcon(Menu menu) {
282         MenuItem item = menu.findItem(Defs.PARTY_SHUFFLE);
283         if (item != null) {
284             int shuffle = MusicUtils.getCurrentShuffleMode();
285             if (shuffle == MediaPlaybackService.SHUFFLE_AUTO) {
286                 item.setIcon(R.drawable.ic_menu_party_shuffle);
287                 item.setTitle(R.string.party_shuffle_off);
288             } else {
289                 item.setIcon(R.drawable.ic_menu_party_shuffle);
290                 item.setTitle(R.string.party_shuffle);
291             }
292         }
293     }
294 
295     /*
296      * Returns true if a file is currently opened for playback (regardless
297      * of whether it's playing or paused).
298      */
isMusicLoaded()299     public static boolean isMusicLoaded() {
300         if (MusicUtils.sService != null) {
301             try {
302                 return sService.getPath() != null;
303             } catch (RemoteException ex) {
304             }
305         }
306         return false;
307     }
308 
309     private final static long[] sEmptyList = new long[0];
310 
getSongListForCursor(Cursor cursor)311     public static long[] getSongListForCursor(Cursor cursor) {
312         if (cursor == null) {
313             return sEmptyList;
314         }
315         int len = cursor.getCount();
316         long[] list = new long[len];
317         cursor.moveToFirst();
318         int colidx = -1;
319         try {
320             colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID);
321         } catch (IllegalArgumentException ex) {
322             colidx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
323         }
324         for (int i = 0; i < len; i++) {
325             list[i] = cursor.getLong(colidx);
326             cursor.moveToNext();
327         }
328         return list;
329     }
330 
getSongListForArtist(Context context, long id)331     public static long[] getSongListForArtist(Context context, long id) {
332         final String[] ccols = new String[] {MediaStore.Audio.Media._ID};
333         String where = MediaStore.Audio.Media.ARTIST_ID + "=" + id + " AND "
334                 + MediaStore.Audio.Media.IS_MUSIC + "=1";
335         Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols, where,
336                 null, MediaStore.Audio.Media.ALBUM_KEY + "," + MediaStore.Audio.Media.TRACK);
337 
338         if (cursor != null) {
339             long[] list = getSongListForCursor(cursor);
340             cursor.close();
341             return list;
342         }
343         return sEmptyList;
344     }
345 
getSongListForAlbum(Context context, long id)346     public static long[] getSongListForAlbum(Context context, long id) {
347         final String[] ccols = new String[] {MediaStore.Audio.Media._ID};
348         String where = MediaStore.Audio.Media.ALBUM_ID + "=" + id + " AND "
349                 + MediaStore.Audio.Media.IS_MUSIC + "=1";
350         Cursor cursor = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols, where,
351                 null, MediaStore.Audio.Media.TRACK);
352 
353         if (cursor != null) {
354             long[] list = getSongListForCursor(cursor);
355             cursor.close();
356             return list;
357         }
358         return sEmptyList;
359     }
360 
getSongListForPlaylist(Context context, long plid)361     public static long[] getSongListForPlaylist(Context context, long plid) {
362         final String[] ccols = new String[] {MediaStore.Audio.Playlists.Members.AUDIO_ID};
363         Cursor cursor =
364                 query(context, MediaStore.Audio.Playlists.Members.getContentUri("external", plid),
365                         ccols, null, null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
366 
367         if (cursor != null) {
368             long[] list = getSongListForCursor(cursor);
369             cursor.close();
370             return list;
371         }
372         return sEmptyList;
373     }
374 
playPlaylist(Context context, long plid)375     public static void playPlaylist(Context context, long plid) {
376         long[] list = getSongListForPlaylist(context, plid);
377         if (list != null) {
378             playAll(context, list, -1, false);
379         }
380     }
381 
getAllSongs(Context context)382     public static long[] getAllSongs(Context context) {
383         Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
384                 new String[] {MediaStore.Audio.Media._ID}, MediaStore.Audio.Media.IS_MUSIC + "=1",
385                 null, null);
386         try {
387             if (c == null || c.getCount() == 0) {
388                 return null;
389             }
390             int len = c.getCount();
391             long[] list = new long[len];
392             for (int i = 0; i < len; i++) {
393                 c.moveToNext();
394                 list[i] = c.getLong(0);
395             }
396 
397             return list;
398         } finally {
399             if (c != null) {
400                 c.close();
401             }
402         }
403     }
404 
405     /**
406      * Fills out the given submenu with items for "new playlist" and
407      * any existing playlists. When the user selects an item, the
408      * application will receive PLAYLIST_SELECTED with the Uri of
409      * the selected playlist, NEW_PLAYLIST if a new playlist
410      * should be created, and QUEUE if the "current playlist" was
411      * selected.
412      * @param context The context to use for creating the menu items
413      * @param sub The submenu to add the items to.
414      */
makePlaylistMenu(Context context, SubMenu sub)415     public static void makePlaylistMenu(Context context, SubMenu sub) {
416         String[] cols =
417                 new String[] {MediaStore.Audio.Playlists._ID, MediaStore.Audio.Playlists.NAME};
418         ContentResolver resolver = context.getContentResolver();
419         if (resolver == null) {
420             System.out.println("resolver = null");
421         } else {
422             String whereclause = MediaStore.Audio.Playlists.NAME + " != ''";
423             Cursor cur = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, cols,
424                     whereclause, null, MediaStore.Audio.Playlists.NAME);
425             sub.clear();
426             sub.add(1, Defs.QUEUE, 0, R.string.queue);
427             sub.add(1, Defs.NEW_PLAYLIST, 0, R.string.new_playlist);
428             if (cur != null && cur.getCount() > 0) {
429                 // sub.addSeparator(1, 0);
430                 cur.moveToFirst();
431                 while (!cur.isAfterLast()) {
432                     Intent intent = new Intent();
433                     intent.putExtra("playlist", cur.getLong(0));
434                     //                    if (cur.getInt(0) == mLastPlaylistSelected) {
435                     //                        sub.add(0, MusicBaseActivity.PLAYLIST_SELECTED,
436                     //                        cur.getString(1)).setIntent(intent);
437                     //                    } else {
438                     sub.add(1, Defs.PLAYLIST_SELECTED, 0, cur.getString(1)).setIntent(intent);
439                     //                    }
440                     cur.moveToNext();
441                 }
442             }
443             if (cur != null) {
444                 cur.close();
445             }
446         }
447     }
448 
clearPlaylist(Context context, int plid)449     public static void clearPlaylist(Context context, int plid) {
450         Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", plid);
451         context.getContentResolver().delete(uri, null, null);
452         return;
453     }
454 
deleteTracks(Context context, long[] list)455     public static void deleteTracks(Context context, long[] list) {
456         String[] cols = new String[] {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA,
457                 MediaStore.Audio.Media.ALBUM_ID};
458         StringBuilder where = new StringBuilder();
459         where.append(MediaStore.Audio.Media._ID + " IN (");
460         for (int i = 0; i < list.length; i++) {
461             where.append(list[i]);
462             if (i < list.length - 1) {
463                 where.append(",");
464             }
465         }
466         where.append(")");
467         Cursor c = query(context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols,
468                 where.toString(), null, null);
469 
470         if (c != null) {
471             // step 1: remove selected tracks from the current playlist, as well
472             // as from the album art cache
473             try {
474                 c.moveToFirst();
475                 while (!c.isAfterLast()) {
476                     // remove from current playlist
477                     long id = c.getLong(0);
478                     sService.removeTrack(id);
479                     // remove from album art cache
480                     long artIndex = c.getLong(2);
481                     synchronized (sArtCache) {
482                         sArtCache.remove(artIndex);
483                     }
484                     c.moveToNext();
485                 }
486             } catch (RemoteException ex) {
487             }
488 
489             // step 2: remove selected tracks from the database
490             context.getContentResolver().delete(
491                     MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, where.toString(), null);
492 
493             // step 3: remove files from card
494             c.moveToFirst();
495             while (!c.isAfterLast()) {
496                 String name = c.getString(1);
497                 File f = new File(name);
498                 try { // File.delete can throw a security exception
499                     if (!f.delete()) {
500                         // I'm not sure if we'd ever get here (deletion would
501                         // have to fail, but no exception thrown)
502                         Log.e("MusicUtils", "Failed to delete file " + name);
503                     }
504                     c.moveToNext();
505                 } catch (SecurityException ex) {
506                     c.moveToNext();
507                 }
508             }
509             c.close();
510         }
511 
512         String message = context.getResources().getQuantityString(
513                 R.plurals.NNNtracksdeleted, list.length, Integer.valueOf(list.length));
514 
515         Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
516         // We deleted a number of tracks, which could affect any number of things
517         // in the media content domain, so update everything.
518         context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
519     }
520 
addToCurrentPlaylist(Context context, long[] list)521     public static void addToCurrentPlaylist(Context context, long[] list) {
522         if (sService == null) {
523             return;
524         }
525         try {
526             sService.enqueue(list, MediaPlaybackService.LAST);
527             String message = context.getResources().getQuantityString(
528                     R.plurals.NNNtrackstoplaylist, list.length, Integer.valueOf(list.length));
529             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
530         } catch (RemoteException ex) {
531         }
532     }
533 
534     private static ContentValues[] sContentValuesCache = null;
535 
536     /**
537      * @param ids The source array containing all the ids to be added to the playlist
538      * @param offset Where in the 'ids' array we start reading
539      * @param len How many items to copy during this pass
540      * @param base The play order offset to use for this pass
541      */
makeInsertItems(long[] ids, int offset, int len, int base)542     private static void makeInsertItems(long[] ids, int offset, int len, int base) {
543         // adjust 'len' if would extend beyond the end of the source array
544         if (offset + len > ids.length) {
545             len = ids.length - offset;
546         }
547         // allocate the ContentValues array, or reallocate if it is the wrong size
548         if (sContentValuesCache == null || sContentValuesCache.length != len) {
549             sContentValuesCache = new ContentValues[len];
550         }
551         // fill in the ContentValues array with the right values for this pass
552         for (int i = 0; i < len; i++) {
553             if (sContentValuesCache[i] == null) {
554                 sContentValuesCache[i] = new ContentValues();
555             }
556 
557             sContentValuesCache[i].put(
558                     MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i);
559             sContentValuesCache[i].put(
560                     MediaStore.Audio.Playlists.Members.AUDIO_ID, ids[offset + i]);
561         }
562     }
563 
addToPlaylist(Context context, long[] ids, long playlistid)564     public static void addToPlaylist(Context context, long[] ids, long playlistid) {
565         if (ids == null) {
566             // this shouldn't happen (the menuitems shouldn't be visible
567             // unless the selected item represents something playable
568             Log.e("MusicBase", "ListSelection null");
569         } else {
570             int size = ids.length;
571             ContentResolver resolver = context.getContentResolver();
572             // need to determine the number of items currently in the playlist,
573             // so the play_order field can be maintained.
574             String[] cols = new String[] {"count(*)"};
575             Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid);
576             Cursor cur = resolver.query(uri, cols, null, null, null);
577             cur.moveToFirst();
578             int base = cur.getInt(0);
579             cur.close();
580             int numinserted = 0;
581             for (int i = 0; i < size; i += 1000) {
582                 makeInsertItems(ids, i, 1000, base);
583                 numinserted += resolver.bulkInsert(uri, sContentValuesCache);
584             }
585             String message = context.getResources().getQuantityString(
586                     R.plurals.NNNtrackstoplaylist, numinserted, numinserted);
587             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
588             // mLastPlaylistSelected = playlistid;
589         }
590     }
591 
query(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, int limit)592     public static Cursor query(Context context, Uri uri, String[] projection, String selection,
593             String[] selectionArgs, String sortOrder, int limit) {
594         try {
595             ContentResolver resolver = context.getContentResolver();
596             if (resolver == null) {
597                 return null;
598             }
599             if (limit > 0) {
600                 uri = uri.buildUpon().appendQueryParameter("limit", "" + limit).build();
601             }
602             return resolver.query(uri, projection, selection, selectionArgs, sortOrder);
603         } catch (UnsupportedOperationException ex) {
604             return null;
605         }
606     }
query(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)607     public static Cursor query(Context context, Uri uri, String[] projection, String selection,
608             String[] selectionArgs, String sortOrder) {
609         return query(context, uri, projection, selection, selectionArgs, sortOrder, 0);
610     }
611 
isMediaScannerScanning(Context context)612     public static boolean isMediaScannerScanning(Context context) {
613         boolean result = false;
614         Cursor cursor = query(context, MediaStore.getMediaScannerUri(),
615                 new String[] {MediaStore.MEDIA_SCANNER_VOLUME}, null, null, null);
616         if (cursor != null) {
617             if (cursor.getCount() == 1) {
618                 cursor.moveToFirst();
619                 result = "external".equals(cursor.getString(0));
620             }
621             cursor.close();
622         }
623 
624         return result;
625     }
626 
setSpinnerState(Activity a)627     public static void setSpinnerState(Activity a) {
628         if (isMediaScannerScanning(a)) {
629             // start the progress spinner
630             a.getWindow().setFeatureInt(
631                     Window.FEATURE_INDETERMINATE_PROGRESS, Window.PROGRESS_INDETERMINATE_ON);
632 
633             a.getWindow().setFeatureInt(
634                     Window.FEATURE_INDETERMINATE_PROGRESS, Window.PROGRESS_VISIBILITY_ON);
635         } else {
636             // stop the progress spinner
637             a.getWindow().setFeatureInt(
638                     Window.FEATURE_INDETERMINATE_PROGRESS, Window.PROGRESS_VISIBILITY_OFF);
639         }
640     }
641 
642     private static String mLastSdStatus;
643 
displayDatabaseError(Activity a)644     public static void displayDatabaseError(Activity a) {
645         if (a.isFinishing()) {
646             // When switching tabs really fast, we can end up with a null
647             // cursor (not sure why), which will bring us here.
648             // Don't bother showing an error message in that case.
649             return;
650         }
651 
652         String status = Environment.getExternalStorageState();
653         int title, message;
654 
655         if (android.os.Environment.isExternalStorageRemovable()) {
656             title = R.string.sdcard_error_title;
657             message = R.string.sdcard_error_message;
658         } else {
659             title = R.string.sdcard_error_title_nosdcard;
660             message = R.string.sdcard_error_message_nosdcard;
661         }
662 
663         if (status.equals(Environment.MEDIA_SHARED) || status.equals(Environment.MEDIA_UNMOUNTED)) {
664             if (android.os.Environment.isExternalStorageRemovable()) {
665                 title = R.string.sdcard_busy_title;
666                 message = R.string.sdcard_busy_message;
667             } else {
668                 title = R.string.sdcard_busy_title_nosdcard;
669                 message = R.string.sdcard_busy_message_nosdcard;
670             }
671         } else if (status.equals(Environment.MEDIA_REMOVED)) {
672             if (android.os.Environment.isExternalStorageRemovable()) {
673                 title = R.string.sdcard_missing_title;
674                 message = R.string.sdcard_missing_message;
675             } else {
676                 title = R.string.sdcard_missing_title_nosdcard;
677                 message = R.string.sdcard_missing_message_nosdcard;
678             }
679         } else if (status.equals(Environment.MEDIA_MOUNTED)) {
680             // The card is mounted, but we didn't get a valid cursor.
681             // This probably means the mediascanner hasn't started scanning the
682             // card yet (there is a small window of time during boot where this
683             // will happen).
684             a.setTitle("");
685             Intent intent = new Intent();
686             intent.setClass(a, ScanningProgress.class);
687             a.startActivityForResult(intent, Defs.SCAN_DONE);
688         } else if (!TextUtils.equals(mLastSdStatus, status)) {
689             mLastSdStatus = status;
690             Log.d(TAG, "sd card: " + status);
691         }
692 
693         a.setTitle(title);
694         View v = a.findViewById(R.id.sd_message);
695         if (v != null) {
696             v.setVisibility(View.VISIBLE);
697         }
698         v = a.findViewById(R.id.sd_icon);
699         if (v != null) {
700             v.setVisibility(View.VISIBLE);
701         }
702         v = a.findViewById(android.R.id.list);
703         if (v != null) {
704             v.setVisibility(View.GONE);
705         }
706         v = a.findViewById(R.id.buttonbar);
707         if (v != null) {
708             v.setVisibility(View.GONE);
709         }
710         TextView tv = (TextView) a.findViewById(R.id.sd_message);
711         tv.setText(message);
712     }
713 
hideDatabaseError(Activity a)714     public static void hideDatabaseError(Activity a) {
715         View v = a.findViewById(R.id.sd_message);
716         if (v != null) {
717             v.setVisibility(View.GONE);
718         }
719         v = a.findViewById(R.id.sd_icon);
720         if (v != null) {
721             v.setVisibility(View.GONE);
722         }
723         v = a.findViewById(android.R.id.list);
724         if (v != null) {
725             v.setVisibility(View.VISIBLE);
726         }
727     }
728 
getContentURIForPath(String path)729     static protected Uri getContentURIForPath(String path) {
730         return Uri.fromFile(new File(path));
731     }
732 
733     /*  Try to use String.format() as little as possible, because it creates a
734      *  new Formatter every time you call it, which is very inefficient.
735      *  Reusing an existing Formatter more than tripled the speed of
736      *  makeTimeString().
737      *  This Formatter/StringBuilder are also used by makeAlbumSongsLabel()
738      */
739     private static StringBuilder sFormatBuilder = new StringBuilder();
740     private static Formatter sFormatter = new Formatter(sFormatBuilder, Locale.getDefault());
741     private static final Object[] sTimeArgs = new Object[5];
742 
makeTimeString(Context context, long secs)743     public static String makeTimeString(Context context, long secs) {
744         String durationformat = context.getString(
745                 secs < 3600 ? R.string.durationformatshort : R.string.durationformatlong);
746 
747         /* Provide multiple arguments so the format can be changed easily
748          * by modifying the xml.
749          */
750         sFormatBuilder.setLength(0);
751 
752         final Object[] timeArgs = sTimeArgs;
753         timeArgs[0] = secs / 3600;
754         timeArgs[1] = secs / 60;
755         timeArgs[2] = (secs / 60) % 60;
756         timeArgs[3] = secs;
757         timeArgs[4] = secs % 60;
758 
759         return sFormatter.format(durationformat, timeArgs).toString();
760     }
761 
shuffleAll(Context context, Cursor cursor)762     public static void shuffleAll(Context context, Cursor cursor) {
763         playAll(context, cursor, 0, true);
764     }
765 
playAll(Context context, Cursor cursor)766     public static void playAll(Context context, Cursor cursor) {
767         playAll(context, cursor, 0, false);
768     }
769 
playAll(Context context, Cursor cursor, int position)770     public static void playAll(Context context, Cursor cursor, int position) {
771         playAll(context, cursor, position, false);
772     }
773 
playAll(Context context, long[] list, int position)774     public static void playAll(Context context, long[] list, int position) {
775         playAll(context, list, position, false);
776     }
777 
playAll( Context context, Cursor cursor, int position, boolean force_shuffle)778     private static void playAll(
779             Context context, Cursor cursor, int position, boolean force_shuffle) {
780         long[] list = getSongListForCursor(cursor);
781         playAll(context, list, position, force_shuffle);
782     }
783 
playAll(Context context, long[] list, int position, boolean force_shuffle)784     private static void playAll(Context context, long[] list, int position, boolean force_shuffle) {
785         if (list.length == 0 || sService == null) {
786             Log.d("MusicUtils", "attempt to play empty song list");
787             // Don't try to play empty playlists. Nothing good will come of it.
788             String message = context.getString(R.string.emptyplaylist, list.length);
789             Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
790             return;
791         }
792         try {
793             if (force_shuffle) {
794                 sService.setShuffleMode(MediaPlaybackService.SHUFFLE_NORMAL);
795             }
796             long curid = sService.getAudioId();
797             int curpos = sService.getQueuePosition();
798             if (position != -1 && curpos == position && curid == list[position]) {
799                 // The selected file is the file that's currently playing;
800                 // figure out if we need to restart with a new playlist,
801                 // or just launch the playback activity.
802                 long[] playlist = sService.getQueue();
803                 if (Arrays.equals(list, playlist)) {
804                     // we don't need to set a new list, but we should resume playback if needed
805                     sService.play();
806                     return; // the 'finally' block will still run
807                 }
808             }
809             if (position < 0) {
810                 position = 0;
811             }
812             sService.open(list, force_shuffle ? -1 : position);
813             sService.play();
814         } catch (RemoteException ex) {
815         } finally {
816             Intent intent = new Intent("com.android.music.PLAYBACK_VIEWER")
817                                     .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
818             context.startActivity(intent);
819         }
820     }
821 
clearQueue()822     public static void clearQueue() {
823         try {
824             sService.removeTracks(0, Integer.MAX_VALUE);
825         } catch (RemoteException ex) {
826         }
827     }
828 
829     // A really simple BitmapDrawable-like class, that doesn't do
830     // scaling, dithering or filtering.
831     private static class FastBitmapDrawable extends Drawable {
832         private Bitmap mBitmap;
FastBitmapDrawable(Bitmap b)833         public FastBitmapDrawable(Bitmap b) {
834             mBitmap = b;
835         }
836         @Override
draw(Canvas canvas)837         public void draw(Canvas canvas) {
838             canvas.drawBitmap(mBitmap, 0, 0, null);
839         }
840         @Override
getOpacity()841         public int getOpacity() {
842             return PixelFormat.OPAQUE;
843         }
844         @Override
setAlpha(int alpha)845         public void setAlpha(int alpha) {}
846         @Override
setColorFilter(ColorFilter cf)847         public void setColorFilter(ColorFilter cf) {}
848     }
849 
850     private static int sArtId = -2;
851     private static Bitmap mCachedBit = null;
852     private static final BitmapFactory.Options sBitmapOptionsCache = new BitmapFactory.Options();
853     private static final BitmapFactory.Options sBitmapOptions = new BitmapFactory.Options();
854     private static final Uri sArtworkUri = Uri.parse("content://media/external/audio/albumart");
855     private static final HashMap<Long, Drawable> sArtCache = new HashMap<Long, Drawable>();
856     private static int sArtCacheId = -1;
857 
858     static {
859         // for the cache,
860         // 565 is faster to decode and display
861         // and we don't want to dither here because the image will be scaled down later
862         sBitmapOptionsCache.inPreferredConfig = Bitmap.Config.RGB_565;
863         sBitmapOptionsCache.inDither = false;
864 
865         sBitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
866         sBitmapOptions.inDither = false;
867     }
868 
initAlbumArtCache()869     public static void initAlbumArtCache() {
870         try {
871             int id = sService.getMediaMountedCount();
872             if (id != sArtCacheId) {
873                 clearAlbumArtCache();
874                 sArtCacheId = id;
875             }
876         } catch (RemoteException e) {
877             e.printStackTrace();
878         }
879     }
880 
clearAlbumArtCache()881     public static void clearAlbumArtCache() {
882         synchronized (sArtCache) {
883             sArtCache.clear();
884         }
885     }
886 
getCachedArtwork( Context context, long artIndex, BitmapDrawable defaultArtwork)887     public static Drawable getCachedArtwork(
888             Context context, long artIndex, BitmapDrawable defaultArtwork) {
889         Drawable d = null;
890         synchronized (sArtCache) {
891             d = sArtCache.get(artIndex);
892         }
893         if (d == null) {
894             d = defaultArtwork;
895             final Bitmap icon = defaultArtwork.getBitmap();
896             int w = icon.getWidth();
897             int h = icon.getHeight();
898             Bitmap b = MusicUtils.getArtworkQuick(context, artIndex, w, h);
899             if (b != null) {
900                 d = new FastBitmapDrawable(b);
901                 synchronized (sArtCache) {
902                     // the cache may have changed since we checked
903                     Drawable value = sArtCache.get(artIndex);
904                     if (value == null) {
905                         sArtCache.put(artIndex, d);
906                     } else {
907                         d = value;
908                     }
909                 }
910             }
911         }
912         return d;
913     }
914 
915     // Get album art for specified album. This method will not try to
916     // fall back to getting artwork directly from the file, nor will
917     // it attempt to repair the database.
getArtworkQuick(Context context, long album_id, int w, int h)918     private static Bitmap getArtworkQuick(Context context, long album_id, int w, int h) {
919         // NOTE: There is in fact a 1 pixel border on the right side in the ImageView
920         // used to display this drawable. Take it into account now, so we don't have to
921         // scale later.
922         w -= 1;
923         ContentResolver res = context.getContentResolver();
924         Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
925         if (uri != null) {
926             ParcelFileDescriptor fd = null;
927             try {
928                 fd = res.openFileDescriptor(uri, "r");
929                 int sampleSize = 1;
930 
931                 // Compute the closest power-of-two scale factor
932                 // and pass that to sBitmapOptionsCache.inSampleSize, which will
933                 // result in faster decoding and better quality
934                 sBitmapOptionsCache.inJustDecodeBounds = true;
935                 BitmapFactory.decodeFileDescriptor(
936                         fd.getFileDescriptor(), null, sBitmapOptionsCache);
937                 int nextWidth = sBitmapOptionsCache.outWidth >> 1;
938                 int nextHeight = sBitmapOptionsCache.outHeight >> 1;
939                 while (nextWidth > w && nextHeight > h) {
940                     sampleSize <<= 1;
941                     nextWidth >>= 1;
942                     nextHeight >>= 1;
943                 }
944 
945                 sBitmapOptionsCache.inSampleSize = sampleSize;
946                 sBitmapOptionsCache.inJustDecodeBounds = false;
947                 Bitmap b = BitmapFactory.decodeFileDescriptor(
948                         fd.getFileDescriptor(), null, sBitmapOptionsCache);
949 
950                 if (b != null) {
951                     // finally rescale to exactly the size we need
952                     if (sBitmapOptionsCache.outWidth != w || sBitmapOptionsCache.outHeight != h) {
953                         Bitmap tmp = Bitmap.createScaledBitmap(b, w, h, true);
954                         // Bitmap.createScaledBitmap() can return the same bitmap
955                         if (tmp != b) b.recycle();
956                         b = tmp;
957                     }
958                 }
959 
960                 return b;
961             } catch (FileNotFoundException e) {
962             } finally {
963                 try {
964                     if (fd != null) fd.close();
965                 } catch (IOException e) {
966                 }
967             }
968         }
969         return null;
970     }
971 
972     /** Get album art for specified album. You should not pass in the album id
973      * for the "unknown" album here (use -1 instead)
974      * This method always returns the default album art icon when no album art is found.
975      */
getArtwork(Context context, long song_id, long album_id)976     public static Bitmap getArtwork(Context context, long song_id, long album_id) {
977         return getArtwork(context, song_id, album_id, true);
978     }
979 
980     /** Get album art for specified album. You should not pass in the album id
981      * for the "unknown" album here (use -1 instead)
982      */
getArtwork( Context context, long song_id, long album_id, boolean allowdefault)983     public static Bitmap getArtwork(
984             Context context, long song_id, long album_id, boolean allowdefault) {
985         if (album_id < 0) {
986             // This is something that is not in the database, so get the album art directly
987             // from the file.
988             if (song_id >= 0) {
989                 Bitmap bm = getArtworkFromFile(context, song_id, -1);
990                 if (bm != null) {
991                     return bm;
992                 }
993             }
994             if (allowdefault) {
995                 return getDefaultArtwork(context);
996             }
997             return null;
998         }
999 
1000         ContentResolver res = context.getContentResolver();
1001         Uri uri = ContentUris.withAppendedId(sArtworkUri, album_id);
1002         if (uri != null) {
1003             InputStream in = null;
1004             try {
1005                 in = res.openInputStream(uri);
1006                 return BitmapFactory.decodeStream(in, null, sBitmapOptions);
1007             } catch (FileNotFoundException ex) {
1008                 // The album art thumbnail does not actually exist. Maybe the user deleted it, or
1009                 // maybe it never existed to begin with.
1010                 Bitmap bm = getArtworkFromFile(context, song_id, album_id);
1011                 if (bm != null) {
1012                     if (bm.getConfig() == null) {
1013                         bm = bm.copy(Bitmap.Config.RGB_565, false);
1014                         if (bm == null && allowdefault) {
1015                             return getDefaultArtwork(context);
1016                         }
1017                     }
1018                 } else if (allowdefault) {
1019                     bm = getDefaultArtwork(context);
1020                 }
1021                 return bm;
1022             } finally {
1023                 try {
1024                     if (in != null) {
1025                         in.close();
1026                     }
1027                 } catch (IOException ex) {
1028                 }
1029             }
1030         }
1031 
1032         return null;
1033     }
1034 
1035     // get album art for specified file
1036     private static final String sExternalMediaUri =
1037             MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString();
getArtworkFromFile(Context context, long songid, long albumid)1038     private static Bitmap getArtworkFromFile(Context context, long songid, long albumid) {
1039         Bitmap bm = null;
1040         byte[] art = null;
1041         String path = null;
1042 
1043         if (albumid < 0 && songid < 0) {
1044             throw new IllegalArgumentException("Must specify an album or a song id");
1045         }
1046 
1047         try {
1048             if (albumid < 0) {
1049                 Uri uri = Uri.parse("content://media/external/audio/media/" + songid + "/albumart");
1050                 ParcelFileDescriptor pfd =
1051                         context.getContentResolver().openFileDescriptor(uri, "r");
1052                 if (pfd != null) {
1053                     FileDescriptor fd = pfd.getFileDescriptor();
1054                     bm = BitmapFactory.decodeFileDescriptor(fd);
1055                 }
1056             } else {
1057                 Uri uri = ContentUris.withAppendedId(sArtworkUri, albumid);
1058                 ParcelFileDescriptor pfd =
1059                         context.getContentResolver().openFileDescriptor(uri, "r");
1060                 if (pfd != null) {
1061                     FileDescriptor fd = pfd.getFileDescriptor();
1062                     bm = BitmapFactory.decodeFileDescriptor(fd);
1063                 }
1064             }
1065         } catch (IllegalStateException ex) {
1066         } catch (FileNotFoundException ex) {
1067         }
1068         if (bm != null) {
1069             mCachedBit = bm;
1070         }
1071         return bm;
1072     }
1073 
getDefaultArtwork(Context context)1074     private static Bitmap getDefaultArtwork(Context context) {
1075         BitmapFactory.Options opts = new BitmapFactory.Options();
1076         opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
1077         return BitmapFactory.decodeStream(
1078                 context.getResources().openRawResource(R.drawable.albumart_mp_unknown), null, opts);
1079     }
1080 
getIntPref(Context context, String name, int def)1081     static int getIntPref(Context context, String name, int def) {
1082         SharedPreferences prefs =
1083                 context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
1084         return prefs.getInt(name, def);
1085     }
1086 
setIntPref(Context context, String name, int value)1087     static void setIntPref(Context context, String name, int value) {
1088         SharedPreferences prefs =
1089                 context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE);
1090         Editor ed = prefs.edit();
1091         ed.putInt(name, value);
1092         SharedPreferencesCompat.apply(ed);
1093     }
1094 
setRingtone(Context context, long id)1095     static void setRingtone(Context context, long id) {
1096         ContentResolver resolver = context.getContentResolver();
1097         // Set the flag in the database to mark this as a ringtone
1098         Uri ringUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
1099         try {
1100             ContentValues values = new ContentValues(2);
1101             values.put(MediaStore.Audio.Media.IS_RINGTONE, "1");
1102             values.put(MediaStore.Audio.Media.IS_ALARM, "1");
1103             resolver.update(ringUri, values, null, null);
1104         } catch (UnsupportedOperationException ex) {
1105             // most likely the card just got unmounted
1106             Log.e(TAG, "couldn't set ringtone flag for id " + id);
1107             return;
1108         }
1109 
1110         String[] cols = new String[] {MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA,
1111                 MediaStore.Audio.Media.TITLE};
1112 
1113         String where = MediaStore.Audio.Media._ID + "=" + id;
1114         Cursor cursor = query(
1115                 context, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cols, where, null, null);
1116         try {
1117             if (cursor != null && cursor.getCount() == 1) {
1118                 // Set the system setting to make this the current ringtone
1119                 cursor.moveToFirst();
1120                 Settings.System.putString(resolver, Settings.System.RINGTONE, ringUri.toString());
1121                 String message = context.getString(R.string.ringtone_set, cursor.getString(2));
1122                 Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
1123             }
1124         } finally {
1125             if (cursor != null) {
1126                 cursor.close();
1127             }
1128         }
1129     }
1130 
1131     static int sActiveTabIndex = -1;
1132 
updateButtonBar(Activity a, int highlight)1133     static boolean updateButtonBar(Activity a, int highlight) {
1134         final TabWidget ll = (TabWidget) a.findViewById(R.id.buttonbar);
1135         boolean withtabs = false;
1136         Intent intent = a.getIntent();
1137         if (intent != null) {
1138             withtabs = intent.getBooleanExtra("withtabs", false);
1139         }
1140 
1141         if (highlight == 0 || !withtabs) {
1142             ll.setVisibility(View.GONE);
1143             return withtabs;
1144         } else if (withtabs) {
1145             ll.setVisibility(View.VISIBLE);
1146         }
1147         for (int i = ll.getChildCount() - 1; i >= 0; i--) {
1148             View v = ll.getChildAt(i);
1149             boolean isActive = (v.getId() == highlight);
1150             if (isActive) {
1151                 ll.setCurrentTab(i);
1152                 sActiveTabIndex = i;
1153             }
1154             v.setTag(i);
1155             v.setOnFocusChangeListener(new View.OnFocusChangeListener() {
1156 
1157                 public void onFocusChange(View v, boolean hasFocus) {
1158                     if (hasFocus) {
1159                         for (int i = 0; i < ll.getTabCount(); i++) {
1160                             if (ll.getChildTabViewAt(i) == v) {
1161                                 ll.setCurrentTab(i);
1162                                 processTabClick((Activity) ll.getContext(), v,
1163                                         ll.getChildAt(sActiveTabIndex).getId());
1164                                 break;
1165                             }
1166                         }
1167                     }
1168                 }
1169             });
1170 
1171             v.setOnClickListener(new View.OnClickListener() {
1172 
1173                 public void onClick(View v) {
1174                     processTabClick(
1175                             (Activity) ll.getContext(), v, ll.getChildAt(sActiveTabIndex).getId());
1176                 }
1177             });
1178         }
1179         return withtabs;
1180     }
1181 
processTabClick(Activity a, View v, int current)1182     static void processTabClick(Activity a, View v, int current) {
1183         int id = v.getId();
1184         if (id == current) {
1185             return;
1186         }
1187 
1188         final TabWidget ll = (TabWidget) a.findViewById(R.id.buttonbar);
1189 
1190         activateTab(a, id);
1191         if (id != R.id.nowplayingtab) {
1192             ll.setCurrentTab((Integer) v.getTag());
1193             setIntPref(a, "activetab", id);
1194         }
1195     }
1196 
activateTab(Activity a, int id)1197     static void activateTab(Activity a, int id) {
1198         Intent intent = new Intent(Intent.ACTION_PICK);
1199         switch (id) {
1200             case R.id.artisttab:
1201                 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/artistalbum");
1202                 break;
1203             case R.id.albumtab:
1204                 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/album");
1205                 break;
1206             case R.id.songtab:
1207                 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
1208                 break;
1209             case R.id.playlisttab:
1210                 intent.setDataAndType(Uri.EMPTY, MediaStore.Audio.Playlists.CONTENT_TYPE);
1211                 break;
1212             case R.id.nowplayingtab:
1213                 intent = new Intent(a, MediaPlaybackActivity.class);
1214                 a.startActivity(intent);
1215             // fall through and return
1216             default:
1217                 return;
1218         }
1219         intent.putExtra("withtabs", true);
1220         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
1221         a.startActivity(intent);
1222         a.finish();
1223         a.overridePendingTransition(0, 0);
1224     }
1225 
updateNowPlaying(Activity a)1226     static void updateNowPlaying(Activity a) {
1227         View nowPlayingView = a.findViewById(R.id.nowplaying);
1228         if (nowPlayingView == null) {
1229             return;
1230         }
1231         try {
1232             boolean withtabs = false;
1233             Intent intent = a.getIntent();
1234             if (intent != null) {
1235                 withtabs = intent.getBooleanExtra("withtabs", false);
1236             }
1237             if (true && MusicUtils.sService != null && MusicUtils.sService.getAudioId() != -1) {
1238                 TextView title = (TextView) nowPlayingView.findViewById(R.id.title);
1239                 TextView artist = (TextView) nowPlayingView.findViewById(R.id.artist);
1240                 title.setText(MusicUtils.sService.getTrackName());
1241                 String artistName = MusicUtils.sService.getArtistName();
1242                 if (MediaStore.UNKNOWN_STRING.equals(artistName)) {
1243                     artistName = a.getString(R.string.unknown_artist_name);
1244                 }
1245                 artist.setText(artistName);
1246                 // mNowPlayingView.setOnFocusChangeListener(mFocuser);
1247                 // mNowPlayingView.setOnClickListener(this);
1248                 nowPlayingView.setVisibility(View.VISIBLE);
1249                 nowPlayingView.setOnClickListener(new View.OnClickListener() {
1250 
1251                     public void onClick(View v) {
1252                         Context c = v.getContext();
1253                         c.startActivity(new Intent(c, MediaPlaybackActivity.class));
1254                     }
1255                 });
1256                 return;
1257             }
1258         } catch (RemoteException ex) {
1259         }
1260         nowPlayingView.setVisibility(View.GONE);
1261     }
1262 
setBackground(View v, Bitmap bm)1263     static void setBackground(View v, Bitmap bm) {
1264         if (bm == null) {
1265             v.setBackgroundResource(0);
1266             return;
1267         }
1268 
1269         int vwidth = v.getWidth();
1270         int vheight = v.getHeight();
1271         int bwidth = bm.getWidth();
1272         int bheight = bm.getHeight();
1273         float scalex = (float) vwidth / bwidth;
1274         float scaley = (float) vheight / bheight;
1275         float scale = Math.max(scalex, scaley) * 1.3f;
1276 
1277         Bitmap.Config config = Bitmap.Config.ARGB_8888;
1278         Bitmap bg = Bitmap.createBitmap(vwidth, vheight, config);
1279         Canvas c = new Canvas(bg);
1280         Paint paint = new Paint();
1281         paint.setAntiAlias(true);
1282         paint.setFilterBitmap(true);
1283         ColorMatrix greymatrix = new ColorMatrix();
1284         greymatrix.setSaturation(0);
1285         ColorMatrix darkmatrix = new ColorMatrix();
1286         darkmatrix.setScale(.3f, .3f, .3f, 1.0f);
1287         greymatrix.postConcat(darkmatrix);
1288         ColorFilter filter = new ColorMatrixColorFilter(greymatrix);
1289         paint.setColorFilter(filter);
1290         Matrix matrix = new Matrix();
1291         matrix.setTranslate(-bwidth / 2, -bheight / 2); // move bitmap center to origin
1292         matrix.postRotate(10);
1293         matrix.postScale(scale, scale);
1294         matrix.postTranslate(vwidth / 2, vheight / 2); // Move bitmap center to view center
1295         c.drawBitmap(bm, matrix, paint);
1296         v.setBackgroundDrawable(new BitmapDrawable(bg));
1297     }
1298 
getCardId(Context context)1299     static int getCardId(Context context) {
1300         ContentResolver res = context.getContentResolver();
1301         Cursor c = res.query(Uri.parse("content://media/external/fs_id"), null, null, null, null);
1302         int id = -1;
1303         if (c != null) {
1304             c.moveToFirst();
1305             id = c.getInt(0);
1306             c.close();
1307         }
1308         return id;
1309     }
1310 
1311     static class LogEntry {
1312         Object item;
1313         long time;
1314 
LogEntry(Object o)1315         LogEntry(Object o) {
1316             item = o;
1317             time = System.currentTimeMillis();
1318         }
1319 
dump(PrintWriter out)1320         void dump(PrintWriter out) {
1321             sTime.set(time);
1322             out.print(sTime.toString() + " : ");
1323             if (item instanceof Exception) {
1324                 ((Exception) item).printStackTrace(out);
1325             } else {
1326                 out.println(item);
1327             }
1328         }
1329     }
1330 
1331     private static LogEntry[] sMusicLog = new LogEntry[100];
1332     private static int sLogPtr = 0;
1333     private static Time sTime = new Time();
1334 
debugLog(Object o)1335     static void debugLog(Object o) {
1336         sMusicLog[sLogPtr] = new LogEntry(o);
1337         sLogPtr++;
1338         if (sLogPtr >= sMusicLog.length) {
1339             sLogPtr = 0;
1340         }
1341     }
1342 
debugDump(PrintWriter out)1343     static void debugDump(PrintWriter out) {
1344         for (int i = 0; i < sMusicLog.length; i++) {
1345             int idx = (sLogPtr + i);
1346             if (idx >= sMusicLog.length) {
1347                 idx -= sMusicLog.length;
1348             }
1349             LogEntry entry = sMusicLog[idx];
1350             if (entry != null) {
1351                 entry.dump(out);
1352             }
1353         }
1354     }
1355 }
1356