PlaylistBrowserActivity.java revision d57d69855619bd57ef2205e1c5089fa8e11b6cb1
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            setTitle();
204            getPlaylistCursor(mAdapter.getQueryHandler(), null);
205        }
206    };
207    public void init(Cursor cursor) {
208
209        mAdapter.changeCursor(cursor);
210
211        if (mPlaylistCursor == null) {
212            MusicUtils.displayDatabaseError(this);
213            closeContextMenu();
214            mReScanHandler.sendEmptyMessageDelayed(0, 1000);
215            return;
216        }
217
218        MusicUtils.hideDatabaseError(this);
219        setTitle();
220    }
221
222    private void setTitle() {
223        setTitle(R.string.playlists_title);
224    }
225
226    @Override
227    public boolean onCreateOptionsMenu(Menu menu) {
228        if (!mCreateShortcut) {
229            menu.add(0, GOTO_START, 0, R.string.goto_start).setIcon(
230                    R.drawable.ic_menu_music_library);
231            menu.add(0, GOTO_PLAYBACK, 0, R.string.goto_playback).setIcon(
232                    R.drawable.ic_menu_playback).setVisible(MusicUtils.isMusicLoaded());
233        }
234        return super.onCreateOptionsMenu(menu);
235    }
236
237    @Override
238    public boolean onOptionsItemSelected(MenuItem item) {
239        Intent intent;
240        switch (item.getItemId()) {
241            case GOTO_START:
242                intent = new Intent();
243                intent.setClass(this, MusicBrowserActivity.class);
244                intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
245                startActivity(intent);
246                return true;
247
248            case GOTO_PLAYBACK:
249                intent = new Intent("com.android.music.PLAYBACK_VIEWER");
250                startActivity(intent);
251                return true;
252        }
253        return super.onOptionsItemSelected(item);
254    }
255
256    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
257        if (mCreateShortcut) {
258            return;
259        }
260
261        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
262
263        menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
264
265        if (mi.id >= 0 /*|| mi.id == PODCASTS_PLAYLIST*/) {
266            menu.add(0, DELETE_PLAYLIST, 0, R.string.delete_playlist_menu);
267        }
268
269        if (mi.id == RECENTLY_ADDED_PLAYLIST) {
270            menu.add(0, EDIT_PLAYLIST, 0, R.string.edit_playlist_menu);
271        }
272
273        if (mi.id >= 0) {
274            menu.add(0, RENAME_PLAYLIST, 0, R.string.rename_playlist_menu);
275        }
276
277        mPlaylistCursor.moveToPosition(mi.position);
278        menu.setHeaderTitle(mPlaylistCursor.getString(mPlaylistCursor.getColumnIndexOrThrow(
279                MediaStore.Audio.Playlists.NAME)));
280    }
281
282    @Override
283    public boolean onContextItemSelected(MenuItem item) {
284        AdapterContextMenuInfo mi = (AdapterContextMenuInfo) item.getMenuInfo();
285        switch (item.getItemId()) {
286            case PLAY_SELECTION:
287                if (mi.id == RECENTLY_ADDED_PLAYLIST) {
288                    playRecentlyAdded();
289                } else if (mi.id == PODCASTS_PLAYLIST) {
290                    playPodcasts();
291                } else {
292                    MusicUtils.playPlaylist(this, mi.id);
293                }
294                break;
295            case DELETE_PLAYLIST:
296                Uri uri = ContentUris.withAppendedId(
297                        MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, mi.id);
298                getContentResolver().delete(uri, null, null);
299                Toast.makeText(this, R.string.playlist_deleted_message, Toast.LENGTH_SHORT).show();
300                if (mPlaylistCursor.getCount() == 0) {
301                    setTitle(R.string.no_playlists_title);
302                }
303                break;
304            case EDIT_PLAYLIST:
305                if (mi.id == RECENTLY_ADDED_PLAYLIST) {
306                    Intent intent = new Intent();
307                    intent.setClass(this, WeekSelector.class);
308                    startActivityForResult(intent, CHANGE_WEEKS);
309                    return true;
310                } else {
311                    Log.e(TAG, "should not be here");
312                }
313                break;
314            case RENAME_PLAYLIST:
315                Intent intent = new Intent();
316                intent.setClass(this, RenamePlaylist.class);
317                intent.putExtra("rename", mi.id);
318                startActivityForResult(intent, RENAME_PLAYLIST);
319                break;
320        }
321        return true;
322    }
323
324    @Override
325    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
326        switch (requestCode) {
327            case SCAN_DONE:
328                if (resultCode == RESULT_CANCELED) {
329                    finish();
330                } else {
331                    getPlaylistCursor(mAdapter.getQueryHandler(), null);
332                }
333                break;
334        }
335    }
336
337    @Override
338    protected void onListItemClick(ListView l, View v, int position, long id)
339    {
340        if (mCreateShortcut) {
341            final Intent shortcut = new Intent();
342            shortcut.setAction(Intent.ACTION_VIEW);
343            shortcut.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/playlist");
344            shortcut.putExtra("playlist", String.valueOf(id));
345
346            final Intent intent = new Intent();
347            intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);
348            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, ((TextView) v.findViewById(R.id.line1)).getText());
349            intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(
350                    this, R.drawable.app_music));
351
352            setResult(RESULT_OK, intent);
353            finish();
354            return;
355        }
356        if (id == RECENTLY_ADDED_PLAYLIST) {
357            Intent intent = new Intent(Intent.ACTION_PICK);
358            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
359            intent.putExtra("playlist", "recentlyadded");
360            startActivity(intent);
361        } else if (id == PODCASTS_PLAYLIST) {
362            Intent intent = new Intent(Intent.ACTION_PICK);
363            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
364            intent.putExtra("playlist", "podcasts");
365            startActivity(intent);
366        } else {
367            Intent intent = new Intent(Intent.ACTION_EDIT);
368            intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
369            intent.putExtra("playlist", Long.valueOf(id).toString());
370            startActivity(intent);
371        }
372    }
373
374    private void playRecentlyAdded() {
375        // do a query for all songs added in the last X weeks
376        int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
377        final String[] ccols = new String[] { MediaStore.Audio.Media._ID};
378        String where = MediaStore.MediaColumns.DATE_ADDED + ">" + (System.currentTimeMillis() / 1000 - X);
379        Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
380                ccols, where, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
381
382        if (cursor == null) {
383            // Todo: show a message
384            return;
385        }
386        try {
387            int len = cursor.getCount();
388            int [] list = new int[len];
389            for (int i = 0; i < len; i++) {
390                cursor.moveToNext();
391                list[i] = cursor.getInt(0);
392            }
393            MusicUtils.playAll(this, list, 0);
394        } catch (SQLiteException ex) {
395        } finally {
396            cursor.close();
397        }
398    }
399
400    private void playPodcasts() {
401        // do a query for all files that are podcasts
402        final String[] ccols = new String[] { MediaStore.Audio.Media._ID};
403        Cursor cursor = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
404                ccols, MediaStore.Audio.Media.IS_PODCAST + "=1",
405                null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
406
407        if (cursor == null) {
408            // Todo: show a message
409            return;
410        }
411        try {
412            int len = cursor.getCount();
413            int [] list = new int[len];
414            for (int i = 0; i < len; i++) {
415                cursor.moveToNext();
416                list[i] = cursor.getInt(0);
417            }
418            MusicUtils.playAll(this, list, 0);
419        } catch (SQLiteException ex) {
420        } finally {
421            cursor.close();
422        }
423    }
424
425
426    String[] mCols = new String[] {
427            MediaStore.Audio.Playlists._ID,
428            MediaStore.Audio.Playlists.NAME
429    };
430
431    private Cursor getPlaylistCursor(AsyncQueryHandler async, String filterstring) {
432
433        StringBuilder where = new StringBuilder();
434        where.append(MediaStore.Audio.Playlists.NAME + " != ''");
435
436        // Add in the filtering constraints
437        String [] keywords = null;
438        if (filterstring != null) {
439            String [] searchWords = filterstring.split(" ");
440            keywords = new String[searchWords.length];
441            Collator col = Collator.getInstance();
442            col.setStrength(Collator.PRIMARY);
443            for (int i = 0; i < searchWords.length; i++) {
444                keywords[i] = '%' + searchWords[i] + '%';
445            }
446            for (int i = 0; i < searchWords.length; i++) {
447                where.append(" AND ");
448                where.append(MediaStore.Audio.Playlists.NAME + " LIKE ?");
449            }
450        }
451
452        String whereclause = where.toString();
453
454
455        if (async != null) {
456            async.startQuery(0, null, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
457                    mCols, whereclause, keywords, MediaStore.Audio.Playlists.NAME);
458            return null;
459        }
460        Cursor c = null;
461        c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
462                mCols, whereclause, keywords, MediaStore.Audio.Playlists.NAME);
463
464        return mergedCursor(c);
465    }
466
467    private Cursor mergedCursor(Cursor c) {
468        if (c == null) {
469            return null;
470        }
471        if (c instanceof MergeCursor) {
472            // this shouldn't happen, but fail gracefully
473            Log.d("PlaylistBrowserActivity", "Already wrapped");
474            return c;
475        }
476        ArrayList<ArrayList> autoplaylists = new ArrayList<ArrayList>();
477        if (mCreateShortcut) {
478            ArrayList<Object> all = new ArrayList<Object>(2);
479            all.add(ALL_SONGS_PLAYLIST);
480            all.add(getString(R.string.play_all));
481            autoplaylists.add(all);
482        }
483        ArrayList<Object> recent = new ArrayList<Object>(2);
484        recent.add(RECENTLY_ADDED_PLAYLIST);
485        recent.add(getString(R.string.recentlyadded));
486        autoplaylists.add(recent);
487
488        // check if there are any podcasts
489        Cursor counter = MusicUtils.query(this, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
490                new String[] {"count(*)"}, "is_podcast=1", null, null);
491        if (counter != null) {
492            counter.moveToFirst();
493            int numpodcasts = counter.getInt(0);
494            counter.close();
495            if (numpodcasts > 0) {
496                ArrayList<Object> podcasts = new ArrayList<Object>(2);
497                podcasts.add(PODCASTS_PLAYLIST);
498                podcasts.add(getString(R.string.podcasts_listitem));
499                autoplaylists.add(podcasts);
500            }
501        }
502
503        ArrayListCursor autoplaylistscursor = new ArrayListCursor(mCols, autoplaylists);
504
505        Cursor cc = new MergeCursor(new Cursor [] {autoplaylistscursor, c});
506        return cc;
507    }
508
509    static class PlaylistListAdapter extends SimpleCursorAdapter {
510        int mTitleIdx;
511        private PlaylistBrowserActivity mActivity = null;
512        private AsyncQueryHandler mQueryHandler;
513
514        class QueryHandler extends AsyncQueryHandler {
515            QueryHandler(ContentResolver res) {
516                super(res);
517            }
518
519            @Override
520            protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
521                //Log.i("@@@", "query complete: " + cursor.getCount() + "   " + mActivity);
522                if (cursor != null) {
523                    cursor = mActivity.mergedCursor(cursor);
524                }
525                mActivity.init(cursor);
526            }
527        }
528
529        PlaylistListAdapter(Context context, PlaylistBrowserActivity currentactivity,
530                int layout, Cursor cursor, String[] from, int[] to) {
531            super(context, layout, cursor, from, to);
532            mActivity = currentactivity;
533            getColumnIndices(cursor);
534            mQueryHandler = new QueryHandler(context.getContentResolver());
535        }
536        private void getColumnIndices(Cursor cursor) {
537            if (cursor != null) {
538                mTitleIdx = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME);
539            }
540        }
541
542        public void setActivity(PlaylistBrowserActivity newactivity) {
543            mActivity = newactivity;
544        }
545
546        public AsyncQueryHandler getQueryHandler() {
547            return mQueryHandler;
548        }
549
550        @Override
551        public void bindView(View view, Context context, Cursor cursor) {
552
553            TextView tv = (TextView) view.findViewById(R.id.line1);
554
555            String name = cursor.getString(mTitleIdx);
556            tv.setText(name);
557
558            ImageView iv = (ImageView) view.findViewById(R.id.icon);
559            iv.setImageResource(R.drawable.ic_mp_playlist_list);
560            ViewGroup.LayoutParams p = iv.getLayoutParams();
561            p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
562            p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
563
564            iv = (ImageView) view.findViewById(R.id.play_indicator);
565            iv.setVisibility(View.GONE);
566
567            view.findViewById(R.id.line2).setVisibility(View.GONE);
568        }
569
570        @Override
571        public void changeCursor(Cursor cursor) {
572            if (cursor != mActivity.mPlaylistCursor) {
573                mActivity.mPlaylistCursor = cursor;
574                super.changeCursor(cursor);
575                getColumnIndices(cursor);
576            }
577        }
578
579        @Override
580        public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
581            return mActivity.getPlaylistCursor(null, constraint.toString());
582        }
583    }
584
585    private Cursor mPlaylistCursor;
586}
587
588