2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.music;
19 import java.text.Collator;
20 import java.util.ArrayList;
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;
33 import com.android.internal.database.ArrayListCursor;
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;
60 public class PlaylistBrowserActivity extends ListActivity
61 implements View.OnCreateContextMenuListener, MusicUtils.Defs
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;
74 private boolean mCreateShortcut;
76 public PlaylistBrowserActivity()
80 /** Called when the activity is first created. */
82 public void onCreate(Bundle icicle)
84 super.onCreate(icicle);
86 final Intent intent = getIntent();
87 final String action = intent.getAction();
88 if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
89 mCreateShortcut = true;
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) {
100 } else if (id == PODCASTS_PLAYLIST) {
102 } else if (id == ALL_SONGS_PLAYLIST) {
103 long [] list = MusicUtils.getAllSongs(PlaylistBrowserActivity.this);
105 MusicUtils.playAll(PlaylistBrowserActivity.this, list, 0);
108 MusicUtils.playPlaylist(PlaylistBrowserActivity.this, id);
114 public void onServiceDisconnected(ComponentName classname) {
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);
125 setContentView(R.layout.media_picker_activity);
126 ListView lv = getListView();
127 lv.setOnCreateContextMenuListener(this);
128 lv.setTextFilterEnabled(true);
130 mAdapter = (PlaylistListAdapter) getLastNonConfigurationInstance();
131 if (mAdapter == null) {
132 //Log.i("@@@", "starting query");
133 mAdapter = new PlaylistListAdapter(
136 R.layout.track_list_item,
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);
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);
156 setTitle(R.string.working_playlists);
157 getPlaylistCursor(mAdapter.getQueryHandler(), null);
163 public Object onRetainNonConfigurationInstance() {
164 PlaylistListAdapter a = mAdapter;
170 public void onDestroy() {
171 MusicUtils.unbindFromService(this);
173 Cursor c = mAdapter.getCursor();
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);
183 unregisterReceiver(mScanListener);
188 public void onResume() {
191 MusicUtils.setSpinnerState(this);
194 public void onPause() {
195 mReScanHandler.removeCallbacksAndMessages(null);
198 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
200 public void onReceive(Context context, Intent intent) {
201 MusicUtils.setSpinnerState(PlaylistBrowserActivity.this);
202 mReScanHandler.sendEmptyMessage(0);
206 private Handler mReScanHandler = new Handler() {
207 public void handleMessage(Message msg) {
208 getPlaylistCursor(mAdapter.getQueryHandler(), null);
211 public void init(Cursor cursor) {
213 mAdapter.changeCursor(cursor);
215 if (mPlaylistCursor == null) {
216 MusicUtils.displayDatabaseError(this);
218 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
222 MusicUtils.hideDatabaseError(this);
226 private void setTitle() {
227 setTitle(R.string.playlists_title);
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());
238 return super.onCreateOptionsMenu(menu);
242 public boolean onOptionsItemSelected(MenuItem item) {
244 switch (item.getItemId()) {
246 intent = new Intent();
247 intent.setClass(this, MusicBrowserActivity.class);
248 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
249 startActivity(intent);
253 intent = new Intent("com.android.music.PLAYBACK_VIEWER");
254 startActivity(intent);
257 return super.onOptionsItemSelected(item);
260 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfoIn) {
261 if (mCreateShortcut) {
265 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
267 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
269 if (mi.id >= 0 /*|| mi.id == PODCASTS_PLAYLIST*/) {
270 menu.add(0, DELETE_PLAYLIST, 0, R.string.delete_playlist_menu);
273 if (mi.id == RECENTLY_ADDED_PLAYLIST) {
274 menu.add(0, EDIT_PLAYLIST, 0, R.string.edit_playlist_menu);
278 menu.add(0, RENAME_PLAYLIST, 0, R.string.rename_playlist_menu);
281 mPlaylistCursor.moveToPosition(mi.position);
282 menu.setHeaderTitle(mPlaylistCursor.getString(mPlaylistCursor.getColumnIndexOrThrow(
283 MediaStore.Audio.Playlists.NAME)));
287 public boolean onContextItemSelected(MenuItem item) {
288 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) item.getMenuInfo();
289 switch (item.getItemId()) {
291 if (mi.id == RECENTLY_ADDED_PLAYLIST) {
293 } else if (mi.id == PODCASTS_PLAYLIST) {
296 MusicUtils.playPlaylist(this, mi.id);
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);
309 if (mi.id == RECENTLY_ADDED_PLAYLIST) {
310 Intent intent = new Intent();
311 intent.setClass(this, WeekSelector.class);
312 startActivityForResult(intent, CHANGE_WEEKS);
315 Log.e(TAG, "should not be here");
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);
329 protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
330 switch (requestCode) {
332 if (resultCode == RESULT_CANCELED) {
335 getPlaylistCursor(mAdapter.getQueryHandler(), null);
342 protected void onListItemClick(ListView l, View v, int position, long id)
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));
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));
356 setResult(RESULT_OK, intent);
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);
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);
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);
386 if (cursor == null) {
387 // Todo: show a message
391 int len = cursor.getCount();
392 long [] list = new long[len];
393 for (int i = 0; i < len; i++) {
395 list[i] = cursor.getLong(0);
397 MusicUtils.playAll(this, list, 0);
398 } catch (SQLiteException ex) {
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);
411 if (cursor == null) {
412 // Todo: show a message
416 int len = cursor.getCount();
417 long [] list = new long[len];
418 for (int i = 0; i < len; i++) {
420 list[i] = cursor.getLong(0);
422 MusicUtils.playAll(this, list, 0);
423 } catch (SQLiteException ex) {
430 String[] mCols = new String[] {
431 MediaStore.Audio.Playlists._ID,
432 MediaStore.Audio.Playlists.NAME
435 private Cursor getPlaylistCursor(AsyncQueryHandler async, String filterstring) {
437 StringBuilder where = new StringBuilder();
438 where.append(MediaStore.Audio.Playlists.NAME + " != ''");
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] + '%';
450 for (int i = 0; i < searchWords.length; i++) {
451 where.append(" AND ");
452 where.append(MediaStore.Audio.Playlists.NAME + " LIKE ?");
456 String whereclause = where.toString();
460 async.startQuery(0, null, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
461 mCols, whereclause, keywords, MediaStore.Audio.Playlists.NAME);
465 c = MusicUtils.query(this, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
466 mCols, whereclause, keywords, MediaStore.Audio.Playlists.NAME);
468 return mergedCursor(c);
471 private Cursor mergedCursor(Cursor c) {
475 if (c instanceof MergeCursor) {
476 // this shouldn't happen, but fail gracefully
477 Log.d("PlaylistBrowserActivity", "Already wrapped");
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);
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);
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);
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);
507 ArrayListCursor autoplaylistscursor = new ArrayListCursor(mCols, autoplaylists);
509 Cursor cc = new MergeCursor(new Cursor [] {autoplaylistscursor, c});
513 static class PlaylistListAdapter extends SimpleCursorAdapter {
516 private PlaylistBrowserActivity mActivity = null;
517 private AsyncQueryHandler mQueryHandler;
518 private String mConstraint = null;
519 private boolean mConstraintIsValid = false;
521 class QueryHandler extends AsyncQueryHandler {
522 QueryHandler(ContentResolver res) {
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);
532 mActivity.init(cursor);
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());
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);
550 public void setActivity(PlaylistBrowserActivity newactivity) {
551 mActivity = newactivity;
554 public AsyncQueryHandler getQueryHandler() {
555 return mQueryHandler;
559 public void bindView(View view, Context context, Cursor cursor) {
561 TextView tv = (TextView) view.findViewById(R.id.line1);
563 String name = cursor.getString(mTitleIdx);
566 long id = cursor.getLong(mIdIdx);
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);
572 iv.setImageResource(R.drawable.ic_mp_playlist_list);
574 ViewGroup.LayoutParams p = iv.getLayoutParams();
575 p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
576 p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
578 iv = (ImageView) view.findViewById(R.id.play_indicator);
579 iv.setVisibility(View.GONE);
581 view.findViewById(R.id.line2).setVisibility(View.GONE);
585 public void changeCursor(Cursor cursor) {
586 if (cursor != mActivity.mPlaylistCursor) {
587 mActivity.mPlaylistCursor = cursor;
588 super.changeCursor(cursor);
589 getColumnIndices(cursor);
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)))) {
601 Cursor c = mActivity.getPlaylistCursor(null, s);
603 mConstraintIsValid = true;
608 private Cursor mPlaylistCursor;