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;
20
21import android.app.ListActivity;
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.graphics.Bitmap;
34import android.graphics.BitmapFactory;
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.provider.MediaStore;
44import android.text.TextUtils;
45import android.util.Log;
46import android.view.ContextMenu;
47import android.view.Menu;
48import android.view.MenuItem;
49import android.view.SubMenu;
50import android.view.View;
51import android.view.ViewGroup;
52import android.view.Window;
53import android.view.ContextMenu.ContextMenuInfo;
54import android.widget.Adapter;
55import android.widget.AlphabetIndexer;
56import android.widget.CursorAdapter;
57import android.widget.ExpandableListView;
58import android.widget.ImageView;
59import android.widget.ListAdapter;
60import android.widget.ListView;
61import android.widget.SectionIndexer;
62import android.widget.SimpleCursorAdapter;
63import android.widget.TextView;
64import android.widget.AdapterView.AdapterContextMenuInfo;
65
66import java.text.Collator;
67
68public class AlbumBrowserActivity extends ListActivity
69        implements View.OnCreateContextMenuListener, MusicUtils.Defs, ServiceConnection {
70    private String mCurrentAlbumId;
71    private String mCurrentAlbumName;
72    private String mCurrentArtistNameForAlbum;
73    boolean mIsUnknownArtist;
74    boolean mIsUnknownAlbum;
75    private AlbumListAdapter 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    public AlbumBrowserActivity() {}
83
84    /** Called when the activity is first created. */
85    @Override
86    public void onCreate(Bundle icicle) {
87        if (icicle != null) {
88            mCurrentAlbumId = icicle.getString("selectedalbum");
89            mArtistId = icicle.getString("artist");
90        } else {
91            mArtistId = getIntent().getStringExtra("artist");
92        }
93        super.onCreate(icicle);
94        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
95        requestWindowFeature(Window.FEATURE_NO_TITLE);
96        setVolumeControlStream(AudioManager.STREAM_MUSIC);
97        mToken = MusicUtils.bindToService(this, this);
98
99        IntentFilter f = new IntentFilter();
100        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
101        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
102        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
103        f.addDataScheme("file");
104        registerReceiver(mScanListener, f);
105
106        setContentView(R.layout.media_picker_activity);
107        MusicUtils.updateButtonBar(this, R.id.albumtab);
108        ListView lv = getListView();
109        lv.setOnCreateContextMenuListener(this);
110        lv.setTextFilterEnabled(true);
111
112        mAdapter = (AlbumListAdapter) getLastNonConfigurationInstance();
113        if (mAdapter == null) {
114            // Log.i("@@@", "starting query");
115            mAdapter = new AlbumListAdapter(getApplication(), this, R.layout.track_list_item,
116                    mAlbumCursor, new String[] {}, new int[] {});
117            setListAdapter(mAdapter);
118            setTitle(R.string.working_albums);
119            getAlbumCursor(mAdapter.getQueryHandler(), null);
120        } else {
121            mAdapter.setActivity(this);
122            setListAdapter(mAdapter);
123            mAlbumCursor = mAdapter.getCursor();
124            if (mAlbumCursor != null) {
125                init(mAlbumCursor);
126            } else {
127                getAlbumCursor(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("artist", mArtistId);
145        super.onSaveInstanceState(outcicle);
146    }
147
148    @Override
149    public void onDestroy() {
150        ListView lv = getListView();
151        if (lv != null) {
152            mLastListPosCourse = lv.getFirstVisiblePosition();
153            View cv = lv.getChildAt(0);
154            if (cv != null) {
155                mLastListPosFine = cv.getTop();
156            }
157        }
158        MusicUtils.unbindFromService(mToken);
159        // If we have an adapter and didn't send it off to another activity yet, we should
160        // close its cursor, which we do by assigning a null cursor to it. Doing this
161        // instead of closing the cursor directly keeps the framework from accessing
162        // the closed cursor later.
163        if (!mAdapterSent && mAdapter != null) {
164            mAdapter.changeCursor(null);
165        }
166        // Because we pass the adapter to the next activity, we need to make
167        // sure it doesn't keep a reference to this activity. We can do this
168        // by clearing its DatasetObservers, which setListAdapter(null) does.
169        setListAdapter(null);
170        mAdapter = null;
171        unregisterReceiver(mScanListener);
172        super.onDestroy();
173    }
174
175    @Override
176    public void onResume() {
177        super.onResume();
178        IntentFilter f = new IntentFilter();
179        f.addAction(MediaPlaybackService.META_CHANGED);
180        f.addAction(MediaPlaybackService.QUEUE_CHANGED);
181        registerReceiver(mTrackListListener, f);
182        mTrackListListener.onReceive(null, null);
183
184        MusicUtils.setSpinnerState(this);
185    }
186
187    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
188        @Override
189        public void onReceive(Context context, Intent intent) {
190            getListView().invalidateViews();
191            MusicUtils.updateNowPlaying(AlbumBrowserActivity.this);
192        }
193    };
194    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
195        @Override
196        public void onReceive(Context context, Intent intent) {
197            MusicUtils.setSpinnerState(AlbumBrowserActivity.this);
198            mReScanHandler.sendEmptyMessage(0);
199            if (intent.getAction().equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
200                MusicUtils.clearAlbumArtCache();
201            }
202        }
203    };
204
205    private Handler mReScanHandler = new Handler() {
206        @Override
207        public void handleMessage(Message msg) {
208            if (mAdapter != null) {
209                getAlbumCursor(mAdapter.getQueryHandler(), null);
210            }
211        }
212    };
213
214    @Override
215    public void onPause() {
216        unregisterReceiver(mTrackListListener);
217        mReScanHandler.removeCallbacksAndMessages(null);
218        super.onPause();
219    }
220
221    public void init(Cursor c) {
222        if (mAdapter == null) {
223            return;
224        }
225        mAdapter.changeCursor(c); // also sets mAlbumCursor
226
227        if (mAlbumCursor == null) {
228            MusicUtils.displayDatabaseError(this);
229            closeContextMenu();
230            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
231            return;
232        }
233
234        // restore previous position
235        if (mLastListPosCourse >= 0) {
236            getListView().setSelectionFromTop(mLastListPosCourse, mLastListPosFine);
237            mLastListPosCourse = -1;
238        }
239
240        MusicUtils.hideDatabaseError(this);
241        MusicUtils.updateButtonBar(this, R.id.albumtab);
242        setTitle();
243    }
244
245    private void setTitle() {
246        CharSequence fancyName = "";
247        if (mAlbumCursor != null && mAlbumCursor.getCount() > 0) {
248            mAlbumCursor.moveToFirst();
249            fancyName = mAlbumCursor.getString(
250                    mAlbumCursor.getColumnIndex(MediaStore.Audio.Albums.ARTIST));
251            if (fancyName == null || fancyName.equals(MediaStore.UNKNOWN_STRING))
252                fancyName = getText(R.string.unknown_artist_name);
253        }
254
255        if (mArtistId != null && fancyName != null)
256            setTitle(fancyName);
257        else
258            setTitle(R.string.albums_title);
259    }
260
261    @Override
262    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
263        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
264        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
265        MusicUtils.makePlaylistMenu(this, sub);
266        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
267
268        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
269        mAlbumCursor.moveToPosition(mi.position);
270        mCurrentAlbumId = mAlbumCursor.getString(
271                mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
272        mCurrentAlbumName = mAlbumCursor.getString(
273                mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
274        mCurrentArtistNameForAlbum = mAlbumCursor.getString(
275                mAlbumCursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST));
276        mIsUnknownArtist = mCurrentArtistNameForAlbum == null
277                || mCurrentArtistNameForAlbum.equals(MediaStore.UNKNOWN_STRING);
278        mIsUnknownAlbum =
279                mCurrentAlbumName == null || mCurrentAlbumName.equals(MediaStore.UNKNOWN_STRING);
280        if (mIsUnknownAlbum) {
281            menu.setHeaderTitle(getString(R.string.unknown_album_name));
282        } else {
283            menu.setHeaderTitle(mCurrentAlbumName);
284        }
285        if (!mIsUnknownAlbum || !mIsUnknownArtist) {
286            menu.add(0, SEARCH, 0, R.string.search_title);
287        }
288    }
289
290    @Override
291    public boolean onContextItemSelected(MenuItem item) {
292        switch (item.getItemId()) {
293            case PLAY_SELECTION: {
294                // play the selected album
295                long[] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
296                MusicUtils.playAll(this, list, 0);
297                return true;
298            }
299
300            case QUEUE: {
301                long[] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
302                MusicUtils.addToCurrentPlaylist(this, list);
303                return true;
304            }
305
306            case NEW_PLAYLIST: {
307                Intent intent = new Intent();
308                intent.setClass(this, CreatePlaylist.class);
309                startActivityForResult(intent, NEW_PLAYLIST);
310                return true;
311            }
312
313            case PLAYLIST_SELECTED: {
314                long[] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
315                long playlist = item.getIntent().getLongExtra("playlist", 0);
316                MusicUtils.addToPlaylist(this, list, playlist);
317                return true;
318            }
319            case DELETE_ITEM: {
320                long[] list = MusicUtils.getSongListForAlbum(this, Long.parseLong(mCurrentAlbumId));
321                String f;
322                if (android.os.Environment.isExternalStorageRemovable()) {
323                    f = getString(R.string.delete_album_desc);
324                } else {
325                    f = getString(R.string.delete_album_desc_nosdcard);
326                }
327                String desc = String.format(f, mCurrentAlbumName);
328                Bundle b = new Bundle();
329                b.putString("description", desc);
330                b.putLongArray("items", list);
331                Intent intent = new Intent();
332                intent.setClass(this, DeleteItems.class);
333                intent.putExtras(b);
334                startActivityForResult(intent, -1);
335                return true;
336            }
337            case SEARCH:
338                doSearch();
339                return true;
340        }
341        return super.onContextItemSelected(item);
342    }
343
344    void doSearch() {
345        CharSequence title = null;
346        String query = "";
347
348        Intent i = new Intent();
349        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
350        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
351
352        title = "";
353        if (!mIsUnknownAlbum) {
354            query = mCurrentAlbumName;
355            i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
356            title = mCurrentAlbumName;
357        }
358        if (!mIsUnknownArtist) {
359            query = query + " " + mCurrentArtistNameForAlbum;
360            i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
361            title = title + " " + mCurrentArtistNameForAlbum;
362        }
363        // Since we hide the 'search' menu item when both album and artist are
364        // unknown, the query and title strings will have at least one of those.
365        i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, MediaStore.Audio.Albums.ENTRY_CONTENT_TYPE);
366        title = getString(R.string.mediasearch, title);
367        i.putExtra(SearchManager.QUERY, query);
368
369        startActivity(Intent.createChooser(i, title));
370    }
371
372    @Override
373    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
374        switch (requestCode) {
375            case SCAN_DONE:
376                if (resultCode == RESULT_CANCELED) {
377                    finish();
378                } else {
379                    getAlbumCursor(mAdapter.getQueryHandler(), null);
380                }
381                break;
382
383            case NEW_PLAYLIST:
384                if (resultCode == RESULT_OK) {
385                    Uri uri = intent.getData();
386                    if (uri != null) {
387                        long[] list = MusicUtils.getSongListForAlbum(
388                                this, Long.parseLong(mCurrentAlbumId));
389                        MusicUtils.addToPlaylist(
390                                this, list, Long.parseLong(uri.getLastPathSegment()));
391                    }
392                }
393                break;
394        }
395    }
396
397    @Override
398    protected void onListItemClick(ListView l, View v, int position, long id) {
399        Intent intent = new Intent(Intent.ACTION_PICK);
400        intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
401        intent.putExtra("album", Long.valueOf(id).toString());
402        intent.putExtra("artist", mArtistId);
403        startActivity(intent);
404    }
405
406    @Override
407    public boolean onCreateOptionsMenu(Menu menu) {
408        super.onCreateOptionsMenu(menu);
409        menu.add(0, PARTY_SHUFFLE, 0,
410                R.string.party_shuffle); // icon will be set in onPrepareOptionsMenu()
411        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
412        return true;
413    }
414
415    @Override
416    public boolean onPrepareOptionsMenu(Menu menu) {
417        MusicUtils.setPartyShuffleMenuIcon(menu);
418        return super.onPrepareOptionsMenu(menu);
419    }
420
421    @Override
422    public boolean onOptionsItemSelected(MenuItem item) {
423        Intent intent;
424        Cursor cursor;
425        switch (item.getItemId()) {
426            case PARTY_SHUFFLE:
427                MusicUtils.togglePartyShuffle();
428                break;
429
430            case SHUFFLE_ALL:
431                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
432                        new String[] {MediaStore.Audio.Media._ID},
433                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
434                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
435                if (cursor != null) {
436                    MusicUtils.shuffleAll(this, cursor);
437                    cursor.close();
438                }
439                return true;
440        }
441        return super.onOptionsItemSelected(item);
442    }
443
444    private Cursor getAlbumCursor(AsyncQueryHandler async, String filter) {
445        String[] cols = new String[] {MediaStore.Audio.Albums._ID, MediaStore.Audio.Albums.ARTIST,
446                MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Albums.ALBUM_ART};
447
448        Cursor ret = null;
449        if (mArtistId != null) {
450            Uri uri = MediaStore.Audio.Artists.Albums.getContentUri(
451                    "external", Long.valueOf(mArtistId));
452            if (!TextUtils.isEmpty(filter)) {
453                uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
454            }
455            if (async != null) {
456                async.startQuery(
457                        0, null, uri, cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
458            } else {
459                ret = MusicUtils.query(
460                        this, uri, cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
461            }
462        } else {
463            Uri uri = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
464            if (!TextUtils.isEmpty(filter)) {
465                uri = uri.buildUpon().appendQueryParameter("filter", Uri.encode(filter)).build();
466            }
467            if (async != null) {
468                async.startQuery(
469                        0, null, uri, cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
470            } else {
471                ret = MusicUtils.query(
472                        this, uri, cols, null, null, MediaStore.Audio.Albums.DEFAULT_SORT_ORDER);
473            }
474        }
475        return ret;
476    }
477
478    static class AlbumListAdapter extends SimpleCursorAdapter implements SectionIndexer {
479        private final Drawable mNowPlayingOverlay;
480        private final BitmapDrawable mDefaultAlbumIcon;
481        private int mAlbumIdx;
482        private int mArtistIdx;
483        private int mAlbumArtIndex;
484        private final Resources mResources;
485        private final StringBuilder mStringBuilder = new StringBuilder();
486        private final String mUnknownAlbum;
487        private final String mUnknownArtist;
488        private final String mAlbumSongSeparator;
489        private final Object[] mFormatArgs = new Object[1];
490        private AlphabetIndexer mIndexer;
491        private AlbumBrowserActivity mActivity;
492        private AsyncQueryHandler mQueryHandler;
493        private String mConstraint = null;
494        private boolean mConstraintIsValid = false;
495
496        static class ViewHolder {
497            TextView line1;
498            TextView line2;
499            ImageView play_indicator;
500            ImageView icon;
501        }
502
503        class QueryHandler extends AsyncQueryHandler {
504            QueryHandler(ContentResolver res) {
505                super(res);
506            }
507
508            @Override
509            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
510                // Log.i("@@@", "query complete");
511                mActivity.init(cursor);
512            }
513        }
514
515        AlbumListAdapter(Context context, AlbumBrowserActivity currentactivity, int layout,
516                Cursor cursor, String[] from, int[] to) {
517            super(context, layout, cursor, from, to);
518
519            mActivity = currentactivity;
520            mQueryHandler = new QueryHandler(context.getContentResolver());
521
522            mUnknownAlbum = context.getString(R.string.unknown_album_name);
523            mUnknownArtist = context.getString(R.string.unknown_artist_name);
524            mAlbumSongSeparator = context.getString(R.string.albumsongseparator);
525
526            Resources r = context.getResources();
527            mNowPlayingOverlay = r.getDrawable(R.drawable.indicator_ic_mp_playing_list);
528
529            Bitmap b = BitmapFactory.decodeResource(r, R.drawable.albumart_mp_unknown_list);
530            mDefaultAlbumIcon = new BitmapDrawable(context.getResources(), b);
531            // no filter or dither, it's a lot faster and we can't tell the difference
532            mDefaultAlbumIcon.setFilterBitmap(false);
533            mDefaultAlbumIcon.setDither(false);
534            getColumnIndices(cursor);
535            mResources = context.getResources();
536        }
537
538        private void getColumnIndices(Cursor cursor) {
539            if (cursor != null) {
540                mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM);
541                mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST);
542                mAlbumArtIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM_ART);
543
544                if (mIndexer != null) {
545                    mIndexer.setCursor(cursor);
546                } else {
547                    mIndexer = new MusicAlphabetIndexer(
548                            cursor, mAlbumIdx, mResources.getString(R.string.fast_scroll_alphabet));
549                }
550            }
551        }
552
553        public void setActivity(AlbumBrowserActivity newactivity) {
554            mActivity = newactivity;
555        }
556
557        public AsyncQueryHandler getQueryHandler() {
558            return mQueryHandler;
559        }
560
561        @Override
562        public View newView(Context context, Cursor cursor, ViewGroup parent) {
563            View v = super.newView(context, cursor, parent);
564            ViewHolder vh = new ViewHolder();
565            vh.line1 = (TextView) v.findViewById(R.id.line1);
566            vh.line2 = (TextView) v.findViewById(R.id.line2);
567            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
568            vh.icon = (ImageView) v.findViewById(R.id.icon);
569            vh.icon.setBackgroundDrawable(mDefaultAlbumIcon);
570            vh.icon.setPadding(0, 0, 1, 0);
571            v.setTag(vh);
572            return v;
573        }
574
575        @Override
576        public void bindView(View view, Context context, Cursor cursor) {
577            ViewHolder vh = (ViewHolder) view.getTag();
578
579            String name = cursor.getString(mAlbumIdx);
580            String displayname = name;
581            boolean unknown = name == null || name.equals(MediaStore.UNKNOWN_STRING);
582            if (unknown) {
583                displayname = mUnknownAlbum;
584            }
585            vh.line1.setText(displayname);
586
587            name = cursor.getString(mArtistIdx);
588            displayname = name;
589            if (name == null || name.equals(MediaStore.UNKNOWN_STRING)) {
590                displayname = mUnknownArtist;
591            }
592            vh.line2.setText(displayname);
593
594            ImageView iv = vh.icon;
595            // We don't actually need the path to the thumbnail file,
596            // we just use it to see if there is album art or not
597            String art = cursor.getString(mAlbumArtIndex);
598            long aid = cursor.getLong(0);
599            if (unknown || art == null || art.length() == 0) {
600                iv.setImageDrawable(null);
601            } else {
602                Drawable d = MusicUtils.getCachedArtwork(context, aid, mDefaultAlbumIcon);
603                iv.setImageDrawable(d);
604            }
605
606            long currentalbumid = MusicUtils.getCurrentAlbumId();
607            iv = vh.play_indicator;
608            if (currentalbumid == aid) {
609                iv.setImageDrawable(mNowPlayingOverlay);
610            } else {
611                iv.setImageDrawable(null);
612            }
613        }
614
615        @Override
616        public void changeCursor(Cursor cursor) {
617            if (mActivity.isFinishing() && cursor != null) {
618                cursor.close();
619                cursor = null;
620            }
621            if (cursor != mActivity.mAlbumCursor) {
622                mActivity.mAlbumCursor = cursor;
623                getColumnIndices(cursor);
624                super.changeCursor(cursor);
625            }
626        }
627
628        @Override
629        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
630            String s = constraint.toString();
631            if (mConstraintIsValid && ((s == null && mConstraint == null)
632                                              || (s != null && s.equals(mConstraint)))) {
633                return getCursor();
634            }
635            Cursor c = mActivity.getAlbumCursor(null, s);
636            mConstraint = s;
637            mConstraintIsValid = true;
638            return c;
639        }
640
641        public Object[] getSections() {
642            return mIndexer.getSections();
643        }
644
645        public int getPositionForSection(int section) {
646            return mIndexer.getPositionForSection(section);
647        }
648
649        public int getSectionForPosition(int position) {
650            return 0;
651        }
652    }
653
654    private Cursor mAlbumCursor;
655    private String mArtistId;
656
657    public void onServiceConnected(ComponentName name, IBinder service) {
658        MusicUtils.updateNowPlaying(this);
659    }
660
661    public void onServiceDisconnected(ComponentName name) {
662        finish();
663    }
664}
665