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