ArtistAlbumBrowserActivity.java revision d57d69855619bd57ef2205e1c5089fa8e11b6cb1
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        setTitle(R.string.artists_title);
226    }
227
228    @Override
229    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
230
231        mCurrentAlbumId = Long.valueOf(id).toString();
232
233        Intent intent = new Intent(Intent.ACTION_PICK);
234        intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
235        intent.putExtra("album", mCurrentAlbumId);
236        Cursor c = (Cursor) getExpandableListAdapter().getChild(groupPosition, childPosition);
237        String album = c.getString(c.getColumnIndex(MediaStore.Audio.Albums.ALBUM));
238        if (album.equals(MediaFile.UNKNOWN_STRING)) {
239            // unknown album, so we should include the artist ID to limit the songs to songs only by that artist
240            mArtistCursor.moveToPosition(groupPosition);
241            mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndex(MediaStore.Audio.Artists._ID));
242            intent.putExtra("artist", mCurrentArtistId);
243        }
244        startActivity(intent);
245        return true;
246    }
247
248    @Override
249    public boolean onCreateOptionsMenu(Menu menu) {
250        super.onCreateOptionsMenu(menu);
251        menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
252        menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback);
253        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
254        return true;
255    }
256
257    @Override
258    public boolean onPrepareOptionsMenu(Menu menu) {
259        menu.findItem(GOTO_PLAYBACK).setVisible(MusicUtils.isMusicLoaded());
260        return super.onPrepareOptionsMenu(menu);
261    }
262
263    @Override
264    public boolean onOptionsItemSelected(MenuItem item) {
265        Intent intent;
266        Cursor cursor;
267        switch (item.getItemId()) {
268            case GOTO_START:
269                intent = new Intent();
270                intent.setClass(this, MusicBrowserActivity.class);
271                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
272                startActivity(intent);
273                return true;
274
275            case GOTO_PLAYBACK:
276                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
277                startActivity(intent);
278                return true;
279
280            case SHUFFLE_ALL:
281                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
282                        new String [] { MediaStore.Audio.Media._ID},
283                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
284                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
285                if (cursor != null) {
286                    MusicUtils.shuffleAll(this, cursor);
287                    cursor.close();
288                }
289                return true;
290        }
291        return super.onOptionsItemSelected(item);
292    }
293
294    @Override
295    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
296        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
297        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
298        MusicUtils.makePlaylistMenu(this, sub);
299        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
300        menu.add(0, SEARCH, 0, R.string.search_title);
301
302        ExpandableListContextMenuInfo mi = (ExpandableListContextMenuInfo) menuInfoIn;
303
304        int itemtype = ExpandableListView.getPackedPositionType(mi.packedPosition);
305        int gpos = ExpandableListView.getPackedPositionGroup(mi.packedPosition);
306        int cpos = ExpandableListView.getPackedPositionChild(mi.packedPosition);
307        if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
308            if (gpos == -1) {
309                // this shouldn't happen
310                Log.d("Artist/Album", "no group");
311                return;
312            }
313            gpos = gpos - getExpandableListView().getHeaderViewsCount();
314            mArtistCursor.moveToPosition(gpos);
315            mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
316            mCurrentArtistName = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
317            mCurrentAlbumId = null;
318            menu.setHeaderTitle(mCurrentArtistName);
319            return;
320        } else if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
321            if (cpos == -1) {
322                // this shouldn't happen
323                Log.d("Artist/Album", "no child");
324                return;
325            }
326            Cursor c = (Cursor) getExpandableListAdapter().getChild(gpos, cpos);
327            c.moveToPosition(cpos);
328            mCurrentArtistId = null;
329            mCurrentAlbumId = Long.valueOf(mi.id).toString();
330            mCurrentAlbumName = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
331            gpos = gpos - getExpandableListView().getHeaderViewsCount();
332            mArtistCursor.moveToPosition(gpos);
333            mCurrentArtistNameForAlbum = mArtistCursor.getString(
334                    mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
335            menu.setHeaderTitle(mCurrentAlbumName);
336        }
337    }
338
339    @Override
340    public boolean onContextItemSelected(MenuItem item) {
341        switch (item.getItemId()) {
342            case PLAY_SELECTION: {
343                // play everything by the selected artist
344                int [] list =
345                    mCurrentArtistId != null ?
346                    MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
347                    : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
348
349                MusicUtils.playAll(this, list, 0);
350                return true;
351            }
352
353            case QUEUE: {
354                int [] list =
355                    mCurrentArtistId != null ?
356                    MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
357                    : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
358                MusicUtils.addToCurrentPlaylist(this, list);
359                return true;
360            }
361
362            case NEW_PLAYLIST: {
363                Intent intent = new Intent();
364                intent.setClass(this, CreatePlaylist.class);
365                startActivityForResult(intent, NEW_PLAYLIST);
366                return true;
367            }
368
369            case PLAYLIST_SELECTED: {
370                int [] list =
371                    mCurrentArtistId != null ?
372                    MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId))
373                    : MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
374                int playlist = item.getIntent().getIntExtra("playlist", 0);
375                MusicUtils.addToPlaylist(this, list, playlist);
376                return true;
377            }
378
379            case DELETE_ITEM: {
380                int [] list;
381                String desc;
382                if (mCurrentArtistId != null) {
383                    list = MusicUtils.getSongListForArtist(this, Integer.parseInt(mCurrentArtistId));
384                    String f = getString(R.string.delete_artist_desc);
385                    desc = String.format(f, mCurrentArtistName);
386                } else {
387                    list = MusicUtils.getSongListForAlbum(this, Integer.parseInt(mCurrentAlbumId));
388                    String f = getString(R.string.delete_album_desc);
389                    desc = String.format(f, mCurrentAlbumName);
390                }
391                Bundle b = new Bundle();
392                b.putString("description", desc);
393                b.putIntArray("items", list);
394                Intent intent = new Intent();
395                intent.setClass(this, DeleteItems.class);
396                intent.putExtras(b);
397                startActivityForResult(intent, -1);
398                return true;
399            }
400
401            case SEARCH:
402                doSearch();
403                return true;
404        }
405        return super.onContextItemSelected(item);
406    }
407
408    void doSearch() {
409        CharSequence title = null;
410        String query = null;
411
412        Intent i = new Intent();
413        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
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
520        class ViewHolder {
521            TextView line1;
522            TextView line2;
523            ImageView play_indicator;
524            ImageView icon;
525        }
526
527        class QueryHandler extends AsyncQueryHandler {
528            QueryHandler(ContentResolver res) {
529                super(res);
530            }
531
532            @Override
533            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
534                //Log.i("@@@", "query complete");
535                mActivity.init(cursor);
536                mActivity.setTitle();
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 (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            return mActivity.getArtistCursor(null, constraint.toString());
791        }
792
793        public Object[] getSections() {
794            return mIndexer.getSections();
795        }
796
797        public int getPositionForSection(int sectionIndex) {
798            return mIndexer.getPositionForSection(sectionIndex);
799        }
800
801        public int getSectionForPosition(int position) {
802            return 0;
803        }
804    }
805
806    private Cursor mArtistCursor;
807}
808
809