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