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