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