OSDN Git Service

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