Home | History | Annotate | Download | only in music
      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 
     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      */
    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;
    158         ServiceToken(ContextWrapper context) {
    159             mWrappedContext = context;
    160         }
    161     }
    162 
    163     public static ServiceToken bindToService(Activity context) {
    164         return bindToService(context, null);
    165     }
    166 
    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 
    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;
    204         ServiceBinder(ServiceConnection callback) {
    205             mCallback = callback;
    206         }
    207 
    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 
    216         public void onServiceDisconnected(ComponentName className) {
    217             if (mCallback != null) {
    218                 mCallback.onServiceDisconnected(className);
    219             }
    220             sService = null;
    221         }
    222     }
    223 
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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      */
    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 
    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 
    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 
    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      */
    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 
    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 
    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     }
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    770     public static void shuffleAll(Context context, Cursor cursor) {
    771         playAll(context, cursor, 0, true);
    772     }
    773 
    774     public static void playAll(Context context, Cursor cursor) {
    775         playAll(context, cursor, 0, false);
    776     }
    777 
    778     public static void playAll(Context context, Cursor cursor, int position) {
    779         playAll(context, cursor, position, false);
    780     }
    781 
    782     public static void playAll(Context context, long [] list, int position) {
    783         playAll(context, list, position, false);
    784     }
    785 
    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 
    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 
    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;
    841         public FastBitmapDrawable(Bitmap b) {
    842             mBitmap = b;
    843         }
    844         @Override
    845         public void draw(Canvas canvas) {
    846             canvas.drawBitmap(mBitmap, 0, 0, null);
    847         }
    848         @Override
    849         public int getOpacity() {
    850             return PixelFormat.OPAQUE;
    851         }
    852         @Override
    853         public void setAlpha(int alpha) {
    854         }
    855         @Override
    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 
    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 
    891     public static void clearAlbumArtCache() {
    892         synchronized(sArtCache) {
    893             sArtCache.clear();
    894         }
    895     }
    896 
    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.
    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      */
    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      */
    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();
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   1323         LogEntry(Object o) {
   1324             item = o;
   1325             time = System.currentTimeMillis();
   1326         }
   1327 
   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 
   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 
   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