OSDN Git Service

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