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