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