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