PlaylistBrowserActivity.java revision 792a2206a4f05f6bd13fce902d3663892d2947af
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 java.text.Collator;
20import java.util.ArrayList;
21
22import android.app.ListActivity;
23import android.content.AsyncQueryHandler;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.ContentResolver;
27import android.content.ContentUris;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.ServiceConnection;
32
33import com.android.internal.database.ArrayListCursor;
34
35import android.database.Cursor;
36import android.database.MergeCursor;
37import android.database.sqlite.SQLiteException;
38import android.media.AudioManager;
39import android.net.Uri;
40import android.os.Bundle;
41import android.os.Handler;
42import android.os.IBinder;
43import android.os.Message;
44import android.provider.MediaStore;
45import android.util.Log;
46import android.view.ContextMenu;
47import android.view.Menu;
48import android.view.MenuItem;
49import android.view.View;
50import android.view.ViewGroup;
51import android.view.Window;
52import android.view.ContextMenu.ContextMenuInfo;
53import android.widget.ImageView;
54import android.widget.ListView;
55import android.widget.SimpleCursorAdapter;
56import android.widget.TextView;
57import android.widget.Toast;
58import android.widget.AdapterView.AdapterContextMenuInfo;
59
60public class PlaylistBrowserActivity extends ListActivity
61    implements View.OnCreateContextMenuListener, MusicUtils.Defs
62{
63    private static final String TAG = "PlaylistBrowserActivity";
64    private static final int DELETE_PLAYLIST = CHILD_MENU_BASE + 1;
65    private static final int EDIT_PLAYLIST = CHILD_MENU_BASE + 2;
66    private static final int RENAME_PLAYLIST = CHILD_MENU_BASE + 3;
67    private static final int CHANGE_WEEKS = CHILD_MENU_BASE + 4;
68    private static final long RECENTLY_ADDED_PLAYLIST = -1;
69    private static final long ALL_SONGS_PLAYLIST = -2;
70    private static final long PODCASTS_PLAYLIST = -3;
71    private PlaylistListAdapter mAdapter;
72    boolean mAdapterSent;
73
74    private boolean mCreateShortcut;
75
76    public PlaylistBrowserActivity()
77    {
78    }
79
80    /** Called when the activity is first created. */
81    @Override
82    public void onCreate(Bundle icicle)
83    {
84        super.onCreate(icicle);
85
86        final Intent intent = getIntent();
87        final String action = intent.getAction();
88        if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
89            mCreateShortcut = true;
90        }
91
92        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
93        setVolumeControlStream(AudioManager.STREAM_MUSIC);
94        MusicUtils.bindToService(this, new ServiceConnection() {
95            public void onServiceConnected(ComponentName classname, IBinder obj) {
96                if (Intent.ACTION_VIEW.equals(action)) {
97                    long id = Long.parseLong(intent.getExtras().getString("playlist"));
98                    if (id == RECENTLY_ADDED_PLAYLIST) {
99                        playRecentlyAdded();
100                    } else if (id == PODCASTS_PLAYLIST) {
101                        playPodcasts();
102                    } else if (id == ALL_SONGS_PLAYLIST) {
103                        int [] list = MusicUtils.getAllSongs(PlaylistBrowserActivity.this);
104                        if (list != null) {
105                            MusicUtils.playAll(PlaylistBrowserActivity.this, list, 0);
106                        }
107                    } else {
108                        MusicUtils.playPlaylist(PlaylistBrowserActivity.this, id);
109                    }
110                    finish();
111                }
112            }
113
114            public void onServiceDisconnected(ComponentName classname) {
115            }
116
117        });
118        IntentFilter f = new IntentFilter();
119        f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
120        f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
121        f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
122        f.addDataScheme("file");
123        registerReceiver(mScanListener, f);
124
125        setContentView(R.layout.media_picker_activity);
126        ListView lv = getListView();
127        lv.setOnCreateContextMenuListener(this);
128        lv.setTextFilterEnabled(true);
129
130        mAdapter = (PlaylistListAdapter) getLastNonConfigurationInstance();
131        if (mAdapter == null) {
132            //Log.i("@@@", "starting query");
133            mAdapter = new PlaylistListAdapter(
134                    getApplication(),
135                    this,
136                    R.layout.track_list_item,
137                    mPlaylistCursor,
138                    new String[] { MediaStore.Audio.Playlists.NAME},
139                    new int[] { android.R.id.text1 });
140            setListAdapter(mAdapter);
141            setTitle(R.string.working_playlists);
142            getPlaylistCursor(mAdapter.getQueryHandler(), null);
143        } else {
144            mAdapter.setActivity(this);
145            setListAdapter(mAdapter);
146            mPlaylistCursor = mAdapter.getCursor();
147            // If mPlaylistCursor is null, this can be because it doesn't have
148            // a cursor yet (because the initial query that sets its cursor
149            // is still in progress), or because the query failed.
150            // In order to not flash the error dialog at the user for the
151            // first case, simply retry the query when the cursor is null.
152            // Worst case, we end up doing the same query twice.
153            if (mPlaylistCursor != null) {
154                init(mPlaylistCursor);
155            } else {
156                setTitle(R.string.working_playlists);
157                getPlaylistCursor(mAdapter.getQueryHandler(), null);
158            }
159        }
160    }
161
162    @Override
163    public Object onRetainNonConfigurationInstance() {
164        PlaylistListAdapter a = mAdapter;
165        mAdapterSent = true;
166        return a;
167    }
168
169    @Override
170    public void onDestroy() {
171        MusicUtils.unbindFromService(this);
172        if (!mAdapterSent) {
173            Cursor c = mAdapter.getCursor();
174            if (c != null) {
175                c.close();
176            }
177        }
178        unregisterReceiver(mScanListener);
179        super.onDestroy();
180    }
181
182    @Override
183    public void onResume() {
184        super.onResume();
185
186        MusicUtils.setSpinnerState(this);
187    }
188    @Override
189    public void onPause() {
190        mReScanHandler.removeCallbacksAndMessages(null);
191        super.onPause();
192    }
193    private BroadcastReceiver mScanListener = new BroadcastReceiver() {
194        @Override
195        public void onReceive(Context context, Intent intent) {
196            MusicUtils.setSpinnerState(PlaylistBrowserActivity.this);
197            mReScanHandler.sendEmptyMessage(0);
198        }
199    };
200
201    private Handler mReScanHandler = new Handler() {
202        public void handleMessage(Message msg) {
203            getPlaylistCursor(mAdapter.getQueryHandler(), null);
204        }
205    };
206    public void init(Cursor cursor) {
207
208        mAdapter.changeCursor(cursor);
209
210        if (mPlaylistCursor == null) {
211            MusicUtils.displayDatabaseError(this);
212            closeContextMenu();
213            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
214            return;
215        }
216
217        MusicUtils.hideDatabaseError(this);
218        setTitle();
219    }
220
221    private void setTitle() {
222        setTitle(R.string.playlists_title);
223    }
224
225    @Override
226    public boolean onCreateOptionsMenu(Menu menu) {
227        if (!mCreateShortcut) {
228            menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(
229                    R.drawable.ic_menu_music_library);
230            menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(
231                    R.drawable.ic_menu_playback).setVisible(MusicUtils.isMusicLoaded());
232        }
233        return super.onCreateOptionsMenu(menu);
234    }
235
236    @Override
237    public boolean onOptionsItemSelected(MenuItem item) {
238        Intent intent;
239        switch (item.getItemId()) {
240            case GOTO_START:
241                intent = new Intent();
242                intent.setClass(this, MusicBrowserActivity.class);
243                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
244                startActivity(intent);
245                return true;
246
247            case GOTO_PLAYBACK:
248                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
249                startActivity(intent);
250                return true;
251        }
252        return super.onOptionsItemSelected(item);
253    }
254
255    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
256        if (mCreateShortcut) {
257            return;
258        }
259
260        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
261
262        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
263
264        if (mi.id >= 0 /*|| mi.id == PODCASTS_PLAYLIST*/) {
265            menu.add(0, DELETE_PLAYLIST, 0, R.string.delete_playlist_menu);
266        }
267
268        if (mi.id == RECENTLY_ADDED_PLAYLIST) {
269            menu.add(0, EDIT_PLAYLIST, 0, R.string.edit_playlist_menu);
270        }
271
272        if (mi.id >= 0) {
273            menu.add(0, RENAME_PLAYLIST, 0, R.string.rename_playlist_menu);
274        }
275
276        mPlaylistCursor.moveToPosition(mi.position);
277        menu.setHeaderTitle(mPlaylistCursor.getString(mPlaylistCursor.getColumnIndexOrThrow(
278                MediaStore.Audio.Playlists.NAME)));
279    }
280
281    @Override
282    public boolean onContextItemSelected(MenuItem item) {
283        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) item.getMenuInfo();
284        switch (item.getItemId()) {
285            case PLAY_SELECTION:
286                if (mi.id == RECENTLY_ADDED_PLAYLIST) {
287                    playRecentlyAdded();
288                } else if (mi.id == PODCASTS_PLAYLIST) {
289                    playPodcasts();
290                } else {
291                    MusicUtils.playPlaylist(this, mi.id);
292                }
293                break;
294            case DELETE_PLAYLIST:
295                Uri uri = ContentUris.withAppendedId(
296                        MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, mi.id);
297                getContentResolver().delete(uri, null, null);
298                Toast.makeText(this, R.string.playlist_deleted_message, Toast.LENGTH_SHORT).show();
299                if (mPlaylistCursor.getCount() == 0) {
300                    setTitle(R.string.no_playlists_title);
301                }
302                break;
303            case EDIT_PLAYLIST:
304                if (mi.id == RECENTLY_ADDED_PLAYLIST) {
305                    Intent intent = new Intent();
306                    intent.setClass(this, WeekSelector.class);
307                    startActivityForResult(intent, CHANGE_WEEKS);
308                    return true;
309                } else {
310                    Log.e(TAG, "should not be here");
311                }
312                break;
313            case RENAME_PLAYLIST:
314                Intent intent = new Intent();
315                intent.setClass(this, RenamePlaylist.class);
316                intent.putExtra("rename", mi.id);
317                startActivityForResult(intent, RENAME_PLAYLIST);
318                break;
319        }
320        return true;
321    }
322
323    @Override
324    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
325        switch (requestCode) {
326            case SCAN_DONE:
327                if (resultCode == RESULT_CANCELED) {
328                    finish();
329                } else {
330                    getPlaylistCursor(mAdapter.getQueryHandler(), null);
331                }
332                break;
333        }
334    }
335
336    @Override
337    protected void onListItemClick(ListView l, View v, int position, long id)
338    {
339        if (mCreateShortcut) {
340            final Intent shortcut = new Intent();
341            shortcut.setAction(Intent.ACTION_VIEW);
342            shortcut.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/playlist");
343            shortcut.putExtra("playlist", String.valueOf(id));
344
345            final Intent intent = new Intent();
346            intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);
347            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, ((TextView) v.findViewById(R.id.line1)).getText());
348            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(
349                    this, R.drawable.app_music));
350
351            setResult(RESULT_OK, intent);
352            finish();
353            return;
354        }
355        if (id == RECENTLY_ADDED_PLAYLIST) {
356            Intent intent = new Intent(Intent.ACTION_PICK);
357            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
358            intent.putExtra("playlist", "recentlyadded");
359            startActivity(intent);
360        } else if (id == PODCASTS_PLAYLIST) {
361            Intent intent = new Intent(Intent.ACTION_PICK);
362            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
363            intent.putExtra("playlist", "podcasts");
364            startActivity(intent);
365        } else {
366            Intent intent = new Intent(Intent.ACTION_EDIT);
367            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
368            intent.putExtra("playlist", Long.valueOf(id).toString());
369            startActivity(intent);
370        }
371    }
372
373    private void playRecentlyAdded() {
374        // do a query for all songs added in the last X weeks
375        int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
376        final String[] ccols = new String[] { MediaStore.Audio.Media._ID};
377        String where = MediaStore.MediaColumns.DATE_ADDED + ">" + (System.currentTimeMillis() / 1000 - X);
378        Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
379                ccols, where, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
380
381        if (cursor == null) {
382            // Todo: show a message
383            return;
384        }
385        try {
386            int len = cursor.getCount();
387            int [] list = new int[len];
388            for (int i = 0; i < len; i++) {
389                cursor.moveToNext();
390                list[i] = cursor.getInt(0);
391            }
392            MusicUtils.playAll(this, list, 0);
393        } catch (SQLiteException ex) {
394        } finally {
395            cursor.close();
396        }
397    }
398
399    private void playPodcasts() {
400        // do a query for all files that are podcasts
401        final String[] ccols = new String[] { MediaStore.Audio.Media._ID};
402        Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
403                ccols, MediaStore.Audio.Media.IS_PODCAST + "=1",
404                null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
405
406        if (cursor == null) {
407            // Todo: show a message
408            return;
409        }
410        try {
411            int len = cursor.getCount();
412            int [] list = new int[len];
413            for (int i = 0; i < len; i++) {
414                cursor.moveToNext();
415                list[i] = cursor.getInt(0);
416            }
417            MusicUtils.playAll(this, list, 0);
418        } catch (SQLiteException ex) {
419        } finally {
420            cursor.close();
421        }
422    }
423
424
425    String[] mCols = new String[] {
426            MediaStore.Audio.Playlists._ID,
427            MediaStore.Audio.Playlists.NAME
428    };
429
430    private Cursor getPlaylistCursor(AsyncQueryHandler async, String filterstring) {
431
432        StringBuilder where = new StringBuilder();
433        where.append(MediaStore.Audio.Playlists.NAME + " != ''");
434
435        // Add in the filtering constraints
436        String [] keywords = null;
437        if (filterstring != null) {
438            String [] searchWords = filterstring.split(" ");
439            keywords = new String[searchWords.length];
440            Collator col = Collator.getInstance();
441            col.setStrength(Collator.PRIMARY);
442            for (int i = 0; i < searchWords.length; i++) {
443                keywords[i] = '%' + searchWords[i] + '%';
444            }
445            for (int i = 0; i < searchWords.length; i++) {
446                where.append(" AND ");
447                where.append(MediaStore.Audio.Playlists.NAME + " LIKE ?");
448            }
449        }
450
451        String whereclause = where.toString();
452
453
454        if (async != null) {
455            async.startQuery(0, null, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
456                    mCols, whereclause, keywords, MediaStore.Audio.Playlists.NAME);
457            return null;
458        }
459        Cursor c = null;
460        c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
461                mCols, whereclause, keywords, MediaStore.Audio.Playlists.NAME);
462
463        return mergedCursor(c);
464    }
465
466    private Cursor mergedCursor(Cursor c) {
467        if (c == null) {
468            return null;
469        }
470        if (c instanceof MergeCursor) {
471            // this shouldn't happen, but fail gracefully
472            Log.d("PlaylistBrowserActivity", "Already wrapped");
473            return c;
474        }
475        ArrayList<ArrayList> autoplaylists = new ArrayList<ArrayList>();
476        if (mCreateShortcut) {
477            ArrayList<Object> all = new ArrayList<Object>(2);
478            all.add(ALL_SONGS_PLAYLIST);
479            all.add(getString(R.string.play_all));
480            autoplaylists.add(all);
481        }
482        ArrayList<Object> recent = new ArrayList<Object>(2);
483        recent.add(RECENTLY_ADDED_PLAYLIST);
484        recent.add(getString(R.string.recentlyadded));
485        autoplaylists.add(recent);
486
487        // check if there are any podcasts
488        Cursor counter = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
489                new String[] {"count(*)"}, "is_podcast=1", null, null);
490        if (counter != null) {
491            counter.moveToFirst();
492            int numpodcasts = counter.getInt(0);
493            counter.close();
494            if (numpodcasts > 0) {
495                ArrayList<Object> podcasts = new ArrayList<Object>(2);
496                podcasts.add(PODCASTS_PLAYLIST);
497                podcasts.add(getString(R.string.podcasts_listitem));
498                autoplaylists.add(podcasts);
499            }
500        }
501
502        ArrayListCursor autoplaylistscursor = new ArrayListCursor(mCols, autoplaylists);
503
504        Cursor cc = new MergeCursor(new Cursor [] {autoplaylistscursor, c});
505        return cc;
506    }
507
508    static class PlaylistListAdapter extends SimpleCursorAdapter {
509        int mTitleIdx;
510        int mIdIdx;
511        private PlaylistBrowserActivity mActivity = null;
512        private AsyncQueryHandler mQueryHandler;
513        private String mConstraint = null;
514        private boolean mConstraintIsValid = false;
515
516        class QueryHandler extends AsyncQueryHandler {
517            QueryHandler(ContentResolver res) {
518                super(res);
519            }
520
521            @Override
522            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
523                //Log.i("@@@", "query complete: " + cursor.getCount() + "   " + mActivity);
524                if (cursor != null) {
525                    cursor = mActivity.mergedCursor(cursor);
526                }
527                mActivity.init(cursor);
528            }
529        }
530
531        PlaylistListAdapter(Context context, PlaylistBrowserActivity currentactivity,
532                int layout, Cursor cursor, String[] from, int[] to) {
533            super(context, layout, cursor, from, to);
534            mActivity = currentactivity;
535            getColumnIndices(cursor);
536            mQueryHandler = new QueryHandler(context.getContentResolver());
537        }
538        private void getColumnIndices(Cursor cursor) {
539            if (cursor != null) {
540                mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME);
541                mIdIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists._ID);
542            }
543        }
544
545        public void setActivity(PlaylistBrowserActivity newactivity) {
546            mActivity = newactivity;
547        }
548
549        public AsyncQueryHandler getQueryHandler() {
550            return mQueryHandler;
551        }
552
553        @Override
554        public void bindView(View view, Context context, Cursor cursor) {
555
556            TextView tv = (TextView) view.findViewById(R.id.line1);
557
558            String name = cursor.getString(mTitleIdx);
559            tv.setText(name);
560
561            long id = cursor.getLong(mIdIdx);
562
563            ImageView iv = (ImageView) view.findViewById(R.id.icon);
564            if (id == RECENTLY_ADDED_PLAYLIST) {
565                iv.setImageResource(R.drawable.ic_mp_playlist_recently_added_list);
566            } else {
567                iv.setImageResource(R.drawable.ic_mp_playlist_list);
568            }
569            ViewGroup.LayoutParams p = iv.getLayoutParams();
570            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
571            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
572
573            iv = (ImageView) view.findViewById(R.id.play_indicator);
574            iv.setVisibility(View.GONE);
575
576            view.findViewById(R.id.line2).setVisibility(View.GONE);
577        }
578
579        @Override
580        public void changeCursor(Cursor cursor) {
581            if (cursor != mActivity.mPlaylistCursor) {
582                mActivity.mPlaylistCursor = cursor;
583                super.changeCursor(cursor);
584                getColumnIndices(cursor);
585            }
586        }
587
588        @Override
589        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
590            String s = constraint.toString();
591            if (mConstraintIsValid && (
592                    (s == null && mConstraint == null) ||
593                    (s != null && s.equals(mConstraint)))) {
594                return getCursor();
595            }
596            Cursor c = mActivity.getPlaylistCursor(null, s);
597            mConstraint = s;
598            mConstraintIsValid = true;
599            return c;
600        }
601    }
602
603    private Cursor mPlaylistCursor;
604}
605
606