TrackBrowserActivity.java revision a857e7ab7608a85c8d66e971e5807051bdb6daba
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 android.app.ListActivity;
20import android.app.SearchManager;
21import android.content.AsyncQueryHandler;
22import android.content.BroadcastReceiver;
23import android.content.ContentResolver;
24import android.content.ContentUris;
25import android.content.ContentValues;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.database.AbstractCursor;
30import android.database.CharArrayBuffer;
31import android.database.Cursor;
32import android.media.AudioManager;
33import android.media.MediaFile;
34import android.net.Uri;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.Message;
38import android.os.RemoteException;
39import android.provider.MediaStore;
40import android.provider.MediaStore.Audio.Playlists;
41import android.util.Log;
42import android.view.ContextMenu;
43import android.view.KeyEvent;
44import android.view.Menu;
45import android.view.MenuItem;
46import android.view.SubMenu;
47import android.view.View;
48import android.view.ViewGroup;
49import android.view.Window;
50import android.view.ContextMenu.ContextMenuInfo;
51import android.widget.AlphabetIndexer;
52import android.widget.ImageView;
53import android.widget.ListView;
54import android.widget.SectionIndexer;
55import android.widget.SimpleCursorAdapter;
56import android.widget.TextView;
57import android.widget.AdapterView.AdapterContextMenuInfo;
58
59import java.text.Collator;
60import java.util.Arrays;
61
62public class TrackBrowserActivity extends ListActivity
63        implements View.OnCreateContextMenuListener, MusicUtils.Defs
64{
65    private final int Q_SELECTED = CHILD_MENU_BASE;
66    private final int Q_ALL = CHILD_MENU_BASE + 1;
67    private final int SAVE_AS_PLAYLIST = CHILD_MENU_BASE + 2;
68    private final int PLAY_ALL = CHILD_MENU_BASE + 3;
69    private final int CLEAR_PLAYLIST = CHILD_MENU_BASE + 4;
70    private final int REMOVE = CHILD_MENU_BASE + 5;
71    private final int SEARCH = CHILD_MENU_BASE + 6;
72
73
74    private static final String LOGTAG = "TrackBrowser";
75
76    private String[] mCursorCols;
77    private String[] mPlaylistMemberCols;
78    private boolean mDeletedOneRow = false;
79    private boolean mEditMode = false;
80    private String mCurrentTrackName;
81    private String mCurrentAlbumName;
82    private String mCurrentArtistNameForAlbum;
83    private MyQueryHandler mQueryHandler;
84    private String mFilterString = "";
85
86    public TrackBrowserActivity()
87    {
88    }
89
90    /** Called when the activity is first created. */
91    @Override
92    public void onCreate(Bundle icicle)
93    {
94        super.onCreate(icicle);
95        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
96        setVolumeControlStream(AudioManager.STREAM_MUSIC);
97        if (icicle != null) {
98            mSelectedId = icicle.getLong("selectedtrack");
99            mAlbumId = icicle.getString("album");
100            mArtistId = icicle.getString("artist");
101            mPlaylist = icicle.getString("playlist");
102            mGenre = icicle.getString("genre");
103            mEditMode = icicle.getBoolean("editmode", false);
104        } else {
105            mAlbumId = getIntent().getStringExtra("album");
106            // If we have an album, show everything on the album, not just stuff
107            // by a particular artist.
108            Intent intent = getIntent();
109            mArtistId = intent.getStringExtra("artist");
110            mPlaylist = intent.getStringExtra("playlist");
111            mGenre = intent.getStringExtra("genre");
112            mEditMode = intent.getAction().equals(Intent.ACTION_EDIT);
113        }
114
115        mCursorCols = new String[] {
116                MediaStore.Audio.Media._ID,
117                MediaStore.Audio.Media.TITLE,
118                MediaStore.Audio.Media.TITLE_KEY,
119                MediaStore.Audio.Media.DATA,
120                MediaStore.Audio.Media.ALBUM,
121                MediaStore.Audio.Media.ARTIST,
122                MediaStore.Audio.Media.ARTIST_ID,
123                MediaStore.Audio.Media.DURATION
124        };
125        mPlaylistMemberCols = new String[] {
126                MediaStore.Audio.Playlists.Members._ID,
127                MediaStore.Audio.Media.TITLE,
128                MediaStore.Audio.Media.TITLE_KEY,
129                MediaStore.Audio.Media.DATA,
130                MediaStore.Audio.Media.ALBUM,
131                MediaStore.Audio.Media.ARTIST,
132                MediaStore.Audio.Media.ARTIST_ID,
133                MediaStore.Audio.Media.DURATION,
134                MediaStore.Audio.Playlists.Members.PLAY_ORDER,
135                MediaStore.Audio.Playlists.Members.AUDIO_ID
136        };
137
138        MusicUtils.bindToService(this);
139
140        IntentFilter f = new IntentFilter();
141        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
142        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
143        f.addDataScheme("file");
144        registerReceiver(mScanListener, f);
145
146        setContentView(R.layout.media_picker_activity);
147        mQueryHandler = new MyQueryHandler(getContentResolver());
148        mTrackCursor = (Cursor) getLastNonConfigurationInstance();
149        if (mTrackCursor == null) {
150            //Log.i("@@@", "starting query");
151            setTitle(R.string.working_songs);
152            getTrackCursor(mQueryHandler);
153        } else {
154            init(mTrackCursor);
155            setTitle();
156        }
157    }
158
159    @Override
160    public Object onRetainNonConfigurationInstance() {
161        Cursor c = mTrackCursor;
162        mTrackCursor = null;
163        return c;
164    }
165
166    class MyQueryHandler extends AsyncQueryHandler {
167        MyQueryHandler(ContentResolver res) {
168            super(res);
169        }
170
171        @Override
172        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
173            //Log.i("@@@", "query complete");
174            init(cursor);
175            setTitle();
176        }
177    }
178
179
180    @Override
181    public void onDestroy() {
182        MusicUtils.unbindFromService(this);
183        try {
184            if ("nowplaying".equals(mPlaylist)) {
185                unregisterReceiver(mNowPlayingListener);
186            } else {
187                unregisterReceiver(mTrackListListener);
188            }
189        } catch (IllegalArgumentException ex) {
190            // we end up here in case we never registered the listeners
191        }
192        if (mTrackCursor != null) {
193            mTrackCursor.close();
194            mTrackCursor = null;
195        }
196        unregisterReceiver(mScanListener);
197        super.onDestroy();
198   }
199
200    @Override
201    public void onResume() {
202        super.onResume();
203        if (mTrackCursor != null) {
204            getListView().invalidateViews();
205        }
206        MusicUtils.setSpinnerState(this);
207    }
208    @Override
209    public void onPause() {
210        mReScanHandler.removeCallbacksAndMessages(null);
211        super.onPause();
212    }
213
214    /*
215     * This listener gets called when the media scanner starts up, and when the
216     * sd card is unmounted.
217     */
218    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
219        @Override
220        public void onReceive(Context context, Intent intent) {
221            MusicUtils.setSpinnerState(TrackBrowserActivity.this);
222            mReScanHandler.sendEmptyMessage(0);
223        }
224    };
225
226    private Handler mReScanHandler = new Handler() {
227        public void handleMessage(Message msg) {
228            setTitle();
229            getTrackCursor(mQueryHandler);
230            if (mTrackCursor == null) {
231                sendEmptyMessageDelayed(0, 1000);
232            }
233        }
234    };
235
236    public void onSaveInstanceState(Bundle outcicle) {
237        // need to store the selected item so we don't lose it in case
238        // of an orientation switch. Otherwise we could lose it while
239        // in the middle of specifying a playlist to add the item to.
240        outcicle.putLong("selectedtrack", mSelectedId);
241        outcicle.putString("artist", mArtistId);
242        outcicle.putString("album", mAlbumId);
243        outcicle.putString("playlist", mPlaylist);
244        outcicle.putString("genre", mGenre);
245        outcicle.putBoolean("editmode", mEditMode);
246        super.onSaveInstanceState(outcicle);
247    }
248
249    public void init(Cursor newCursor) {
250
251        mTrackCursor = newCursor;
252
253        setContentView(R.layout.media_picker_activity);
254        mTrackList = (ListView) findViewById(android.R.id.list);
255        if (mEditMode) {
256            //((TouchInterceptor) mTrackList).setDragListener(mDragListener);
257            ((TouchInterceptor) mTrackList).setDropListener(mDropListener);
258            ((TouchInterceptor) mTrackList).setRemoveListener(mRemoveListener);
259        }
260
261        if (mTrackCursor == null) {
262            MusicUtils.displayDatabaseError(this);
263            return;
264        }
265
266
267        TrackListAdapter adapter = new TrackListAdapter(
268                this,
269                mEditMode ? R.layout.edit_track_list_item : R.layout.track_list_item,
270                mTrackCursor,
271                new String[] {},
272                new int[] {},
273                "nowplaying".equals(mPlaylist),
274                mPlaylist != null);
275
276        setTitle();
277        setListAdapter(adapter);
278        ListView lv = getListView();
279        lv.setOnCreateContextMenuListener(this);
280        lv.setCacheColorHint(0);
281        if (!mEditMode) {
282            lv.setTextFilterEnabled(true);
283        }
284
285        // When showing the queue, position the selection on the currently playing track
286        // Otherwise, position the selection on the first matching artist, if any
287        IntentFilter f = new IntentFilter();
288        f.addAction(MediaPlaybackService.META_CHANGED);
289        f.addAction(MediaPlaybackService.QUEUE_CHANGED);
290        if ("nowplaying".equals(mPlaylist)) {
291            try {
292                int cur = MusicUtils.sService.getQueuePosition();
293                setSelection(cur);
294                registerReceiver(mNowPlayingListener, new IntentFilter(f));
295                mNowPlayingListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
296            } catch (RemoteException ex) {
297            }
298        } else {
299            String key = getIntent().getStringExtra("artist");
300            if (key != null) {
301                int keyidx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST_ID);
302                mTrackCursor.moveToFirst();
303                while (! mTrackCursor.isAfterLast()) {
304                    String artist = mTrackCursor.getString(keyidx);
305                    if (artist.equals(key)) {
306                        setSelection(mTrackCursor.getPosition());
307                        break;
308                    }
309                    mTrackCursor.moveToNext();
310                }
311            }
312            registerReceiver(mTrackListListener, new IntentFilter(f));
313            mTrackListListener.onReceive(this, new Intent(MediaPlaybackService.META_CHANGED));
314        }
315    }
316
317    private void setTitle() {
318        int numresults = mTrackCursor != null ? mTrackCursor.getCount() : 0;
319        if (numresults > 0) {
320            mTrackCursor.moveToFirst();
321
322            CharSequence fancyName = null;
323            if (mAlbumId != null) {
324                int idx = mTrackCursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
325                fancyName = mTrackCursor.getString(idx);
326                // For compilation albums show only the album title,
327                // but for regular albums show "artist - album".
328                // To determine whether something is a compilation
329                // album, do a query for the artist + album of the
330                // first item, and see if it returns the same number
331                // of results as the album query.
332                String where = MediaStore.Audio.Media.ALBUM_ID + "='" + mAlbumId +
333                        "' AND " + MediaStore.Audio.Media.ARTIST_ID + "=" +
334                        mTrackCursor.getLong(mTrackCursor.getColumnIndexOrThrow(
335                                MediaStore.Audio.Media.ARTIST_ID));
336                Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
337                    new String[] {MediaStore.Audio.Media.ALBUM}, where, null, null);
338                if (cursor != null) {
339                    if (cursor.getCount() != numresults) {
340                        // compilation album
341                        fancyName = mTrackCursor.getString(idx);
342                    }
343                    cursor.deactivate();
344                }
345            } else if (mPlaylist != null) {
346                if (mPlaylist.equals("nowplaying")) {
347                    if (MusicUtils.getCurrentShuffleMode() == MediaPlaybackService.SHUFFLE_AUTO) {
348                        fancyName = getText(R.string.partyshuffle_title);
349                    } else {
350                        fancyName = getText(R.string.nowplaying_title);
351                    }
352                } else {
353                    String [] cols = new String [] {
354                    MediaStore.Audio.Playlists.NAME
355                    };
356                    Cursor cursor = MusicUtils.query(this,
357                            ContentUris.withAppendedId(Playlists.EXTERNAL_CONTENT_URI, Long.valueOf(mPlaylist)),
358                            cols, null, null, null);
359                    if (cursor != null) {
360                        if (cursor.getCount() != 0) {
361                            cursor.moveToFirst();
362                            fancyName = cursor.getString(0);
363                        }
364                        cursor.deactivate();
365                    }
366                }
367            } else if (mGenre != null) {
368                String [] cols = new String [] {
369                MediaStore.Audio.Genres.NAME
370                };
371                Cursor cursor = MusicUtils.query(this,
372                        ContentUris.withAppendedId(MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI, Long.valueOf(mGenre)),
373                        cols, null, null, null);
374                if (cursor != null) {
375                    if (cursor.getCount() != 0) {
376                        cursor.moveToFirst();
377                        fancyName = cursor.getString(0);
378                    }
379                    cursor.deactivate();
380                }
381            }
382
383            if (fancyName != null) {
384                setTitle(fancyName);
385            } else {
386                setTitle(R.string.tracks_title);
387            }
388        } else {
389            setTitle(R.string.no_tracks_title);
390        }
391    }
392
393    private TouchInterceptor.DragListener mDragListener =
394        new TouchInterceptor.DragListener() {
395        public void drag(int from, int to) {
396            if (mTrackCursor instanceof NowPlayingCursor) {
397                NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
398                c.moveItem(from, to);
399                ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
400                getListView().invalidateViews();
401                mDeletedOneRow = true;
402            }
403        }
404    };
405    private TouchInterceptor.DropListener mDropListener =
406        new TouchInterceptor.DropListener() {
407        public void drop(int from, int to) {
408            if (mTrackCursor instanceof NowPlayingCursor) {
409                // update the currently playing list
410                NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
411                c.moveItem(from, to);
412                ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
413                getListView().invalidateViews();
414                mDeletedOneRow = true;
415            } else {
416                // update a saved playlist
417                Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external",
418                        Long.valueOf(mPlaylist));
419                ContentValues values = new ContentValues();
420                String where = MediaStore.Audio.Playlists.Members._ID + "=?";
421                String [] wherearg = new String[1];
422                ContentResolver res = getContentResolver();
423
424                int colidx = mTrackCursor.getColumnIndexOrThrow(
425                        MediaStore.Audio.Playlists.Members.PLAY_ORDER);
426                if (from < to) {
427                    // move the item to somewhere later in the list
428                    mTrackCursor.moveToPosition(to);
429                    int toidx = mTrackCursor.getInt(colidx);
430                    mTrackCursor.moveToPosition(from);
431                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx);
432                    wherearg[0] = mTrackCursor.getString(0);
433                    res.update(baseUri, values, where, wherearg);
434                    for (int i = from + 1; i <= to; i++) {
435                        mTrackCursor.moveToPosition(i);
436                        values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i - 1);
437                        wherearg[0] = mTrackCursor.getString(0);
438                        res.update(baseUri, values, where, wherearg);
439                    }
440                } else if (from > to) {
441                    // move the item to somewhere earlier in the list
442                    mTrackCursor.moveToPosition(to);
443                    int toidx = mTrackCursor.getInt(colidx);
444                    mTrackCursor.moveToPosition(from);
445                    values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, toidx);
446                    wherearg[0] = mTrackCursor.getString(0);
447                    res.update(baseUri, values, where, wherearg);
448                    for (int i = from - 1; i >= to; i--) {
449                        mTrackCursor.moveToPosition(i);
450                        values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, i + 1);
451                        wherearg[0] = mTrackCursor.getString(0);
452                        res.update(baseUri, values, where, wherearg);
453                    }
454                }
455            }
456        }
457    };
458
459    private TouchInterceptor.RemoveListener mRemoveListener =
460        new TouchInterceptor.RemoveListener() {
461        public void remove(int which) {
462            removePlaylistItem(which);
463        }
464    };
465
466    private void removePlaylistItem(int which) {
467        View v = mTrackList.getChildAt(which - mTrackList.getFirstVisiblePosition());
468        try {
469            if (MusicUtils.sService != null
470                    && which != MusicUtils.sService.getQueuePosition()) {
471                mDeletedOneRow = true;
472            }
473        } catch (RemoteException e) {
474            // Service died, so nothing playing.
475            mDeletedOneRow = true;
476        }
477        v.setVisibility(View.GONE);
478        mTrackList.invalidateViews();
479        if (mTrackCursor instanceof NowPlayingCursor) {
480            ((NowPlayingCursor)mTrackCursor).removeItem(which);
481        } else {
482            int colidx = mTrackCursor.getColumnIndexOrThrow(
483                    MediaStore.Audio.Playlists.Members._ID);
484            mTrackCursor.moveToPosition(which);
485            long id = mTrackCursor.getLong(colidx);
486            Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
487                    Long.valueOf(mPlaylist));
488            getContentResolver().delete(
489                    ContentUris.withAppendedId(uri, id), null, null);
490        }
491        v.setVisibility(View.VISIBLE);
492        mTrackList.invalidateViews();
493    }
494
495    private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
496        @Override
497        public void onReceive(Context context, Intent intent) {
498            getListView().invalidateViews();
499        }
500    };
501
502    private BroadcastReceiver mNowPlayingListener = new BroadcastReceiver() {
503        @Override
504        public void onReceive(Context context, Intent intent) {
505            if (intent.getAction().equals(MediaPlaybackService.META_CHANGED)) {
506                getListView().invalidateViews();
507            } else if (intent.getAction().equals(MediaPlaybackService.QUEUE_CHANGED)) {
508                if (mDeletedOneRow) {
509                    // This is the notification for a single row that was
510                    // deleted previously, which is already reflected in
511                    // the UI.
512                    mDeletedOneRow = false;
513                    return;
514                }
515                mTrackCursor.close();
516                mTrackCursor = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
517                if (mTrackCursor.getCount() == 0) {
518                    finish();
519                    return;
520                }
521                ((TrackListAdapter)getListAdapter()).changeCursor(mTrackCursor);
522                getListView().invalidateViews();
523            }
524        }
525    };
526
527    @Override
528    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
529        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
530        SubMenu sub = menu.addSubMenu(0, ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
531        MusicUtils.makePlaylistMenu(this, sub);
532        if (mEditMode) {
533            menu.add(0, REMOVE, 0, R.string.remove_from_playlist);
534        }
535        menu.add(0, USE_AS_RINGTONE, 0, R.string.ringtone_menu);
536        menu.add(0, DELETE_ITEM, 0, R.string.delete_item);
537        menu.add(0, SEARCH, 0, R.string.search_title);
538        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
539        mSelectedPosition =  mi.position;
540        mTrackCursor.moveToPosition(mSelectedPosition);
541        try {
542            int id_idx = mTrackCursor.getColumnIndexOrThrow(
543                    MediaStore.Audio.Playlists.Members.AUDIO_ID);
544            mSelectedId = mTrackCursor.getInt(id_idx);
545        } catch (IllegalArgumentException ex) {
546            mSelectedId = mi.id;
547        }
548        mCurrentAlbumName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
549                MediaStore.Audio.Media.ALBUM));
550        mCurrentArtistNameForAlbum = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
551                MediaStore.Audio.Media.ARTIST));
552        mCurrentTrackName = mTrackCursor.getString(mTrackCursor.getColumnIndexOrThrow(
553                MediaStore.Audio.Media.TITLE));
554        menu.setHeaderTitle(mCurrentTrackName);
555    }
556
557    @Override
558    public boolean onContextItemSelected(MenuItem item) {
559        switch (item.getItemId()) {
560            case PLAY_SELECTION: {
561                // play the track
562                int position = mSelectedPosition;
563                MusicUtils.playAll(this, mTrackCursor, position);
564                return true;
565            }
566
567            case QUEUE: {
568                int [] list = new int[] { (int) mSelectedId };
569                MusicUtils.addToCurrentPlaylist(this, list);
570                return true;
571            }
572
573            case NEW_PLAYLIST: {
574                Intent intent = new Intent();
575                intent.setClass(this, CreatePlaylist.class);
576                startActivityForResult(intent, NEW_PLAYLIST);
577                return true;
578            }
579
580            case PLAYLIST_SELECTED: {
581                int [] list = new int[] { (int) mSelectedId };
582                int playlist = item.getIntent().getIntExtra("playlist", 0);
583                MusicUtils.addToPlaylist(this, list, playlist);
584                return true;
585            }
586
587            case USE_AS_RINGTONE:
588                // Set the system setting to make this the current ringtone
589                MusicUtils.setRingtone(this, mSelectedId);
590                return true;
591
592            case DELETE_ITEM: {
593                int [] list = new int[1];
594                list[0] = (int) mSelectedId;
595                Bundle b = new Bundle();
596                String f = getString(R.string.delete_song_desc);
597                String desc = String.format(f, mCurrentTrackName);
598                b.putString("description", desc);
599                b.putIntArray("items", list);
600                Intent intent = new Intent();
601                intent.setClass(this, DeleteItems.class);
602                intent.putExtras(b);
603                startActivityForResult(intent, -1);
604                return true;
605            }
606
607            case REMOVE:
608                removePlaylistItem(mSelectedPosition);
609                return true;
610
611            case SEARCH:
612                doSearch();
613                return true;
614        }
615        return super.onContextItemSelected(item);
616    }
617
618    void doSearch() {
619        CharSequence title = null;
620        String query = null;
621
622        Intent i = new Intent();
623        i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH);
624
625        title = mCurrentAlbumName;
626        query = mCurrentArtistNameForAlbum + " " + mCurrentAlbumName;
627        i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, mCurrentArtistNameForAlbum);
628        i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, mCurrentAlbumName);
629        i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*");
630        title = getString(R.string.mediasearch, title);
631        i.putExtra(SearchManager.QUERY, query);
632
633        startActivity(Intent.createChooser(i, title));
634    }
635
636    // In order to use alt-up/down as a shortcut for moving the selected item
637    // in the list, we need to override dispatchKeyEvent, not onKeyDown.
638    // (onKeyDown never sees these events, since they are handled by the list)
639    @Override
640    public boolean dispatchKeyEvent(KeyEvent event) {
641        if (mPlaylist != null && event.getMetaState() != 0 &&
642                event.getAction() == KeyEvent.ACTION_DOWN) {
643            switch (event.getKeyCode()) {
644                case KeyEvent.KEYCODE_DPAD_UP:
645                    moveItem(true);
646                    return true;
647                case KeyEvent.KEYCODE_DPAD_DOWN:
648                    moveItem(false);
649                    return true;
650                case KeyEvent.KEYCODE_DEL:
651                    removeItem();
652                    return true;
653            }
654        }
655
656        return super.dispatchKeyEvent(event);
657    }
658
659    private void removeItem() {
660        int curcount = mTrackCursor.getCount();
661        int curpos = mTrackList.getSelectedItemPosition();
662        if (curcount == 0 || curpos < 0) {
663            return;
664        }
665
666        if ("nowplaying".equals(mPlaylist)) {
667            // remove track from queue
668
669            // Work around bug 902971. To get quick visual feedback
670            // of the deletion of the item, hide the selected view.
671            try {
672                if (curpos != MusicUtils.sService.getQueuePosition()) {
673                    mDeletedOneRow = true;
674                }
675            } catch (RemoteException ex) {
676            }
677            View v = mTrackList.getSelectedView();
678            v.setVisibility(View.GONE);
679            mTrackList.invalidateViews();
680            ((NowPlayingCursor)mTrackCursor).removeItem(curpos);
681            v.setVisibility(View.VISIBLE);
682            mTrackList.invalidateViews();
683        } else {
684            // remove track from playlist
685            int colidx = mTrackCursor.getColumnIndexOrThrow(
686                    MediaStore.Audio.Playlists.Members._ID);
687            mTrackCursor.moveToPosition(curpos);
688            long id = mTrackCursor.getLong(colidx);
689            Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
690                    Long.valueOf(mPlaylist));
691            getContentResolver().delete(
692                    ContentUris.withAppendedId(uri, id), null, null);
693            curcount--;
694            if (curcount == 0) {
695                finish();
696            } else {
697                mTrackList.setSelection(curpos < curcount ? curpos : curcount);
698            }
699        }
700    }
701
702    private void moveItem(boolean up) {
703        int curcount = mTrackCursor.getCount();
704        int curpos = mTrackList.getSelectedItemPosition();
705        if ( (up && curpos < 1) || (!up  && curpos >= curcount - 1)) {
706            return;
707        }
708
709        if (mTrackCursor instanceof NowPlayingCursor) {
710            NowPlayingCursor c = (NowPlayingCursor) mTrackCursor;
711            c.moveItem(curpos, up ? curpos - 1 : curpos + 1);
712            ((TrackListAdapter)getListAdapter()).notifyDataSetChanged();
713            getListView().invalidateViews();
714            mDeletedOneRow = true;
715            if (up) {
716                mTrackList.setSelection(curpos - 1);
717            } else {
718                mTrackList.setSelection(curpos + 1);
719            }
720        } else {
721            int colidx = mTrackCursor.getColumnIndexOrThrow(
722                    MediaStore.Audio.Playlists.Members.PLAY_ORDER);
723            mTrackCursor.moveToPosition(curpos);
724            int currentplayidx = mTrackCursor.getInt(colidx);
725            Uri baseUri = MediaStore.Audio.Playlists.Members.getContentUri("external",
726                    Long.valueOf(mPlaylist));
727            ContentValues values = new ContentValues();
728            String where = MediaStore.Audio.Playlists.Members._ID + "=?";
729            String [] wherearg = new String[1];
730            ContentResolver res = getContentResolver();
731            if (up) {
732                values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx - 1);
733                wherearg[0] = mTrackCursor.getString(0);
734                res.update(baseUri, values, where, wherearg);
735                mTrackCursor.moveToPrevious();
736            } else {
737                values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx + 1);
738                wherearg[0] = mTrackCursor.getString(0);
739                res.update(baseUri, values, where, wherearg);
740                mTrackCursor.moveToNext();
741            }
742            values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, currentplayidx);
743            wherearg[0] = mTrackCursor.getString(0);
744            res.update(baseUri, values, where, wherearg);
745        }
746    }
747
748    @Override
749    protected void onListItemClick(ListView l, View v, int position, long id)
750    {
751        if (mTrackCursor.getCount() == 0) {
752            return;
753        }
754        MusicUtils.playAll(this, mTrackCursor, position);
755    }
756
757    @Override
758    public boolean onCreateOptionsMenu(Menu menu) {
759        /* This activity is used for a number of different browsing modes, and the menu can
760         * be different for each of them:
761         * - all tracks, optionally restricted to an album, artist or playlist
762         * - the list of currently playing songs
763         */
764        super.onCreateOptionsMenu(menu);
765        if (mPlaylist == null) {
766            menu.add(0, PLAY_ALL, 0, R.string.play_all).setIcon(R.drawable.ic_menu_play_clip);
767        }
768        menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(R.drawable.ic_menu_music_library);
769        menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(R.drawable.ic_menu_playback)
770                .setVisible(MusicUtils.isMusicLoaded());
771        menu.add(0, SHUFFLE_ALL, 0, R.string.shuffle_all).setIcon(R.drawable.ic_menu_shuffle);
772        if (mPlaylist != null) {
773            menu.add(0, SAVE_AS_PLAYLIST, 0, R.string.save_as_playlist).setIcon(R.drawable.ic_menu_save);
774            if (mPlaylist.equals("nowplaying")) {
775                menu.add(0, CLEAR_PLAYLIST, 0, R.string.clear_playlist).setIcon(R.drawable.ic_menu_clear_playlist);
776            }
777        }
778        return true;
779    }
780
781    @Override
782    public boolean onOptionsItemSelected(MenuItem item) {
783        Intent intent;
784        Cursor cursor;
785        switch (item.getItemId()) {
786            case PLAY_ALL: {
787                MusicUtils.playAll(this, mTrackCursor);
788                return true;
789            }
790
791            case GOTO_START:
792                intent = new Intent();
793                intent.setClass(this, MusicBrowserActivity.class);
794                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
795                startActivity(intent);
796                return true;
797
798            case GOTO_PLAYBACK:
799                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
800                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
801                startActivity(intent);
802                return true;
803
804            case SHUFFLE_ALL:
805                // Should 'shuffle all' shuffle ALL, or only the tracks shown?
806                cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
807                        new String [] { MediaStore.Audio.Media._ID},
808                        MediaStore.Audio.Media.IS_MUSIC + "=1", null,
809                        MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
810                if (cursor != null) {
811                    MusicUtils.shuffleAll(this, cursor);
812                    cursor.close();
813                }
814                return true;
815
816            case SAVE_AS_PLAYLIST:
817                intent = new Intent();
818                intent.setClass(this, CreatePlaylist.class);
819                startActivityForResult(intent, SAVE_AS_PLAYLIST);
820                return true;
821
822            case CLEAR_PLAYLIST:
823                // We only clear the current playlist
824                MusicUtils.clearQueue();
825                return true;
826        }
827        return super.onOptionsItemSelected(item);
828    }
829
830    @Override
831    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
832        switch (requestCode) {
833            case SCAN_DONE:
834                if (resultCode == RESULT_CANCELED) {
835                    finish();
836                } else {
837                    getTrackCursor(mQueryHandler);
838                }
839                break;
840
841            case NEW_PLAYLIST:
842                if (resultCode == RESULT_OK) {
843                    Uri uri = intent.getData();
844                    if (uri != null) {
845                        int [] list = new int[] { (int) mSelectedId };
846                        MusicUtils.addToPlaylist(this, list, Integer.valueOf(uri.getLastPathSegment()));
847                    }
848                }
849                break;
850
851            case SAVE_AS_PLAYLIST:
852                if (resultCode == RESULT_OK) {
853                    Uri uri = intent.getData();
854                    if (uri != null) {
855                        int [] list = MusicUtils.getSongListForCursor(mTrackCursor);
856                        int plid = Integer.parseInt(uri.getLastPathSegment());
857                        MusicUtils.addToPlaylist(this, list, plid);
858                    }
859                }
860                break;
861        }
862    }
863
864    private Cursor getTrackCursor(AsyncQueryHandler async) {
865        Cursor ret = null;
866        mSortOrder = MediaStore.Audio.Media.TITLE_KEY;
867        StringBuilder where = new StringBuilder();
868        where.append(MediaStore.Audio.Media.TITLE + " != ''");
869
870        // Add in the filtering constraints
871        String [] keywords = null;
872        if (mFilterString != null) {
873            String [] searchWords = mFilterString.split(" ");
874            keywords = new String[searchWords.length];
875            Collator col = Collator.getInstance();
876            col.setStrength(Collator.PRIMARY);
877            for (int i = 0; i < searchWords.length; i++) {
878                keywords[i] = '%' + MediaStore.Audio.keyFor(searchWords[i]) + '%';
879            }
880            for (int i = 0; i < searchWords.length; i++) {
881                where.append(" AND ");
882                where.append(MediaStore.Audio.Media.ARTIST_KEY + "||");
883                where.append(MediaStore.Audio.Media.ALBUM_KEY + "||");
884                where.append(MediaStore.Audio.Media.TITLE_KEY + " LIKE ?");
885            }
886        }
887
888        if (mGenre != null) {
889            mSortOrder = MediaStore.Audio.Genres.Members.DEFAULT_SORT_ORDER;
890            if (async != null) {
891                async.startQuery(0, null,
892                        MediaStore.Audio.Genres.Members.getContentUri("external",
893                        Integer.valueOf(mGenre)),
894                        mCursorCols, where.toString(), keywords, mSortOrder);
895                ret = null;
896            } else {
897                ret = MusicUtils.query(this,
898                        MediaStore.Audio.Genres.Members.getContentUri("external", Integer.valueOf(mGenre)),
899                        mCursorCols, where.toString(), keywords, mSortOrder);
900            }
901        } else if (mPlaylist != null) {
902            if (mPlaylist.equals("nowplaying")) {
903                if (MusicUtils.sService != null) {
904                    ret = new NowPlayingCursor(MusicUtils.sService, mCursorCols);
905                    if (ret.getCount() == 0) {
906                        finish();
907                    }
908                } else {
909                    // Nothing is playing.
910                }
911            } else {
912                mSortOrder = MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER;
913                if (async != null) {
914                    async.startQuery(0, null,
915                            MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)),
916                            mPlaylistMemberCols, where.toString(), keywords, mSortOrder);
917                    ret = null;
918                } else {
919                    ret = MusicUtils.query(this,
920                            MediaStore.Audio.Playlists.Members.getContentUri("external", Long.valueOf(mPlaylist)),
921                            mPlaylistMemberCols, where.toString(), keywords, mSortOrder);
922                }
923            }
924        } else {
925            if (mAlbumId != null) {
926                where.append(" AND " + MediaStore.Audio.Media.ALBUM_ID + "=" + mAlbumId);
927                mSortOrder = MediaStore.Audio.Media.TRACK + ", " + mSortOrder;
928            }
929            where.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
930            if (async != null) {
931                async.startQuery(0, null,
932                        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
933                        mCursorCols, where.toString() , keywords, mSortOrder);
934                ret = null;
935            } else {
936                ret = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
937                        mCursorCols, where.toString() , keywords, mSortOrder);
938            }
939        }
940
941        // This special case is for the "nowplaying" cursor, which cannot be handled
942        // asynchronously, so we do some extra initialization here.
943        if (ret != null && async != null) {
944            init(ret);
945            setTitle();
946        }
947        return ret;
948    }
949
950    private class NowPlayingCursor extends AbstractCursor
951    {
952        public NowPlayingCursor(IMediaPlaybackService service, String [] cols)
953        {
954            mCols = cols;
955            mService  = service;
956            makeNowPlayingCursor();
957        }
958        private void makeNowPlayingCursor() {
959            mCurrentPlaylistCursor = null;
960            try {
961                mNowPlaying = mService.getQueue();
962            } catch (RemoteException ex) {
963                mNowPlaying = new int[0];
964            }
965            mSize = mNowPlaying.length;
966            if (mSize == 0) {
967                return;
968            }
969
970            StringBuilder where = new StringBuilder();
971            where.append(MediaStore.Audio.Media._ID + " IN (");
972            for (int i = 0; i < mSize; i++) {
973                where.append(mNowPlaying[i]);
974                if (i < mSize - 1) {
975                    where.append(",");
976                }
977            }
978            where.append(")");
979
980            mCurrentPlaylistCursor = MusicUtils.query(TrackBrowserActivity.this,
981                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
982                    mCols, where.toString(), null, MediaStore.Audio.Media._ID);
983
984            if (mCurrentPlaylistCursor == null) {
985                mSize = 0;
986                return;
987            }
988
989            int size = mCurrentPlaylistCursor.getCount();
990            mCursorIdxs = new int[size];
991            mCurrentPlaylistCursor.moveToFirst();
992            int colidx = mCurrentPlaylistCursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
993            for (int i = 0; i < size; i++) {
994                mCursorIdxs[i] = mCurrentPlaylistCursor.getInt(colidx);
995                mCurrentPlaylistCursor.moveToNext();
996            }
997            mCurrentPlaylistCursor.moveToFirst();
998            mCurPos = -1;
999
1000            // At this point we can verify the 'now playing' list we got
1001            // earlier to make sure that all the items in there still exist
1002            // in the database, and remove those that aren't. This way we
1003            // don't get any blank items in the list.
1004            try {
1005                int removed = 0;
1006                for (int i = mNowPlaying.length - 1; i >= 0; i--) {
1007                    int trackid = mNowPlaying[i];
1008                    int crsridx = Arrays.binarySearch(mCursorIdxs, trackid);
1009                    if (crsridx < 0) {
1010                        //Log.i("@@@@@", "item no longer exists in db: " + trackid);
1011                        removed += mService.removeTrack(trackid);
1012                    }
1013                }
1014                if (removed > 0) {
1015                    mNowPlaying = mService.getQueue();
1016                    mSize = mNowPlaying.length;
1017                    if (mSize == 0) {
1018                        mCursorIdxs = null;
1019                        return;
1020                    }
1021                }
1022            } catch (RemoteException ex) {
1023                mNowPlaying = new int[0];
1024            }
1025        }
1026
1027        @Override
1028        public int getCount()
1029        {
1030            return mSize;
1031        }
1032
1033        @Override
1034        public boolean onMove(int oldPosition, int newPosition)
1035        {
1036            if (oldPosition == newPosition)
1037                return true;
1038
1039            if (mNowPlaying == null || mCursorIdxs == null) {
1040                return false;
1041            }
1042
1043            // The cursor doesn't have any duplicates in it, and is not ordered
1044            // in queue-order, so we need to figure out where in the cursor we
1045            // should be.
1046
1047            int newid = mNowPlaying[newPosition];
1048            int crsridx = Arrays.binarySearch(mCursorIdxs, newid);
1049            mCurrentPlaylistCursor.moveToPosition(crsridx);
1050            mCurPos = newPosition;
1051
1052            return true;
1053        }
1054
1055        public boolean removeItem(int which)
1056        {
1057            try {
1058                if (mService.removeTracks(which, which) == 0) {
1059                    return false; // delete failed
1060                }
1061                int i = (int) which;
1062                mSize--;
1063                while (i < mSize) {
1064                    mNowPlaying[i] = mNowPlaying[i+1];
1065                    i++;
1066                }
1067                onMove(-1, (int) mCurPos);
1068            } catch (RemoteException ex) {
1069            }
1070            return true;
1071        }
1072
1073        public void moveItem(int from, int to) {
1074            try {
1075                mService.moveQueueItem(from, to);
1076                mNowPlaying = mService.getQueue();
1077                onMove(-1, mCurPos); // update the underlying cursor
1078            } catch (RemoteException ex) {
1079            }
1080        }
1081
1082        private void dump() {
1083            String where = "(";
1084            for (int i = 0; i < mSize; i++) {
1085                where += mNowPlaying[i];
1086                if (i < mSize - 1) {
1087                    where += ",";
1088                }
1089            }
1090            where += ")";
1091            Log.i("NowPlayingCursor: ", where);
1092        }
1093
1094        @Override
1095        public String getString(int column)
1096        {
1097            try {
1098                return mCurrentPlaylistCursor.getString(column);
1099            } catch (Exception ex) {
1100                onChange(true);
1101                return "";
1102            }
1103        }
1104
1105        @Override
1106        public short getShort(int column)
1107        {
1108            return mCurrentPlaylistCursor.getShort(column);
1109        }
1110
1111        @Override
1112        public int getInt(int column)
1113        {
1114            try {
1115                return mCurrentPlaylistCursor.getInt(column);
1116            } catch (Exception ex) {
1117                onChange(true);
1118                return 0;
1119            }
1120        }
1121
1122        @Override
1123        public long getLong(int column)
1124        {
1125            try {
1126                return mCurrentPlaylistCursor.getLong(column);
1127            } catch (Exception ex) {
1128                onChange(true);
1129                return 0;
1130            }
1131        }
1132
1133        @Override
1134        public float getFloat(int column)
1135        {
1136            return mCurrentPlaylistCursor.getFloat(column);
1137        }
1138
1139        @Override
1140        public double getDouble(int column)
1141        {
1142            return mCurrentPlaylistCursor.getDouble(column);
1143        }
1144
1145        @Override
1146        public boolean isNull(int column)
1147        {
1148            return mCurrentPlaylistCursor.isNull(column);
1149        }
1150
1151        @Override
1152        public String[] getColumnNames()
1153        {
1154            return mCols;
1155        }
1156
1157        @Override
1158        public void deactivate()
1159        {
1160            if (mCurrentPlaylistCursor != null)
1161                mCurrentPlaylistCursor.deactivate();
1162        }
1163
1164        @Override
1165        public boolean requery()
1166        {
1167            makeNowPlayingCursor();
1168            return true;
1169        }
1170
1171        private String [] mCols;
1172        private Cursor mCurrentPlaylistCursor;     // updated in onMove
1173        private int mSize;          // size of the queue
1174        private int[] mNowPlaying;
1175        private int[] mCursorIdxs;
1176        private int mCurPos;
1177        private IMediaPlaybackService mService;
1178    }
1179
1180    class TrackListAdapter extends SimpleCursorAdapter implements SectionIndexer {
1181        boolean mIsNowPlaying;
1182        boolean mDisableNowPlayingIndicator;
1183
1184        final int mTitleIdx;
1185        final int mArtistIdx;
1186        final int mAlbumIdx;
1187        final int mDurationIdx;
1188        int mAudioIdIdx;
1189
1190        private final StringBuilder mBuilder = new StringBuilder();
1191        private final String mUnknownArtist;
1192        private final String mUnknownAlbum;
1193
1194        private AlphabetIndexer mIndexer;
1195        private String mAlphabet;
1196
1197        class ViewHolder {
1198            TextView line1;
1199            TextView line2;
1200            TextView duration;
1201            ImageView play_indicator;
1202            CharArrayBuffer buffer1;
1203            char [] buffer2;
1204        }
1205
1206        TrackListAdapter(Context context, int layout, Cursor cursor, String[] from, int[] to,
1207                boolean isnowplaying, boolean disablenowplayingindicator) {
1208            super(context, layout, cursor, from, to);
1209            mIsNowPlaying = isnowplaying;
1210            mDisableNowPlayingIndicator = disablenowplayingindicator;
1211            mUnknownArtist = context.getString(R.string.unknown_artist_name);
1212            mUnknownAlbum = context.getString(R.string.unknown_album_name);
1213
1214            mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE);
1215            mArtistIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST);
1216            mAlbumIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM);
1217            mDurationIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION);
1218            try {
1219                mAudioIdIdx = cursor.getColumnIndexOrThrow(
1220                        MediaStore.Audio.Playlists.Members.AUDIO_ID);
1221            } catch (IllegalArgumentException ex) {
1222                mAudioIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID);
1223            }
1224            if (!mEditMode) {
1225                mAlphabet = context.getString(
1226                        com.android.internal.R.string.fast_scroll_alphabet);
1227
1228                mIndexer = new MusicAlphabetIndexer(cursor, mTitleIdx, mAlphabet);
1229            }
1230        }
1231
1232        @Override
1233        public View newView(Context context, Cursor cursor, ViewGroup parent) {
1234            View v = super.newView(context, cursor, parent);
1235            ImageView iv = (ImageView) v.findViewById(R.id.icon);
1236            if (mEditMode) {
1237                iv.setVisibility(View.VISIBLE);
1238                iv.setImageResource(R.drawable.ic_mp_move);
1239                ViewGroup.LayoutParams p = iv.getLayoutParams();
1240                p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
1241                p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
1242            } else {
1243                iv.setVisibility(View.GONE);
1244            }
1245
1246            ViewHolder vh = new ViewHolder();
1247            vh.line1 = (TextView) v.findViewById(R.id.line1);
1248            vh.line2 = (TextView) v.findViewById(R.id.line2);
1249            vh.duration = (TextView) v.findViewById(R.id.duration);
1250            vh.play_indicator = (ImageView) v.findViewById(R.id.play_indicator);
1251            vh.buffer1 = new CharArrayBuffer(100);
1252            vh.buffer2 = new char[200];
1253            v.setTag(vh);
1254            return v;
1255        }
1256
1257        @Override
1258        public void bindView(View view, Context context, Cursor cursor) {
1259
1260            ViewHolder vh = (ViewHolder) view.getTag();
1261
1262            cursor.copyStringToBuffer(mTitleIdx, vh.buffer1);
1263            vh.line1.setText(vh.buffer1.data, 0, vh.buffer1.sizeCopied);
1264
1265            int secs = cursor.getInt(mDurationIdx) / 1000;
1266            if (secs == 0) {
1267                vh.duration.setText("");
1268            } else {
1269                vh.duration.setText(MusicUtils.makeTimeString(context, secs));
1270            }
1271
1272            final StringBuilder builder = mBuilder;
1273            builder.delete(0, builder.length());
1274
1275            String name = cursor.getString(mAlbumIdx);
1276            if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
1277                builder.append(mUnknownAlbum);
1278            } else {
1279                builder.append(name);
1280            }
1281            builder.append('\n');
1282            name = cursor.getString(mArtistIdx);
1283            if (name == null || name.equals(MediaFile.UNKNOWN_STRING)) {
1284                builder.append(mUnknownArtist);
1285            } else {
1286                builder.append(name);
1287            }
1288            int len = builder.length();
1289            if (vh.buffer2.length < len) {
1290                vh.buffer2 = new char[len];
1291            }
1292            builder.getChars(0, len, vh.buffer2, 0);
1293            vh.line2.setText(vh.buffer2, 0, len);
1294
1295            ImageView iv = vh.play_indicator;
1296            int id = -1;
1297            if (MusicUtils.sService != null) {
1298                // TODO: IPC call on each bind??
1299                try {
1300                    if (mIsNowPlaying) {
1301                        id = MusicUtils.sService.getQueuePosition();
1302                    } else {
1303                        id = MusicUtils.sService.getAudioId();
1304                    }
1305                } catch (RemoteException ex) {
1306                }
1307            }
1308
1309            // Determining whether and where to show the "now playing indicator
1310            // is tricky, because we don't actually keep track of where the songs
1311            // in the current playlist came from after they've started playing.
1312            //
1313            // If the "current playlists" is shown, then we can simply match by position,
1314            // otherwise, we need to match by id. Match-by-id gets a little weird if
1315            // a song appears in a playlist more than once, and you're in edit-playlist
1316            // mode. In that case, both items will have the "now playing" indicator.
1317            // For this reason, we don't show the play indicator at all when in edit
1318            // playlist mode (except when you're viewing the "current playlists",
1319            // which is not really a playlist)
1320            if ( (mIsNowPlaying && cursor.getPosition() == id) ||
1321                 (!mIsNowPlaying && !mDisableNowPlayingIndicator && cursor.getInt(mAudioIdIdx) == id)) {
1322                iv.setImageResource(R.drawable.indicator_ic_mp_playing_list);
1323                iv.setVisibility(View.VISIBLE);
1324            } else {
1325                iv.setVisibility(View.GONE);
1326            }
1327        }
1328
1329        @Override
1330        public void changeCursor(Cursor cursor) {
1331            super.changeCursor(cursor);
1332            mTrackCursor = cursor;
1333            if (mIndexer != null) {
1334                mIndexer.setCursor(cursor);
1335            }
1336        }
1337
1338        @Override
1339        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
1340            mFilterString = constraint.toString();
1341            return getTrackCursor(null);
1342        }
1343
1344        // SectionIndexer methods
1345
1346        public Object[] getSections() {
1347            if (mIndexer != null) {
1348                return mIndexer.getSections();
1349            } else {
1350                return null;
1351            }
1352        }
1353
1354        public int getPositionForSection(int section) {
1355            int pos = mIndexer.getPositionForSection(section);
1356            return pos;
1357        }
1358
1359        public int getSectionForPosition(int position) {
1360            return 0;
1361        }
1362    }
1363
1364    private ListView mTrackList;
1365    private Cursor mTrackCursor;
1366    private String mAlbumId;
1367    private String mArtistId;
1368    private String mPlaylist;
1369    private String mGenre;
1370    private String mSortOrder;
1371    private int mSelectedPosition;
1372    private long mSelectedId;
1373}
1374
1375