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