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