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