ArtistAlbumBrowserActivity.java revision 89f6c66505717689d9ea3f3d8e81709616b3e588
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.MusicUtils.ServiceToken;
20import com.android.music.QueryBrowserActivity.QueryListAdapter.QueryHandler;
21
22import android.app.ExpandableListActivity;
23import android.app.SearchManager;
24import android.content.AsyncQueryHandler;
25import android.content.BroadcastReceiver;
26import android.content.ComponentName;
27import android.content.ContentResolver;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.ServiceConnection;
32import android.content.res.Resources;
33import android.database.Cursor;
34import android.database.CursorWrapper;
35import android.graphics.drawable.BitmapDrawable;
36import android.graphics.drawable.Drawable;
37import android.media.AudioManager;
38import android.net.Uri;
39import android.os.Bundle;
40import android.os.Handler;
41import android.os.IBinder;
42import android.os.Message;
43import android.os.Parcel;
44import android.os.Parcelable;
45import android.provider.MediaStore;
46import android.text.TextUtils;
47import android.util.Log;
48import android.util.SparseArray;
49import android.view.ContextMenu;
50import android.view.Menu;
51import android.view.MenuItem;
52import android.view.SubMenu;
53import android.view.View;
54import android.view.ViewGroup;
55import android.view.Window;
56import android.view.ContextMenu.ContextMenuInfo;
57import android.widget.ExpandableListView;
58import android.widget.ImageView;
59import android.widget.SectionIndexer;
60import android.widget.SimpleCursorTreeAdapter;
61import android.widget.TextView;
62import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
63
64import java.text.Collator;
65
66
67public class ArtistAlbumBrowserActivity extends ExpandableListActivity
68        implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection
69{
70    private String mCurrentArtistId;
71    private String mCurrentArtistName;
72    private String mCurrentAlbumId;
73    private String mCurrentAlbumName;
74    private String mCurrentArtistNameForAlbum;
75    boolean mIsUnknownArtist;
76    boolean mIsUnknownAlbum;
77    private ArtistAlbumListAdapter mAdapter;
78    private boolean mAdapterSent;
79    private final static int SEARCH = CHILD_MENU_BASE;
80    private static int mLastListPosCourse = -1;
81    private static int mLastListPosFine = -1;
82    private ServiceToken mToken;
83
84    /** Called when the activity is first created. */
85    @Override
86    public void onCreate(Bundle icicle) {
87        super.onCreate(icicle);
88        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
89        requestWindowFeature(Window.FEATURE_NO_TITLE);
90        setVolumeControlStream(AudioManager.STREAM_MUSIC);
91        if (icicle != null) {
92            mCurrentAlbumId = icicle.getString("selectedalbum");
93            mCurrentAlbumName = icicle.getString("selectedalbumname");
94            mCurrentArtistId = icicle.getString("selectedartist");
95            mCurrentArtistName = icicle.getString("selectedartistname");
96        }
97        mToken = MusicUtils.bindToService(this, this);
98
99        IntentFilter f = new IntentFilter();
100        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
101        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
102        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
103        f.addDataScheme("file");
104        registerReceiver(mScanListener, f);
105
106        setContentView(R.layout.media_picker_activity_expanding);
107        MusicUtils.updateButtonBar(this, R.id.artisttab);
108        ExpandableListView lv = getExpandableListView();
109        lv.setOnCreateContextMenuListener(this);
110        lv.setTextFilterEnabled(true);
111
112        mAdapter = (ArtistAlbumListAdapter) getLastNonConfigurationInstance();
113        if (mAdapter == null) {
114            //Log.i("@@@", "starting query");
115            mAdapter = new ArtistAlbumListAdapter(
116                    getApplication(),
117                    this,
118                    null, // cursor
119                    R.layout.track_list_item_group,
120                    new String[] {},
121                    new int[] {},
122                    R.layout.track_list_item_child,
123                    new String[] {},
124                    new int[] {});
125            setListAdapter(mAdapter);
126            setTitle(R.string.working_artists);
127            getArtistCursor(mAdapter.getQueryHandler(), null);
128        } else {
129            mAdapter.setActivity(this);
130            setListAdapter(mAdapter);
131            mArtistCursor = mAdapter.getCursor();
132            if (mArtistCursor != null) {
133                init(mArtistCursor);
134            } else {
135                getArtistCursor(mAdapter.getQueryHandler(), null);
136            }
137        }
138    }
139
140    @Override
141    public Object onRetainNonConfigurationInstance() {
142        mAdapterSent = true;
143        return mAdapter;
144    }
145
146    @Override
147    public void onSaveInstanceState(Bundle outcicle) {
148        // need to store the selected item so we don't lose it in case
149        // of an orientation switch. Otherwise we could lose it while
150        // in the middle of specifying a playlist to add the item to.
151        outcicle.putString("selectedalbum", mCurrentAlbumId);
152        outcicle.putString("selectedalbumname", mCurrentAlbumName);
153        outcicle.putString("selectedartist", mCurrentArtistId);
154        outcicle.putString("selectedartistname", mCurrentArtistName);
155        super.onSaveInstanceState(outcicle);
156    }
157
158    @Override
159    public void onDestroy() {
160        ExpandableListView lv = getExpandableListView();
161        if (lv != null) {
162            mLastListPosCourse = lv.getFirstVisiblePosition();
163            View cv = lv.getChildAt(0);
164            if (cv != null) {
165                mLastListPosFine = cv.getTop();
166            }
167        }
168
169        MusicUtils.unbindFromService(mToken);
170        // If we have an adapter and didn't send it off to another activity yet, we should
171        // close its cursor, which we do by assigning a null cursor to it. Doing this
172        // instead of closing the cursor directly keeps the framework from accessing
173        // the closed cursor later.
174        if (!mAdapterSent && mAdapter != null) {
175            mAdapter.changeCursor(null);
176        }
177        // Because we pass the adapter to the next activity, we need to make
178        // sure it doesn't keep a reference to this activity. We can do this
179        // by clearing its DatasetObservers, which setListAdapter(null) does.
180        setListAdapter(null);
181        mAdapter = null;
182        unregisterReceiver(mScanListener);
183        setListAdapter(null);
184        super.onDestroy();
185    }
186
187    @Override
188    public void onResume() {
189        super.onResume();
190        IntentFilter f = new IntentFilter();
191        f.addAction(MediaPlaybackService.META_CHANGED);
192        f.addAction(MediaPlaybackService.QUEUE_CHANGED);
193        registerReceiver(mTrackListListener, f);
194        mTrackListListener.onReceive(null, null);
195
196        MusicUtils.setSpinnerState(this);
197    }
198
199    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
200        @Override
201        public void onReceive(Context context, Intent intent) {
202            getExpandableListView().invalidateViews();
203            MusicUtils.updateNowPlaying(ArtistAlbumBrowserActivity.this);
204        }
205    };
206    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
207        @Override
208        public void onReceive(Context context, Intent intent) {
209            MusicUtils.setSpinnerState(ArtistAlbumBrowserActivity.this);
210            mReScanHandler.sendEmptyMessage(0);
211            if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
212                MusicUtils.clearAlbumArtCache();
213            }
214        }
215    };
216
217    private Handler mReScanHandler = new Handler() {
218        @Override
219        public void handleMessage(Message msg) {
220            if (mAdapter != null) {
221                getArtistCursor(mAdapter.getQueryHandler(), null);
222            }
223        }
224    };
225
226    @Override
227    public void onPause() {
228        unregisterReceiver(mTrackListListener);
229        mReScanHandler.removeCallbacksAndMessages(null);
230        super.onPause();
231    }
232
233    public void init(Cursor c) {
234
235        if (mAdapter == null) {
236            return;
237        }
238        mAdapter.changeCursor(c); // also sets mArtistCursor
239
240        if (mArtistCursor == null) {
241            MusicUtils.displayDatabaseError(this);
242            closeContextMenu();
243            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
244            return;
245        }
246
247        // restore previous position
248        if (mLastListPosCourse >= 0) {
249            ExpandableListView elv = getExpandableListView();
250            elv.setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
251            mLastListPosCourse = -1;
252        }
253
254        MusicUtils.hideDatabaseError(this);
255        MusicUtils.updateButtonBar(this, R.id.artisttab);
256        setTitle();
257    }
258
259    private void setTitle() {
260        setTitle(R.string.artists_title);
261    }
262
263    @Override
264    public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
265
266        mCurrentAlbumId = Long.valueOf(id).toString();
267
268        Intent intent = new Intent(Intent.ACTION_PICK);
269        intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
270        intent.putExtra("album", mCurrentAlbumId);
271        Cursor c = (Cursor) getExpandableListAdapter().getChild(groupPosition, childPosition);
272        String album = c.getString(c.getColumnIndex(MediaStore.Audio.Albums.ALBUM));
273        if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
274            // unknown album, so we should include the artist ID to limit the songs to songs only by that artist
275            mArtistCursor.moveToPosition(groupPosition);
276            mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndex(MediaStore.Audio.Artists._ID));
277            intent.putExtra("artist", mCurrentArtistId);
278        }
279        startActivity(intent);
280        return true;
281    }
282
283    @Override
284    public boolean onCreateOptionsMenu(Menu menu) {
285        super.onCreateOptionsMenu(menu);
286        menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
287        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
288        return true;
289    }
290
291    @Override
292    public boolean onPrepareOptionsMenu(Menu menu) {
293        MusicUtils.setPartyShuffleMenuIcon(menu);
294        return super.onPrepareOptionsMenu(menu);
295    }
296
297    @Override
298    public boolean onOptionsItemSelected(MenuItem item) {
299        Intent intent;
300        Cursor cursor;
301        switch (item.getItemId()) {
302            case PARTY_SHUFFLE:
303                MusicUtils.togglePartyShuffle();
304                break;
305
306            case SHUFFLE_ALL:
307                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
308                        new String [] { MediaStore.Audio.Media._ID},
309                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
310                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
311                if (cursor != null) {
312                    MusicUtils.shuffleAll(this, cursor);
313                    cursor.close();
314                }
315                return true;
316        }
317        return super.onOptionsItemSelected(item);
318    }
319
320    @Override
321    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
322        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
323        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
324        MusicUtils.makePlaylistMenu(this, sub);
325        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
326
327        ExpandableListContextMenuInfo mi = (ExpandableListContextMenuInfo) menuInfoIn;
328
329        int itemtype = ExpandableListView.getPackedPositionType(mi.packedPosition);
330        int gpos = ExpandableListView.getPackedPositionGroup(mi.packedPosition);
331        int cpos = ExpandableListView.getPackedPositionChild(mi.packedPosition);
332        if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
333            if (gpos == -1) {
334                // this shouldn't happen
335                Log.d("Artist/Album", "no group");
336                return;
337            }
338            gpos = gpos - getExpandableListView().getHeaderViewsCount();
339            mArtistCursor.moveToPosition(gpos);
340            mCurrentArtistId = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists._ID));
341            mCurrentArtistName = mArtistCursor.getString(mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
342            mCurrentAlbumId = null;
343            mIsUnknownArtist = mCurrentArtistName == null ||
344                    mCurrentArtistName.equals(MediaStore.UNKNOWN_STRING);
345            mIsUnknownAlbum = true;
346            if (mIsUnknownArtist) {
347                menu.setHeaderTitle(getString(R.string.unknown_artist_name));
348            } else {
349                menu.setHeaderTitle(mCurrentArtistName);
350                menu.add(0, SEARCH, 0, R.string.search_title);
351            }
352            return;
353        } else if (itemtype == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
354            if (cpos == -1) {
355                // this shouldn't happen
356                Log.d("Artist/Album", "no child");
357                return;
358            }
359            Cursor c = (Cursor) getExpandableListAdapter().getChild(gpos, cpos);
360            c.moveToPosition(cpos);
361            mCurrentArtistId = null;
362            mCurrentAlbumId = Long.valueOf(mi.id).toString();
363            mCurrentAlbumName = c.getString(c.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
364            gpos = gpos - getExpandableListView().getHeaderViewsCount();
365            mArtistCursor.moveToPosition(gpos);
366            mCurrentArtistNameForAlbum = mArtistCursor.getString(
367                    mArtistCursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
368            mIsUnknownArtist = mCurrentArtistNameForAlbum == null ||
369                    mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING);
370            mIsUnknownAlbum = mCurrentAlbumName == null ||
371                    mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING);
372            if (mIsUnknownAlbum) {
373                menu.setHeaderTitle(getString(R.string.unknown_album_name));
374            } else {
375                menu.setHeaderTitle(mCurrentAlbumName);
376            }
377            if (!mIsUnknownAlbum || !mIsUnknownArtist) {
378                menu.add(0, SEARCH, 0, R.string.search_title);
379            }
380        }
381    }
382
383    @Override
384    public boolean onContextItemSelected(MenuItem item) {
385        switch (item.getItemId()) {
386            case PLAY_SELECTION: {
387                // play everything by the selected artist
388                long [] list =
389                    mCurrentArtistId != null ?
390                    MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
391                    : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
392
393                MusicUtils.playAll(this, list, 0);
394                return true;
395            }
396
397            case QUEUE: {
398                long [] list =
399                    mCurrentArtistId != null ?
400                    MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
401                    : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
402                MusicUtils.addToCurrentPlaylist(this, list);
403                return true;
404            }
405
406            case NEW_PLAYLIST: {
407                Intent intent = new Intent();
408                intent.setClass(this, CreatePlaylist.class);
409                startActivityForResult(intent, NEW_PLAYLIST);
410                return true;
411            }
412
413            case PLAYLIST_SELECTED: {
414                long [] list =
415                    mCurrentArtistId != null ?
416                    MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId))
417                    : MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
418                long playlist = item.getIntent().getLongExtra("playlist", 0);
419                MusicUtils.addToPlaylist(this, list, playlist);
420                return true;
421            }
422
423            case DELETE_ITEM: {
424                long [] list;
425                String desc;
426                if (mCurrentArtistId != null) {
427                    list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId));
428                    String f = getString(R.string.delete_artist_desc);
429                    desc = String.format(f, mCurrentArtistName);
430                } else {
431                    list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
432                    String f = getString(R.string.delete_album_desc);
433                    desc = String.format(f, mCurrentAlbumName);
434                }
435                Bundle b = new Bundle();
436                b.putString("description", desc);
437                b.putLongArray("items", list);
438                Intent intent = new Intent();
439                intent.setClass(this, DeleteItems.class);
440                intent.putExtras(b);
441                startActivityForResult(intent, -1);
442                return true;
443            }
444
445            case SEARCH:
446                doSearch();
447                return true;
448        }
449        return super.onContextItemSelected(item);
450    }
451
452    void doSearch() {
453        CharSequence title = null;
454        String query = null;
455
456        Intent i = new Intent();
457        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
458        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
459
460        if (mCurrentArtistId != null) {
461            title = mCurrentArtistName;
462            query = mCurrentArtistName;
463            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistName);
464            i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Artists.ENTRY_CONTENT_TYPE);
465        } else {
466            if (mIsUnknownAlbum) {
467                title = query = mCurrentArtistNameForAlbum;
468            } else {
469                title = query = mCurrentAlbumName;
470                if (!mIsUnknownArtist) {
471                    query = query + " " + mCurrentArtistNameForAlbum;
472                }
473            }
474            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
475            i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
476            i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
477        }
478        title = getString(R.string.mediasearch, title);
479        i.putExtra(SearchManager.QUERY, query);
480
481        startActivity(Intent.createChooser(i, title));
482    }
483
484    @Override
485    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
486        switch (requestCode) {
487            case SCAN_DONE:
488                if (resultCode == RESULT_CANCELED) {
489                    finish();
490                } else {
491                    getArtistCursor(mAdapter.getQueryHandler(), null);
492                }
493                break;
494
495            case NEW_PLAYLIST:
496                if (resultCode == RESULT_OK) {
497                    Uri uri = intent.getData();
498                    if (uri != null) {
499                        long [] list = null;
500                        if (mCurrentArtistId != null) {
501                            list = MusicUtils.getSongListForArtist(this, Long.parseLong(mCurrentArtistId));
502                        } else if (mCurrentAlbumId != null) {
503                            list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
504                        }
505                        MusicUtils.addToPlaylist(this, list, Long.parseLong(uri.getLastPathSegment()));
506                    }
507                }
508                break;
509        }
510    }
511
512    private Cursor getArtistCursor(AsyncQueryHandler async, String filter) {
513
514        String[] cols = new String[] {
515                MediaStore.Audio.Artists._ID,
516                MediaStore.Audio.Artists.ARTIST,
517                MediaStore.Audio.Artists.NUMBER_OF_ALBUMS,
518                MediaStore.Audio.Artists.NUMBER_OF_TRACKS
519        };
520
521        Uri uri = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI;
522        if (!TextUtils.isEmpty(filter)) {
523            uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
524        }
525
526        Cursor ret = null;
527        if (async != null) {
528            async.startQuery(0, null, uri,
529                    cols, null , null, MediaStore.Audio.Artists.ARTIST_KEY);
530        } else {
531            ret = MusicUtils.query(this, uri,
532                    cols, null , null, 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(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(MediaStore.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(MediaStore.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(MediaStore.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 (mActivity.isFinishing() && cursor != null) {
812                cursor.close();
813                cursor = null;
814            }
815            if (cursor != mActivity.mArtistCursor) {
816                mActivity.mArtistCursor = cursor;
817                getColumnIndices(cursor);
818                super.changeCursor(cursor);
819            }
820        }
821
822        @Override
823        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
824            String s = constraint.toString();
825            if (mConstraintIsValid && (
826                    (s == null && mConstraint == null) ||
827                    (s != null && s.equals(mConstraint)))) {
828                return getCursor();
829            }
830            Cursor c = mActivity.getArtistCursor(null, s);
831            mConstraint = s;
832            mConstraintIsValid = true;
833            return c;
834        }
835
836        public Object[] getSections() {
837            return mIndexer.getSections();
838        }
839
840        public int getPositionForSection(int sectionIndex) {
841            return mIndexer.getPositionForSection(sectionIndex);
842        }
843
844        public int getSectionForPosition(int position) {
845            return 0;
846        }
847    }
848
849    private Cursor mArtistCursor;
850
851    public void onServiceConnected(ComponentName name, IBinder service) {
852        MusicUtils.updateNowPlaying(this);
853    }
854
855    public void onServiceDisconnected(ComponentName name) {
856        finish();
857    }
858}
859
860