Home | History | Annotate | Download | only in music
      1 /*
      2  * Copyright (C) 2007 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 com.android.music.MusicUtils.ServiceToken;
     20 import com.android.music.QueryBrowserActivity.QueryListAdapter.QueryHandler;
     21 
     22 import android.app.ExpandableListActivity;
     23 import android.app.SearchManager;
     24 import android.content.AsyncQueryHandler;
     25 import android.content.BroadcastReceiver;
     26 import android.content.ComponentName;
     27 import android.content.ContentResolver;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.content.IntentFilter;
     31 import android.content.ServiceConnection;
     32 import android.content.res.Resources;
     33 import android.database.Cursor;
     34 import android.database.CursorWrapper;
     35 import android.graphics.drawable.BitmapDrawable;
     36 import android.graphics.drawable.Drawable;
     37 import android.media.AudioManager;
     38 import android.net.Uri;
     39 import android.os.Bundle;
     40 import android.os.Handler;
     41 import android.os.IBinder;
     42 import android.os.Message;
     43 import android.os.Parcel;
     44 import android.os.Parcelable;
     45 import android.provider.MediaStore;
     46 import android.text.TextUtils;
     47 import android.util.Log;
     48 import android.util.SparseArray;
     49 import android.view.ContextMenu;
     50 import android.view.Menu;
     51 import android.view.MenuItem;
     52 import android.view.SubMenu;
     53 import android.view.View;
     54 import android.view.ViewGroup;
     55 import android.view.Window;
     56 import android.view.ContextMenu.ContextMenuInfo;
     57 import android.widget.ExpandableListView;
     58 import android.widget.ImageView;
     59 import android.widget.SectionIndexer;
     60 import android.widget.SimpleCursorTreeAdapter;
     61 import android.widget.TextView;
     62 import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
     63 
     64 import java.text.Collator;
     65 
     66 
     67 public class ArtistAlbumBrowserActivity extends ExpandableListActivity
     68         implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection
     69 {
     70     private String mCurrentArtistId;
     71     private String mCurrentArtistName;
     72     private String mCurrentAlbumId;
     73     private String mCurrentAlbumName;
     74     private String mCurrentArtistNameForAlbum;
     75     boolean mIsUnknownArtist;
     76     boolean mIsUnknownAlbum;
     77     private ArtistAlbumListAdapter mAdapter;
     78     private boolean mAdapterSent;
     79     private final static int SEARCH = CHILD_MENU_BASE;
     80     private static int mLastListPosCourse = -1;
     81     private static int mLastListPosFine = -1;
     82     private ServiceToken mToken;
     83 
     84     /** Called when the activity is first created. */
     85     @Override
     86     public void onCreate(Bundle icicle) {
     87         super.onCreate(icicle);
     88         requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
     89         requestWindowFeature(Window.FEATURE_NO_TITLE);
     90         setVolumeControlStream(AudioManager.STREAM_MUSIC);
     91         if (icicle != null) {
     92             mCurrentAlbumId = icicle.getString("selectedalbum");
     93             mCurrentAlbumName = icicle.getString("selectedalbumname");
     94             mCurrentArtistId = icicle.getString("selectedartist");
     95             mCurrentArtistName = icicle.getString("selectedartistname");
     96         }
     97         mToken = MusicUtils.bindToService(this, this);
     98 
     99         IntentFilter f = new IntentFilter();
    100         f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
    101         f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    102         f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
    103         f.addDataScheme("file");
    104         registerReceiver(mScanListener, f);
    105 
    106         setContentView(R.layout.media_picker_activity_expanding);
    107         MusicUtils.updateButtonBar(this, R.id.artisttab);
    108         ExpandableListView lv = getExpandableListView();
    109         lv.setOnCreateContextMenuListener(this);
    110         lv.setTextFilterEnabled(true);
    111 
    112         mAdapter = (ArtistAlbumListAdapter) getLastNonConfigurationInstance();
    113         if (mAdapter == null) {
    114             //Log.i("@@@", "starting query");
    115             mAdapter = new ArtistAlbumListAdapter(
    116                     getApplication(),
    117                     this,
    118                     null, // cursor
    119                     R.layout.track_list_item_group,
    120                     new String[] {},
    121                     new int[] {},
    122                     R.layout.track_list_item_child,
    123                     new String[] {},
    124                     new int[] {});
    125             setListAdapter(mAdapter);
    126             setTitle(R.string.working_artists);
    127             getArtistCursor(mAdapter.getQueryHandler(), null);
    128         } else {
    129             mAdapter.setActivity(this);
    130             setListAdapter(mAdapter);
    131             mArtistCursor = mAdapter.getCursor();
    132             if (mArtistCursor != null) {
    133                 init(mArtistCursor);
    134             } else {
    135                 getArtistCursor(mAdapter.getQueryHandler(), null);
    136             }
    137         }
    138     }
    139 
    140     @Override
    141     public Object onRetainNonConfigurationInstance() {
    142         mAdapterSent = true;
    143         return mAdapter;
    144     }
    145 
    146     @Override
    147     public void onSaveInstanceState(Bundle outcicle) {
    148         // need to store the selected item so we don't lose it in case
    149         // of an orientation switch. Otherwise we could lose it while
    150         // in the middle of specifying a playlist to add the item to.
    151         outcicle.putString("selectedalbum", mCurrentAlbumId);
    152         outcicle.putString("selectedalbumname", mCurrentAlbumName);
    153         outcicle.putString("selectedartist", mCurrentArtistId);
    154         outcicle.putString("selectedartistname", mCurrentArtistName);
    155         super.onSaveInstanceState(outcicle);
    156     }
    157 
    158     @Override
    159     public void onDestroy() {
    160         ExpandableListView lv = getExpandableListView();
    161         if (lv != null) {
    162             mLastListPosCourse = lv.getFirstVisiblePosition();
    163             View cv = lv.getChildAt(0);
    164             if (cv != null) {
    165                 mLastListPosFine = cv.getTop();
    166             }
    167         }
    168 
    169         MusicUtils.unbindFromService(mToken);
    170         // If we have an adapter and didn't send it off to another activity yet, we should
    171         // close its cursor, which we do by assigning a null cursor to it. Doing this
    172         // instead of closing the cursor directly keeps the framework from accessing
    173         // the closed cursor later.
    174         if (!mAdapterSent && mAdapter != null) {
    175             mAdapter.changeCursor(null);
    176         }
    177         // Because we pass the adapter to the next activity, we need to make
    178         // sure it doesn't keep a reference to this activity. We can do this
    179         // by clearing its DatasetObservers, which setListAdapter(null) does.
    180         setListAdapter(null);
    181         mAdapter = null;
    182         unregisterReceiver(mScanListener);
    183         setListAdapter(null);
    184         super.onDestroy();
    185     }
    186 
    187     @Override
    188     public void onResume() {
    189         super.onResume();
    190         IntentFilter f = new IntentFilter();
    191         f.addAction(MediaPlaybackService.META_CHANGED);
    192         f.addAction(MediaPlaybackService.QUEUE_CHANGED);
    193         registerReceiver(mTrackListListener, f);
    194         mTrackListListener.onReceive(null, null);
    195 
    196         MusicUtils.setSpinnerState(this);
    197     }
    198 
    199     private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
    200         @Override
    201         public void onReceive(Context context, Intent intent) {
    202             getExpandableListView().invalidateViews();
    203             MusicUtils.updateNowPlaying(ArtistAlbumBrowserActivity.this);
    204         }
    205     };
    206     private BroadcastReceiver mScanListener = new BroadcastReceiver() {
    207         @Override
    208         public void onReceive(Context context, Intent intent) {
    209             MusicUtils.setSpinnerState(ArtistAlbumBrowserActivity.this);
    210             mReScanHandler.sendEmptyMessage(0);
    211             if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
    212                 MusicUtils.clearAlbumArtCache();
    213             }
    214         }
    215     };
    216 
    217     private Handler mReScanHandler = new Handler() {
    218         @Override
    219         public void handleMessage(Message msg) {
    220             if (mAdapter != null) {
    221                 getArtistCursor(mAdapter.getQueryHandler(), null);
    222             }
    223         }
    224     };
    225 
    226     @Override
    227     public void onPause() {
    228         unregisterReceiver(mTrackListListener);
    229         mReScanHandler.removeCallbacksAndMessages(null);
    230         super.onPause();
    231     }
    232 
    233     public void init(Cursor c) {
    234 
    235         if (mAdapter == null) {
    236             return;
    237         }
    238         mAdapter.changeCursor(c); // also sets mArtistCursor
    239 
    240         if (mArtistCursor == null) {
    241             MusicUtils.displayDatabaseError(this);
    242             closeContextMenu();
    243             mReScanHandler.sendEmptyMessageDelayed(0, 1000);
    244             return;
    245         }
    246 
    247         // restore previous position
    248         if (mLastListPosCourse >= 0) {
    249             ExpandableListView elv = getExpandableListView();
    250             elv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
    251             mLastListPosCourse = -1;
    252         }
    253 
    254         MusicUtils.hideDatabaseError(this);
    255         MusicUtils.updateButtonBar(this, R.id.artisttab);
    256         setTitle();
    257     }
    258 
    259     private void setTitle() {
    260         setTitle(R.string.artists_title);
    261     }
    262 
    263     @Override
    264     public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
    265 
    266         mCurrentAlbumId = Long.valueOf(id).toString();
    267 
    268         Intent intent = new Intent(Intent.ACTION_PICK);
    269         intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
    270         intent.putExtra("album", mCurrentAlbumId);
    271         Cursor c = (Cursor) getExpandableListAdapter().getChild(groupPosition, childPosition);
    272         String album = c.getString(c.getColumnIndex(MediaStore.Audio.Albums.ALBUM));
    273         if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
    274             // unknown album, so we should include the artist ID to limit the songs to songs only by that artist
    275             mArtistCursor.moveToPosition(groupPosition);
    276             mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndex(MediaStore.Audio.Artists._ID));
    277             intent.putExtra("artist", mCurrentArtistId);
    278         }
    279         startActivity(intent);
    280         return true;
    281     }
    282 
    283     @Override
    284     public boolean onCreateOptionsMenu(Menu menu) {
    285         super.onCreateOptionsMenu(menu);
    286         menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
    287         menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
    288         return true;
    289     }
    290 
    291     @Override
    292     public boolean onPrepareOptionsMenu(Menu menu) {
    293         MusicUtils.setPartyShuffleMenuIcon(menu);
    294         return super.onPrepareOptionsMenu(menu);
    295     }
    296 
    297     @Override
    298     public boolean onOptionsItemSelected(MenuItem item) {
    299         Intent intent;
    300         Cursor cursor;
    301         switch (item.getItemId()) {
    302             case PARTY_SHUFFLE:
    303                 MusicUtils.togglePartyShuffle();
    304                 break;
    305 
    306             case SHUFFLE_ALL:
    307                 cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
    308                         new String [] { MediaStore.Audio.Media._ID},
    309                         MediaStore.Audio.Media.IS_MUSIC + "=1", null,
    310                         MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
    311                 if (cursor != null) {
    312                     MusicUtils.shuffleAll(this, cursor);
    313                     cursor.close();
    314                 }
    315                 return true;
    316         }
    317         return super.onOptionsItemSelected(item);
    318     }
    319 
    320     @Override
    321     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
    322         menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
    323         SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
    324         MusicUtils.makePlaylistMenu(this, sub);
    325         menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
    326 
    327         ExpandableListContextMenuInfo mi = (ExpandableListContextMenuInfo) menuInfoIn;
    328 
    329         int itemtype = ExpandableListView.getPackedPositionType(mi.packedPosition);
    330         int gpos = ExpandableListView.getPackedPositionGroup(mi.packedPosition);
    331         int cpos = ExpandableListView.getPackedPositionChild(mi.packedPosition);
    332         if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
    333             if (gpos == -1) {
    334                 // this shouldn't happen
    335                 Log.d("Artist/Album", "no group");
    336                 return;
    337             }
    338             gpos = gpos - getExpandableListView().getHeaderViewsCount();
    339             mArtistCursor.moveToPosition(gpos);
    340             mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
    341             mCurrentArtistName = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
    342             mCurrentAlbumId = null;
    343             mIsUnknownArtist = mCurrentArtistName == null ||
    344                     mCurrentArtistName.equals(MediaStore.UNKNOWN_STRING);
    345             mIsUnknownAlbum = true;
    346             if (mIsUnknownArtist) {
    347                 menu.setHeaderTitle(getString(R.string.unknown_artist_name));
    348             } else {
    349                 menu.setHeaderTitle(mCurrentArtistName);
    350                 menu.add(0, SEARCH, 0, R.string.search_title);
    351             }
    352             return;
    353         } else if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
    354             if (cpos == -1) {
    355                 // this shouldn't happen
    356                 Log.d("Artist/Album", "no child");
    357                 return;
    358             }
    359             Cursor c = (Cursor) getExpandableListAdapter().getChild(gpos, cpos);
    360             c.moveToPosition(cpos);
    361             mCurrentArtistId = null;
    362             mCurrentAlbumId = Long.valueOf(mi.id).toString();
    363             mCurrentAlbumName = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
    364             gpos = gpos - getExpandableListView().getHeaderViewsCount();
    365             mArtistCursor.moveToPosition(gpos);
    366             mCurrentArtistNameForAlbum = mArtistCursor.getString(
    367                     mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
    368             mIsUnknownArtist = mCurrentArtistNameForAlbum == null ||
    369                     mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING);
    370             mIsUnknownAlbum = mCurrentAlbumName == null ||
    371                     mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING);
    372             if (mIsUnknownAlbum) {
    373                 menu.setHeaderTitle(getString(R.string.unknown_album_name));
    374             } else {
    375                 menu.setHeaderTitle(mCurrentAlbumName);
    376             }
    377             if (!mIsUnknownAlbum || !mIsUnknownArtist) {
    378                 menu.add(0, SEARCH, 0, R.string.search_title);
    379             }
    380         }
    381     }
    382 
    383     @Override
    384     public boolean onContextItemSelected(MenuItem item) {
    385         switch (item.getItemId()) {
    386             case PLAY_SELECTION: {
    387                 // play everything by the selected artist
    388                 long [] list =
    389                     mCurrentArtistId != null ?
    390                     MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
    391                     : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
    392 
    393                 MusicUtils.playAll(this, list, 0);
    394                 return true;
    395             }
    396 
    397             case QUEUE: {
    398                 long [] list =
    399                     mCurrentArtistId != null ?
    400                     MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
    401                     : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
    402                 MusicUtils.addToCurrentPlaylist(this, list);
    403                 return true;
    404             }
    405 
    406             case NEW_PLAYLIST: {
    407                 Intent intent = new Intent();
    408                 intent.setClass(this, CreatePlaylist.class);
    409                 startActivityForResult(intent, NEW_PLAYLIST);
    410                 return true;
    411             }
    412 
    413             case PLAYLIST_SELECTED: {
    414                 long [] list =
    415                     mCurrentArtistId != null ?
    416                     MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
    417                     : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
    418                 long playlist = item.getIntent().getLongExtra("playlist", 0);
    419                 MusicUtils.addToPlaylist(this, list, playlist);
    420                 return true;
    421             }
    422 
    423             case DELETE_ITEM: {
    424                 long [] list;
    425                 String desc;
    426                 if (mCurrentArtistId != null) {
    427                     list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId));
    428                     String f;
    429                     if (android.os.Environment.isExternalStorageRemovable()) {
    430                         f = getString(R.string.delete_artist_desc);
    431                     } else {
    432                         f = getString(R.string.delete_artist_desc_nosdcard);
    433                     }
    434                     desc = String.format(f, mCurrentArtistName);
    435                 } else {
    436                     list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
    437                     String f;
    438                     if (android.os.Environment.isExternalStorageRemovable()) {
    439                         f = getString(R.string.delete_album_desc);
    440                     } else {
    441                         f = getString(R.string.delete_album_desc_nosdcard);
    442                     }
    443 
    444                     desc = String.format(f, mCurrentAlbumName);
    445                 }
    446                 Bundle b = new Bundle();
    447                 b.putString("description", desc);
    448                 b.putLongArray("items", list);
    449                 Intent intent = new Intent();
    450                 intent.setClass(this, DeleteItems.class);
    451                 intent.putExtras(b);
    452                 startActivityForResult(intent, -1);
    453                 return true;
    454             }
    455 
    456             case SEARCH:
    457                 doSearch();
    458                 return true;
    459         }
    460         return super.onContextItemSelected(item);
    461     }
    462 
    463     void doSearch() {
    464         CharSequence title = null;
    465         String query = null;
    466 
    467         Intent i = new Intent();
    468         i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
    469         i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    470 
    471         if (mCurrentArtistId != null) {
    472             title = mCurrentArtistName;
    473             query = mCurrentArtistName;
    474             i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistName);
    475             i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE);
    476         } else {
    477             if (mIsUnknownAlbum) {
    478                 title = query = mCurrentArtistNameForAlbum;
    479             } else {
    480                 title = query = mCurrentAlbumName;
    481                 if (!mIsUnknownArtist) {
    482                     query = query + " " + mCurrentArtistNameForAlbum;
    483                 }
    484             }
    485             i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
    486             i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
    487             i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
    488         }
    489         title = getString(R.string.mediasearch, title);
    490         i.putExtra(SearchManager.QUERY, query);
    491 
    492         startActivity(Intent.createChooser(i, title));
    493     }
    494 
    495     @Override
    496     protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    497         switch (requestCode) {
    498             case SCAN_DONE:
    499                 if (resultCode == RESULT_CANCELED) {
    500                     finish();
    501                 } else {
    502                     getArtistCursor(mAdapter.getQueryHandler(), null);
    503                 }
    504                 break;
    505 
    506             case NEW_PLAYLIST:
    507                 if (resultCode == RESULT_OK) {
    508                     Uri uri = intent.getData();
    509                     if (uri != null) {
    510                         long [] list = null;
    511                         if (mCurrentArtistId != null) {
    512                             list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId));
    513                         } else if (mCurrentAlbumId != null) {
    514                             list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
    515                         }
    516                         MusicUtils.addToPlaylist(this, list, Long.parseLong(uri.getLastPathSegment()));
    517                     }
    518                 }
    519                 break;
    520         }
    521     }
    522 
    523     private Cursor getArtistCursor(AsyncQueryHandler async, String filter) {
    524 
    525         String[] cols = new String[] {
    526                 MediaStore.Audio.Artists._ID,
    527                 MediaStore.Audio.Artists.ARTIST,
    528                 MediaStore.Audio.Artists.NUMBER_OF_ALBUMS,
    529                 MediaStore.Audio.Artists.NUMBER_OF_TRACKS
    530         };
    531 
    532         Uri uri = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI;
    533         if (!TextUtils.isEmpty(filter)) {
    534             uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
    535         }
    536 
    537         Cursor ret = null;
    538         if (async != null) {
    539             async.startQuery(0, null, uri,
    540                     cols, null , null, MediaStore.Audio.Artists.ARTIST_KEY);
    541         } else {
    542             ret = MusicUtils.query(this, uri,
    543                     cols, null , null, MediaStore.Audio.Artists.ARTIST_KEY);
    544         }
    545         return ret;
    546     }
    547 
    548     static class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter implements SectionIndexer {
    549 
    550         private final Drawable mNowPlayingOverlay;
    551         private final BitmapDrawable mDefaultAlbumIcon;
    552         private int mGroupArtistIdIdx;
    553         private int mGroupArtistIdx;
    554         private int mGroupAlbumIdx;
    555         private int mGroupSongIdx;
    556         private final Context mContext;
    557         private final Resources mResources;
    558         private final String mAlbumSongSeparator;
    559         private final String mUnknownAlbum;
    560         private final String mUnknownArtist;
    561         private final StringBuilder mBuffer = new StringBuilder();
    562         private final Object[] mFormatArgs = new Object[1];
    563         private final Object[] mFormatArgs3 = new Object[3];
    564         private MusicAlphabetIndexer mIndexer;
    565         private ArtistAlbumBrowserActivity mActivity;
    566         private AsyncQueryHandler mQueryHandler;
    567         private String mConstraint = null;
    568         private boolean mConstraintIsValid = false;
    569 
    570         static class ViewHolder {
    571             TextView line1;
    572             TextView line2;
    573             ImageView play_indicator;
    574             ImageView icon;
    575         }
    576 
    577         class QueryHandler extends AsyncQueryHandler {
    578             QueryHandler(ContentResolver res) {
    579                 super(res);
    580             }
    581 
    582             @Override
    583             protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    584                 //Log.i("@@@", "query complete");
    585                 mActivity.init(cursor);
    586             }
    587         }
    588 
    589         ArtistAlbumListAdapter(Context context, ArtistAlbumBrowserActivity currentactivity,
    590                 Cursor cursor, int glayout, String[] gfrom, int[] gto,
    591                 int clayout, String[] cfrom, int[] cto) {
    592             super(context, cursor, glayout, gfrom, gto, clayout, cfrom, cto);
    593             mActivity = currentactivity;
    594             mQueryHandler = new QueryHandler(context.getContentResolver());
    595 
    596             Resources r = context.getResources();
    597             mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
    598             mDefaultAlbumIcon = (BitmapDrawable) r.getDrawable(R.drawable.albumart_mp_unknown_list);
    599             // no filter or dither, it's a lot faster and we can't tell the difference
    600             mDefaultAlbumIcon.setFilterBitmap(false);
    601             mDefaultAlbumIcon.setDither(false);
    602 
    603             mContext = context;
    604             getColumnIndices(cursor);
    605             mResources = context.getResources();
    606             mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
    607             mUnknownAlbum = context.getString(R.string.unknown_album_name);
    608             mUnknownArtist = context.getString(R.string.unknown_artist_name);
    609         }
    610 
    611         private void getColumnIndices(Cursor cursor) {
    612             if (cursor != null) {
    613                 mGroupArtistIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID);
    614                 mGroupArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST);
    615                 mGroupAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS);
    616                 mGroupSongIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS);
    617                 if (mIndexer != null) {
    618                     mIndexer.setCursor(cursor);
    619                 } else {
    620                     mIndexer = new MusicAlphabetIndexer(cursor, mGroupArtistIdx,
    621                             mResources.getString(R.string.fast_scroll_alphabet));
    622                 }
    623             }
    624         }
    625 
    626         public void setActivity(ArtistAlbumBrowserActivity newactivity) {
    627             mActivity = newactivity;
    628         }
    629 
    630         public AsyncQueryHandler getQueryHandler() {
    631             return mQueryHandler;
    632         }
    633 
    634         @Override
    635         public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) {
    636             View v = super.newGroupView(context, cursor, isExpanded, parent);
    637             ImageView iv = (ImageView) v.findViewById(R.id.icon);
    638             ViewGroup.LayoutParams p = iv.getLayoutParams();
    639             p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
    640             p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
    641             ViewHolder vh = new ViewHolder();
    642             vh.line1 = (TextView) v.findViewById(R.id.line1);
    643             vh.line2 = (TextView) v.findViewById(R.id.line2);
    644             vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
    645             vh.icon = (ImageView) v.findViewById(R.id.icon);
    646             vh.icon.setPadding(0, 0, 1, 0);
    647             v.setTag(vh);
    648             return v;
    649         }
    650 
    651         @Override
    652         public View newChildView(Context context, Cursor cursor, boolean isLastChild,
    653                 ViewGroup parent) {
    654             View v = super.newChildView(context, cursor, isLastChild, parent);
    655             ViewHolder vh = new ViewHolder();
    656             vh.line1 = (TextView) v.findViewById(R.id.line1);
    657             vh.line2 = (TextView) v.findViewById(R.id.line2);
    658             vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
    659             vh.icon = (ImageView) v.findViewById(R.id.icon);
    660             vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
    661             vh.icon.setPadding(0, 0, 1, 0);
    662             v.setTag(vh);
    663             return v;
    664         }
    665 
    666         @Override
    667         public void bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded) {
    668 
    669             ViewHolder vh = (ViewHolder) view.getTag();
    670 
    671             String artist = cursor.getString(mGroupArtistIdx);
    672             String displayartist = artist;
    673             boolean unknown = artist == null || artist.equals(MediaStore.UNKNOWN_STRING);
    674             if (unknown) {
    675                 displayartist = mUnknownArtist;
    676             }
    677             vh.line1.setText(displayartist);
    678 
    679             int numalbums = cursor.getInt(mGroupAlbumIdx);
    680             int numsongs = cursor.getInt(mGroupSongIdx);
    681 
    682             String songs_albums = MusicUtils.makeAlbumsLabel(context,
    683                     numalbums, numsongs, unknown);
    684 
    685             vh.line2.setText(songs_albums);
    686 
    687             long currentartistid = MusicUtils.getCurrentArtistId();
    688             long artistid = cursor.getLong(mGroupArtistIdIdx);
    689             if (currentartistid == artistid && !isexpanded) {
    690                 vh.play_indicator.setImageDrawable(mNowPlayingOverlay);
    691             } else {
    692                 vh.play_indicator.setImageDrawable(null);
    693             }
    694         }
    695 
    696         @Override
    697         public void bindChildView(View view, Context context, Cursor cursor, boolean islast) {
    698 
    699             ViewHolder vh = (ViewHolder) view.getTag();
    700 
    701             String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
    702             String displayname = name;
    703             boolean unknown = name == null || name.equals(MediaStore.UNKNOWN_STRING);
    704             if (unknown) {
    705                 displayname = mUnknownAlbum;
    706             }
    707             vh.line1.setText(displayname);
    708 
    709             int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS));
    710             int numartistsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST));
    711 
    712             final StringBuilder builder = mBuffer;
    713             builder.delete(0, builder.length());
    714             if (unknown) {
    715                 numsongs = numartistsongs;
    716             }
    717 
    718             if (numsongs == 1) {
    719                 builder.append(context.getString(R.string.onesong));
    720             } else {
    721                 if (numsongs == numartistsongs) {
    722                     final Object[] args = mFormatArgs;
    723                     args[0] = numsongs;
    724                     builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args));
    725                 } else {
    726                     final Object[] args = mFormatArgs3;
    727                     args[0] = numsongs;
    728                     args[1] = numartistsongs;
    729                     args[2] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
    730                     builder.append(mResources.getQuantityString(R.plurals.Nsongscomp, numsongs, args));
    731                 }
    732             }
    733             vh.line2.setText(builder.toString());
    734 
    735             ImageView iv = vh.icon;
    736             // We don't actually need the path to the thumbnail file,
    737             // we just use it to see if there is album art or not
    738             String art = cursor.getString(cursor.getColumnIndexOrThrow(
    739                     MediaStore.Audio.Albums.ALBUM_ART));
    740             if (unknown || art == null || art.length() == 0) {
    741                 iv.setBackgroundDrawable(mDefaultAlbumIcon);
    742                 iv.setImageDrawable(null);
    743             } else {
    744                 long artIndex = cursor.getLong(0);
    745                 Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
    746                 iv.setImageDrawable(d);
    747             }
    748 
    749             long currentalbumid = MusicUtils.getCurrentAlbumId();
    750             long aid = cursor.getLong(0);
    751             iv = vh.play_indicator;
    752             if (currentalbumid == aid) {
    753                 iv.setImageDrawable(mNowPlayingOverlay);
    754             } else {
    755                 iv.setImageDrawable(null);
    756             }
    757         }
    758 
    759 
    760         @Override
    761         protected Cursor getChildrenCursor(Cursor groupCursor) {
    762 
    763             long id = groupCursor.getLong(groupCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
    764 
    765             String[] cols = new String[] {
    766                     MediaStore.Audio.Albums._ID,
    767                     MediaStore.Audio.Albums.ALBUM,
    768                     MediaStore.Audio.Albums.NUMBER_OF_SONGS,
    769                     MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST,
    770                     MediaStore.Audio.Albums.ALBUM_ART
    771             };
    772             Cursor c = MusicUtils.query(mActivity,
    773                     MediaStore.Audio.Artists.Albums.getContentUri("external", id),
    774                     cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
    775 
    776             class MyCursorWrapper extends CursorWrapper {
    777                 String mArtistName;
    778                 int mMagicColumnIdx;
    779                 MyCursorWrapper(Cursor c, String artist) {
    780                     super(c);
    781                     mArtistName = artist;
    782                     if (mArtistName == null || mArtistName.equals(MediaStore.UNKNOWN_STRING)) {
    783                         mArtistName = mUnknownArtist;
    784                     }
    785                     mMagicColumnIdx = c.getColumnCount();
    786                 }
    787 
    788                 @Override
    789                 public String getString(int columnIndex) {
    790                     if (columnIndex != mMagicColumnIdx) {
    791                         return super.getString(columnIndex);
    792                     }
    793                     return mArtistName;
    794                 }
    795 
    796                 @Override
    797                 public int getColumnIndexOrThrow(String name) {
    798                     if (MediaStore.Audio.Albums.ARTIST.equals(name)) {
    799                         return mMagicColumnIdx;
    800                     }
    801                     return super.getColumnIndexOrThrow(name);
    802                 }
    803 
    804                 @Override
    805                 public String getColumnName(int idx) {
    806                     if (idx != mMagicColumnIdx) {
    807                         return super.getColumnName(idx);
    808                     }
    809                     return MediaStore.Audio.Albums.ARTIST;
    810                 }
    811 
    812                 @Override
    813                 public int getColumnCount() {
    814                     return super.getColumnCount() + 1;
    815                 }
    816             }
    817             return new MyCursorWrapper(c, groupCursor.getString(mGroupArtistIdx));
    818         }
    819 
    820         @Override
    821         public void changeCursor(Cursor cursor) {
    822             if (mActivity.isFinishing() && cursor != null) {
    823                 cursor.close();
    824                 cursor = null;
    825             }
    826             if (cursor != mActivity.mArtistCursor) {
    827                 mActivity.mArtistCursor = cursor;
    828                 getColumnIndices(cursor);
    829                 super.changeCursor(cursor);
    830             }
    831         }
    832 
    833         @Override
    834         public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
    835             String s = constraint.toString();
    836             if (mConstraintIsValid && (
    837                     (s == null && mConstraint == null) ||
    838                     (s != null && s.equals(mConstraint)))) {
    839                 return getCursor();
    840             }
    841             Cursor c = mActivity.getArtistCursor(null, s);
    842             mConstraint = s;
    843             mConstraintIsValid = true;
    844             return c;
    845         }
    846 
    847         public Object[] getSections() {
    848             return mIndexer.getSections();
    849         }
    850 
    851         public int getPositionForSection(int sectionIndex) {
    852             return mIndexer.getPositionForSection(sectionIndex);
    853         }
    854 
    855         public int getSectionForPosition(int position) {
    856             return 0;
    857         }
    858     }
    859 
    860     private Cursor mArtistCursor;
    861 
    862     public void onServiceConnected(ComponentName name, IBinder service) {
    863         MusicUtils.updateNowPlaying(this);
    864     }
    865 
    866     public void onServiceDisconnected(ComponentName name) {
    867         finish();
    868     }
    869 }
    870 
    871