OSDN Git Service

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