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