OSDN Git Service

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