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