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