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