ArtistAlbumBrowserActivity.java revision 173c561b6ee64fe8ce37c7402d9d715eebd7f959
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
17package com.android.music;
18
19import com.android.music.QueryBrowserActivity.QueryListAdapter.QueryHandler;
20
21import android.app.ExpandableListActivity;
22import android.app.SearchManager;
23import android.content.AsyncQueryHandler;
24import android.content.BroadcastReceiver;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.res.Resources;
30import android.database.Cursor;
31import android.database.CursorWrapper;
32import android.graphics.drawable.BitmapDrawable;
33import android.graphics.drawable.Drawable;
34import android.media.AudioManager;
35import android.media.MediaFile;
36import android.net.Uri;
37import android.os.Bundle;
38import android.os.Handler;
39import android.os.Message;
40import android.provider.MediaStore;
41import android.util.Log;
42import android.view.ContextMenu;
43import android.view.Menu;
44import android.view.MenuItem;
45import android.view.SubMenu;
46import android.view.View;
47import android.view.ViewGroup;
48import android.view.Window;
49import android.view.ContextMenu.ContextMenuInfo;
50import android.widget.CursorAdapter;
51import android.widget.CursorTreeAdapter;
52import android.widget.ExpandableListAdapter;
53import android.widget.ExpandableListView;
54import android.widget.ImageView;
55import android.widget.SectionIndexer;
56import android.widget.SimpleCursorTreeAdapter;
57import android.widget.TextView;
58import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
59
60import java.text.Collator;
61
62
63public 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.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
414        if (mCurrentArtistId != null) {
415            title = mCurrentArtistName;
416            query = mCurrentArtistName;
417            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistName);
418            i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE);
419        } else {
420            title = mCurrentAlbumName;
421            query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName;
422            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
423            i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
424            i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
425        }
426        title = getString(R.string.mediasearch, title);
427        i.putExtra(SearchManager.QUERY, query);
428
429        startActivity(Intent.createChooser(i, title));
430    }
431
432    @Override
433    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
434        switch (requestCode) {
435            case SCAN_DONE:
436                if (resultCode == RESULT_CANCELED) {
437                    finish();
438                } else {
439                    getArtistCursor(mAdapter.getQueryHandler(), null);
440                }
441                break;
442
443            case NEW_PLAYLIST:
444                if (resultCode == RESULT_OK) {
445                    Uri uri = intent.getData();
446                    if (uri != null) {
447                        int [] list = null;
448                        if (mCurrentArtistId != null) {
449                            list = MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId));
450                        } else if (mCurrentAlbumId != null) {
451                            list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
452                        }
453                        MusicUtils.addToPlaylist(this, list, Integer.parseInt(uri.getLastPathSegment()));
454                    }
455                }
456                break;
457        }
458    }
459
460    private Cursor getArtistCursor(AsyncQueryHandler async, String filter) {
461
462        StringBuilder where = new StringBuilder();
463        where.append(MediaStore.Audio.Artists.ARTIST + " != ''");
464
465        // Add in the filtering constraints
466        String [] keywords = null;
467        if (filter != null) {
468            String [] searchWords = filter.split(" ");
469            keywords = new String[searchWords.length];
470            Collator col = Collator.getInstance();
471            col.setStrength(Collator.PRIMARY);
472            for (int i = 0; i < searchWords.length; i++) {
473                keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
474            }
475            for (int i = 0; i < searchWords.length; i++) {
476                where.append(" AND ");
477                where.append(MediaStore.Audio.Media.ARTIST_KEY + " LIKE ?");
478            }
479        }
480
481        String whereclause = where.toString();
482        String[] cols = new String[] {
483                MediaStore.Audio.Artists._ID,
484                MediaStore.Audio.Artists.ARTIST,
485                MediaStore.Audio.Artists.NUMBER_OF_ALBUMS,
486                MediaStore.Audio.Artists.NUMBER_OF_TRACKS
487        };
488        Cursor ret = null;
489        if (async != null) {
490            async.startQuery(0, null, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
491                    cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY);
492        } else {
493            ret = MusicUtils.query(this, MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
494                    cols, whereclause , keywords, MediaStore.Audio.Artists.ARTIST_KEY);
495        }
496        return ret;
497    }
498
499    static class ArtistAlbumListAdapter extends SimpleCursorTreeAdapter implements SectionIndexer {
500
501        private final Drawable mNowPlayingOverlay;
502        private final BitmapDrawable mDefaultAlbumIcon;
503        private int mGroupArtistIdIdx;
504        private int mGroupArtistIdx;
505        private int mGroupAlbumIdx;
506        private int mGroupSongIdx;
507        private final Context mContext;
508        private final Resources mResources;
509        private final String mAlbumSongSeparator;
510        private final String mUnknownAlbum;
511        private final String mUnknownArtist;
512        private final StringBuilder mBuffer = new StringBuilder();
513        private final Object[] mFormatArgs = new Object[1];
514        private final Object[] mFormatArgs3 = new Object[3];
515        private MusicAlphabetIndexer mIndexer;
516        private ArtistAlbumBrowserActivity mActivity;
517        private AsyncQueryHandler mQueryHandler;
518        private String mConstraint = null;
519        private boolean mConstraintIsValid = false;
520
521        class ViewHolder {
522            TextView line1;
523            TextView line2;
524            ImageView play_indicator;
525            ImageView icon;
526        }
527
528        class QueryHandler extends AsyncQueryHandler {
529            QueryHandler(ContentResolver res) {
530                super(res);
531            }
532
533            @Override
534            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
535                //Log.i("@@@", "query complete");
536                mActivity.init(cursor);
537            }
538        }
539
540        ArtistAlbumListAdapter(Context context, ArtistAlbumBrowserActivity currentactivity,
541                Cursor cursor, int glayout, String[] gfrom, int[] gto,
542                int clayout, String[] cfrom, int[] cto) {
543            super(context, cursor, glayout, gfrom, gto, clayout, cfrom, cto);
544            mActivity = currentactivity;
545            mQueryHandler = new QueryHandler(context.getContentResolver());
546
547            Resources r = context.getResources();
548            mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
549            mDefaultAlbumIcon = (BitmapDrawable) r.getDrawable(R.drawable.albumart_mp_unknown_list);
550            // no filter or dither, it's a lot faster and we can't tell the difference
551            mDefaultAlbumIcon.setFilterBitmap(false);
552            mDefaultAlbumIcon.setDither(false);
553
554            mContext = context;
555            getColumnIndices(cursor);
556            mResources = context.getResources();
557            mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
558            mUnknownAlbum = context.getString(R.string.unknown_album_name);
559            mUnknownArtist = context.getString(R.string.unknown_artist_name);
560        }
561
562        private void getColumnIndices(Cursor cursor) {
563            if (cursor != null) {
564                mGroupArtistIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID);
565                mGroupArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST);
566                mGroupAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_ALBUMS);
567                mGroupSongIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.NUMBER_OF_TRACKS);
568                if (mIndexer != null) {
569                    mIndexer.setCursor(cursor);
570                } else {
571                    mIndexer = new MusicAlphabetIndexer(cursor, mGroupArtistIdx,
572                            mResources.getString(com.android.internal.R.string.fast_scroll_alphabet));
573                }
574            }
575        }
576
577        public void setActivity(ArtistAlbumBrowserActivity newactivity) {
578            mActivity = newactivity;
579        }
580
581        public AsyncQueryHandler getQueryHandler() {
582            return mQueryHandler;
583        }
584
585        @Override
586        public View newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent) {
587            View v = super.newGroupView(context, cursor, isExpanded, parent);
588            ImageView iv = (ImageView) v.findViewById(R.id.icon);
589            ViewGroup.LayoutParams p = iv.getLayoutParams();
590            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
591            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
592            ViewHolder vh = new ViewHolder();
593            vh.line1 = (TextView) v.findViewById(R.id.line1);
594            vh.line2 = (TextView) v.findViewById(R.id.line2);
595            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
596            vh.icon = (ImageView) v.findViewById(R.id.icon);
597            vh.icon.setPadding(0, 0, 1, 0);
598            v.setTag(vh);
599            return v;
600        }
601
602        @Override
603        public View newChildView(Context context, Cursor cursor, boolean isLastChild,
604                ViewGroup parent) {
605            View v = super.newChildView(context, cursor, isLastChild, parent);
606            ViewHolder vh = new ViewHolder();
607            vh.line1 = (TextView) v.findViewById(R.id.line1);
608            vh.line2 = (TextView) v.findViewById(R.id.line2);
609            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
610            vh.icon = (ImageView) v.findViewById(R.id.icon);
611            vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
612            vh.icon.setPadding(0, 0, 1, 0);
613            v.setTag(vh);
614            return v;
615        }
616
617        @Override
618        public void bindGroupView(View view, Context context, Cursor cursor, boolean isexpanded) {
619
620            ViewHolder vh = (ViewHolder) view.getTag();
621
622            String artist = cursor.getString(mGroupArtistIdx);
623            String displayartist = artist;
624            boolean unknown = MediaFile.UNKNOWN_STRING.equals(artist);
625            if (unknown) {
626                displayartist = mUnknownArtist;
627            }
628            vh.line1.setText(displayartist);
629
630            int numalbums = cursor.getInt(mGroupAlbumIdx);
631            int numsongs = cursor.getInt(mGroupSongIdx);
632
633            String songs_albums = MusicUtils.makeAlbumsLabel(context,
634                    numalbums, numsongs, unknown);
635
636            vh.line2.setText(songs_albums);
637
638            int currentartistid = MusicUtils.getCurrentArtistId();
639            int artistid = cursor.getInt(mGroupArtistIdIdx);
640            if (currentartistid == artistid && !isexpanded) {
641                vh.play_indicator.setImageDrawable(mNowPlayingOverlay);
642            } else {
643                vh.play_indicator.setImageDrawable(null);
644            }
645        }
646
647        @Override
648        public void bindChildView(View view, Context context, Cursor cursor, boolean islast) {
649
650            ViewHolder vh = (ViewHolder) view.getTag();
651
652            String name = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
653            String displayname = name;
654            boolean unknown = name.equals(MediaFile.UNKNOWN_STRING);
655            if (unknown) {
656                displayname = mUnknownAlbum;
657            }
658            vh.line1.setText(displayname);
659
660            int numsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS));
661            int numartistsongs = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST));
662            int first = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.FIRST_YEAR));
663            int last = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.LAST_YEAR));
664
665            if (first == 0) {
666                first = last;
667            }
668
669            final StringBuilder builder = mBuffer;
670            builder.delete(0, builder.length());
671            if (unknown) {
672                numsongs = numartistsongs;
673            }
674
675            if (numsongs == 1) {
676                builder.append(context.getString(R.string.onesong));
677            } else {
678                if (numsongs == numartistsongs) {
679                    final Object[] args = mFormatArgs;
680                    args[0] = numsongs;
681                    builder.append(mResources.getQuantityString(R.plurals.Nsongs, numsongs, args));
682                } else {
683                    final Object[] args = mFormatArgs3;
684                    args[0] = numsongs;
685                    args[1] = numartistsongs;
686                    args[2] = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
687                    builder.append(mResources.getQuantityString(R.plurals.Nsongscomp, numsongs, args));
688                }
689            }
690            vh.line2.setText(builder.toString());
691
692            ImageView iv = vh.icon;
693            // We don't actually need the path to the thumbnail file,
694            // we just use it to see if there is album art or not
695            String art = cursor.getString(cursor.getColumnIndexOrThrow(
696                    MediaStore.Audio.Albums.ALBUM_ART));
697            if (unknown || art == null || art.length() == 0) {
698                iv.setBackgroundDrawable(mDefaultAlbumIcon);
699                iv.setImageDrawable(null);
700            } else {
701                int artIndex = cursor.getInt(0);
702                Drawable d = MusicUtils.getCachedArtwork(context, artIndex, mDefaultAlbumIcon);
703                iv.setImageDrawable(d);
704            }
705
706            int currentalbumid = MusicUtils.getCurrentAlbumId();
707            int aid = cursor.getInt(0);
708            iv = vh.play_indicator;
709            if (currentalbumid == aid) {
710                iv.setImageDrawable(mNowPlayingOverlay);
711            } else {
712                iv.setImageDrawable(null);
713            }
714        }
715
716
717        @Override
718        protected Cursor getChildrenCursor(Cursor groupCursor) {
719
720            int id = groupCursor.getInt(groupCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
721
722            String[] cols = new String[] {
723                    MediaStore.Audio.Albums._ID,
724                    MediaStore.Audio.Albums.ALBUM,
725                    MediaStore.Audio.Albums.NUMBER_OF_SONGS,
726                    MediaStore.Audio.Albums.NUMBER_OF_SONGS_FOR_ARTIST,
727                    MediaStore.Audio.Albums.FIRST_YEAR,
728                    MediaStore.Audio.Albums.LAST_YEAR,
729                    MediaStore.Audio.Albums.ALBUM_ART
730            };
731            Cursor c = MusicUtils.query(mActivity,
732                    MediaStore.Audio.Artists.Albums.getContentUri("external", id),
733                    cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
734
735            class MyCursorWrapper extends CursorWrapper {
736                String mArtistName;
737                int mMagicColumnIdx;
738                MyCursorWrapper(Cursor c, String artist) {
739                    super(c);
740                    mArtistName = artist;
741                    if (MediaFile.UNKNOWN_STRING.equals(mArtistName)) {
742                        mArtistName = mUnknownArtist;
743                    }
744                    mMagicColumnIdx = c.getColumnCount();
745                }
746
747                @Override
748                public String getString(int columnIndex) {
749                    if (columnIndex != mMagicColumnIdx) {
750                        return super.getString(columnIndex);
751                    }
752                    return mArtistName;
753                }
754
755                @Override
756                public int getColumnIndexOrThrow(String name) {
757                    if (name.equals(MediaStore.Audio.Albums.ARTIST)) {
758                        return mMagicColumnIdx;
759                    }
760                    return super.getColumnIndexOrThrow(name);
761                }
762
763                @Override
764                public String getColumnName(int idx) {
765                    if (idx != mMagicColumnIdx) {
766                        return super.getColumnName(idx);
767                    }
768                    return MediaStore.Audio.Albums.ARTIST;
769                }
770
771                @Override
772                public int getColumnCount() {
773                    return super.getColumnCount() + 1;
774                }
775            }
776            return new MyCursorWrapper(c, groupCursor.getString(mGroupArtistIdx));
777        }
778
779        @Override
780        public void changeCursor(Cursor cursor) {
781            if (cursor != mActivity.mArtistCursor) {
782                mActivity.mArtistCursor = cursor;
783                getColumnIndices(cursor);
784                super.changeCursor(cursor);
785            }
786        }
787
788        @Override
789        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
790            String s = constraint.toString();
791            if (mConstraintIsValid && (
792                    (s == null && mConstraint == null) ||
793                    (s != null && s.equals(mConstraint)))) {
794                return getCursor();
795            }
796            Cursor c = mActivity.getArtistCursor(null, s);
797            mConstraint = s;
798            mConstraintIsValid = true;
799            return c;
800        }
801
802        public Object[] getSections() {
803            return mIndexer.getSections();
804        }
805
806        public int getPositionForSection(int sectionIndex) {
807            return mIndexer.getPositionForSection(sectionIndex);
808        }
809
810        public int getSectionForPosition(int position) {
811            return 0;
812        }
813    }
814
815    private Cursor mArtistCursor;
816}
817
818