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