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;
32 import android.content.SharedPreferences;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.content.res.Configuration;
36 import android.content.res.Resources;
37 import android.database.Cursor;
38 import android.database.MatrixCursor;
39 import android.database.MergeCursor;
40 import android.database.sqlite.SQLiteException;
41 import android.media.AudioManager;
42 import android.net.Uri;
43 import android.os.Bundle;
44 import android.os.Handler;
45 import android.os.IBinder;
46 import android.os.Message;
47 import android.os.RemoteException;
48 import android.provider.BaseColumns;
49 import android.provider.MediaStore;
50 import android.provider.MediaStore.Audio.AudioColumns;
51 import android.util.Log;
52 import android.view.ContextMenu;
53 import android.view.ContextMenu.ContextMenuInfo;
54 import android.view.GestureDetector;
55 import android.view.GestureDetector.SimpleOnGestureListener;
56 import android.view.KeyEvent;
57 import android.view.Menu;
58 import android.view.MenuItem;
59 import android.view.MotionEvent;
60 import android.view.View;
61 import android.view.ViewGroup;
62 import android.view.Window;
63 import android.widget.AdapterView.AdapterContextMenuInfo;
64 import android.widget.FrameLayout;
65 import android.widget.ImageButton;
66 import android.widget.ImageView;
67 import android.widget.LinearLayout;
68 import android.widget.ListView;
69 import android.widget.ProgressBar;
70 import android.widget.RelativeLayout;
71 import android.widget.SimpleCursorAdapter;
72 import android.widget.TabWidget;
73 import android.widget.TextView;
74 import android.widget.Toast;
76 import com.android.music.MusicUtils.ServiceToken;
78 public class PlaylistBrowserActivity extends ListActivity implements
79 View.OnCreateContextMenuListener, MusicUtils.Defs {
80 private static final String TAG = "PlaylistBrowserActivity";
81 private static final int DELETE_PLAYLIST = CHILD_MENU_BASE + 1;
82 private static final int EDIT_PLAYLIST = CHILD_MENU_BASE + 2;
83 private static final int RENAME_PLAYLIST = CHILD_MENU_BASE + 3;
84 private static final int CHANGE_WEEKS = CHILD_MENU_BASE + 4;
85 private static final long RECENTLY_ADDED_PLAYLIST = -1;
86 private static final long ALL_SONGS_PLAYLIST = -2;
87 private static final long PODCASTS_PLAYLIST = -3;
88 private PlaylistListAdapter mAdapter;
90 private static int mLastListPosCourse = -1;
91 private static int mLastListPosFine = -1;
93 private boolean mCreateShortcut;
94 private ServiceToken mToken;
95 private ServiceToken mOSC;
96 private IMediaPlaybackService mService = null;
97 private SharedPreferences mPreferences;
99 public LinearLayout mPlaylistTab;
100 public TabWidget mButtonBar;
101 public TextView mButtonBarArtist;
102 public TextView mButtonBarAlbum;
103 public TextView mButtonBarSong;
104 public TextView mButtonBarPlaylist;
105 public TextView mButtonBarNP;
106 public RelativeLayout mGroup;
107 public RelativeLayout mChild;
108 // Smaller now playing window buttons
109 private LinearLayout mNowPlaying;
110 private ImageView mInfoDivider;
111 private ProgressBar mProgress;
112 private ImageButton mDoSearch;
113 private ImageButton mMarket;
114 private ImageButton mNext;
115 private ImageButton mPlay;
116 private ImageButton mPlusPlaylist;
117 private ImageButton mPrev;
118 private ImageButton mShare;
119 private ImageButton mFlow;
120 // Back button long press
121 public String back_button_db;
122 // Smaller now playing window swipe gesture
123 public GestureDetector gestureDetector;
124 private static final int SWIPE_MIN_DISTANCE = 120;
125 private static final int SWIPE_MAX_OFF_PATH = 250;
126 private static final int SWIPE_THRESHOLD_VELOCITY = 200;
127 private String np_swipe_gesture_db;
129 private GestureDetector swipetabs;
130 // ADW Theme constants
131 public static final int THEME_ITEM_BACKGROUND = 0;
132 public static final int THEME_ITEM_FOREGROUND = 1;
133 public static final int THEME_ITEM_TEXT_DRAWABLE = 2;
135 public PlaylistBrowserActivity() {
138 /** Called when the activity is first created. */
140 public void onCreate(Bundle icicle) {
141 super.onCreate(icicle);
142 mPreferences = getSharedPreferences(
143 MusicSettingsActivity.PREFERENCES_FILE, MODE_PRIVATE);
145 final Intent intent = getIntent();
146 final String action = intent.getAction();
147 if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
148 mCreateShortcut = true;
151 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
152 requestWindowFeature(Window.FEATURE_NO_TITLE);
153 setVolumeControlStream(AudioManager.STREAM_MUSIC);
154 mOSC = MusicUtils.bindToService(this, osc);
155 mToken = MusicUtils.bindToService(this, new ServiceConnection() {
156 public void onServiceConnected(ComponentName classname, IBinder obj) {
157 if (Intent.ACTION_VIEW.equals(action)) {
158 Bundle b = intent.getExtras();
160 Log.w(TAG, "Unexpected:getExtras() returns null.");
163 long id = Long.parseLong(b.getString("playlist"));
164 if (id == RECENTLY_ADDED_PLAYLIST) {
166 } else if (id == PODCASTS_PLAYLIST) {
168 } else if (id == ALL_SONGS_PLAYLIST) {
169 long[] list = MusicUtils
170 .getAllSongs(PlaylistBrowserActivity.this);
173 PlaylistBrowserActivity.this, list,
177 MusicUtils.playPlaylist(
178 PlaylistBrowserActivity.this, id);
180 } catch (NumberFormatException e) {
181 Log.w(TAG, "Playlist id missing or broken");
187 MusicUtils.updateNowPlaying(PlaylistBrowserActivity.this);
190 public void onServiceDisconnected(ComponentName classname) {
194 IntentFilter f = new IntentFilter();
195 f.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
196 f.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
197 f.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
198 f.addDataScheme("file");
199 registerReceiver(mScanListener, f);
201 setContentView(R.layout.media_picker_activity);
202 MusicUtils.updateButtonBar(this, R.id.playlisttab);
203 ListView lv = getListView();
204 lv.setOnCreateContextMenuListener(this);
205 lv.setTextFilterEnabled(true);
207 mAdapter = (PlaylistListAdapter) getLastNonConfigurationInstance();
208 if (mAdapter == null) {
209 // Log.i("@@@", "starting query");
210 mAdapter = new PlaylistListAdapter(getApplication(), this,
211 R.layout.track_list_item, mPlaylistCursor,
212 new String[] { MediaStore.Audio.Playlists.NAME },
213 new int[] { android.R.id.text1 });
214 setListAdapter(mAdapter);
215 setTitle(R.string.working_playlists);
216 getPlaylistCursor(mAdapter.getQueryHandler(), null);
218 mAdapter.setActivity(this);
219 setListAdapter(mAdapter);
220 mPlaylistCursor = mAdapter.getCursor();
221 // If mPlaylistCursor is null, this can be because it doesn't have
222 // a cursor yet (because the initial query that sets its cursor
223 // is still in progress), or because the query failed.
224 // In order to not flash the error dialog at the user for the
225 // first case, simply retry the query when the cursor is null.
226 // Worst case, we end up doing the same query twice.
227 if (mPlaylistCursor != null) {
228 init(mPlaylistCursor);
230 setTitle(R.string.working_playlists);
231 getPlaylistCursor(mAdapter.getQueryHandler(), null);
235 gestureDetector = new GestureDetector(new NowPlayingGesture());
236 View nowplayingview = (View) findViewById(R.id.nowplaying);
238 swipetabs = new GestureDetector(new TabSwipe());
239 View mainview = (View) findViewById(android.R.id.list);
241 // Set the touch listener for the main view to be our custom gesture
243 mainview.setOnTouchListener(new View.OnTouchListener() {
244 public boolean onTouch(View v, MotionEvent event) {
245 if (swipetabs.onTouchEvent(event)) {
252 // Smaller now playing window buttons
253 mShare = (ImageButton) findViewById(R.id.share_song);
254 mPlay = (ImageButton) findViewById(R.id.media_play);
255 mNext = (ImageButton) findViewById(R.id.media_next);
256 mPrev = (ImageButton) findViewById(R.id.media_prev);
257 mMarket = (ImageButton) findViewById(R.id.market_music);
258 mDoSearch = (ImageButton) findViewById(R.id.doSearch);
259 mPlusPlaylist = (ImageButton) findViewById(R.id.plus_playlist);
260 mFlow = (ImageButton) findViewById(R.id.overFlow);
261 // I found that without setting the onClickListeners in Portrait mode
262 // only, flipping into Landscape force closes.
263 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
264 // Smaller now playing window actions
265 mPlusPlaylist.setOnClickListener(mAddPlaylist);
266 mDoSearch.setOnClickListener(mDoSearchListener);
267 mMarket.setOnClickListener(mMarketSearch);
268 mPlay.setOnClickListener(mMediaPlay);
269 mNext.setOnClickListener(mMediaNext);
270 mPrev.setOnClickListener(mMediaPrev);
271 mShare.setOnClickListener(mShareSong);
272 mFlow.setOnClickListener(mOverFlow);
273 // I only want this to show in the Playlist activity
274 if (mPreferences.getBoolean(
275 MusicSettingsActivity.KEY_ENABLE_NEW_PLAYLIST_BUTTON, true)) {
276 mPlusPlaylist.setVisibility(View.VISIBLE);
278 mPlusPlaylist.setVisibility(View.GONE);
280 nowplayingview.setOnTouchListener(new View.OnTouchListener() {
281 public boolean onTouch(View vee, MotionEvent nowplayingevent) {
282 if (gestureDetector.onTouchEvent(nowplayingevent)) {
289 // ADW: Load the specified theme
290 String themePackage = MusicUtils.getThemePackageName(this,
291 MusicSettingsActivity.THEME_DEFAULT);
292 PackageManager pm = getPackageManager();
293 Resources themeResources = null;
294 if (!themePackage.equals(MusicSettingsActivity.THEME_DEFAULT)) {
296 themeResources = pm.getResourcesForApplication(themePackage);
297 } catch (NameNotFoundException e) {
298 // ADW The saved theme was uninstalled so we save the
300 MusicUtils.setThemePackageName(this,
301 MusicSettingsActivity.THEME_DEFAULT);
304 // Set Views for themes
305 if (themeResources != null) {
307 mPlaylistTab = (LinearLayout) findViewById(R.id.album_tab);
308 mButtonBar = (TabWidget) findViewById(R.id.buttonbar);
309 mButtonBarArtist = (TextView) findViewById(R.id.artisttab);
310 mButtonBarAlbum = (TextView) findViewById(R.id.albumtab);
311 mButtonBarSong = (TextView) findViewById(R.id.songtab);
312 mButtonBarPlaylist = (TextView) findViewById(R.id.playlisttab);
313 mButtonBarNP = (TextView) findViewById(R.id.nowplayingtab);
314 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
315 themePackage, "tab_playlist", mPlaylistTab,
316 THEME_ITEM_BACKGROUND);
317 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
318 themePackage, "buttonbar", mButtonBar,
319 THEME_ITEM_BACKGROUND);
320 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
321 themePackage, "tab_bg_artist", mButtonBarArtist,
322 THEME_ITEM_BACKGROUND);
323 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
324 themePackage, "tab_bg_album", mButtonBarAlbum,
325 THEME_ITEM_BACKGROUND);
326 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
327 themePackage, "tab_bg_song", mButtonBarSong,
328 THEME_ITEM_BACKGROUND);
329 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
330 themePackage, "tab_bg_playlist", mButtonBarPlaylist,
331 THEME_ITEM_BACKGROUND);
332 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
333 themePackage, "tab_bg_nowplaying", mButtonBarNP,
334 THEME_ITEM_BACKGROUND);
335 // Small now playing window
336 mNowPlaying = (LinearLayout) findViewById(R.id.nowplaying);
337 mInfoDivider = (ImageView) findViewById(R.id.info_divider);
338 mProgress = (ProgressBar) findViewById(R.id.progress);
339 mShare = (ImageButton) findViewById(R.id.share_song);
340 mPlay = (ImageButton) findViewById(R.id.media_play);
341 mNext = (ImageButton) findViewById(R.id.media_next);
342 mPrev = (ImageButton) findViewById(R.id.media_prev);
343 mMarket = (ImageButton) findViewById(R.id.market_music);
344 mDoSearch = (ImageButton) findViewById(R.id.doSearch);
345 mFlow = (ImageButton) findViewById(R.id.overFlow);
346 mPlusPlaylist = (ImageButton) findViewById(R.id.plus_playlist);
347 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
348 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
349 themePackage, "snp_background", mNowPlaying,
350 THEME_ITEM_BACKGROUND);
351 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
352 themePackage, "snp_progress", mProgress,
353 THEME_ITEM_BACKGROUND);
354 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
355 themePackage, "snp_info_divider", mInfoDivider,
356 THEME_ITEM_BACKGROUND);
357 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
358 themePackage, "snp_share", mShare,
359 THEME_ITEM_FOREGROUND);
360 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
361 themePackage, "snp_play", mPlay, THEME_ITEM_FOREGROUND);
362 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
363 themePackage, "snp_next", mNext, THEME_ITEM_FOREGROUND);
364 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
365 themePackage, "snp_prev", mPrev, THEME_ITEM_FOREGROUND);
366 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
367 themePackage, "snp_market", mMarket,
368 THEME_ITEM_FOREGROUND);
369 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
370 themePackage, "snp_search", mDoSearch,
371 THEME_ITEM_FOREGROUND);
372 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
373 themePackage, "snp_flow", mFlow, THEME_ITEM_FOREGROUND);
374 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
375 themePackage, "snp_plus_playlist", mPlusPlaylist,
376 THEME_ITEM_FOREGROUND);
382 class TabSwipe extends SimpleOnGestureListener {
384 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
386 Intent intent = new Intent(Intent.ACTION_PICK);
389 if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
391 if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
392 && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
393 intent.setDataAndType(Uri.EMPTY,
394 "vnd.android.cursor.dir/artistalbum");
395 intent.putExtra("withtabs", true);
396 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
397 startActivity(intent);
399 PlaylistBrowserActivity.this.overridePendingTransition(
400 R.anim.slide_in_right, R.anim.slide_out_left);
402 } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
403 && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
404 intent.setDataAndType(Uri.EMPTY,
405 "vnd.android.cursor.dir/track");
406 intent.putExtra("withtabs", true);
407 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
408 startActivity(intent);
410 PlaylistBrowserActivity.this.overridePendingTransition(
411 R.anim.slide_in_left, R.anim.slide_out_right);
416 } catch (Exception e) {
417 Log.e("Fling", "There was an error processing the Fling event:"
423 // It is necessary to return true from onDown for the onFling event to
426 public boolean onDown(MotionEvent e) {
432 class NowPlayingGesture extends SimpleOnGestureListener {
434 public boolean onFling(MotionEvent e3, MotionEvent e4, float vX,
436 np_swipe_gesture_db = mPreferences.getString("np_swipe_gesture",
438 if (np_swipe_gesture_db.equals("0")) {
439 if (e3.getY() - e4.getY() > SWIPE_MIN_DISTANCE
440 && Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {
445 np_swipe_gesture_db = mPreferences.getString("np_swipe_gesture",
447 if (np_swipe_gesture_db.equals("1")) {
448 if (e3.getY() - e4.getY() > SWIPE_MIN_DISTANCE
449 && Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {
454 np_swipe_gesture_db = mPreferences.getString("np_swipe_gesture",
456 if (np_swipe_gesture_db.equals("2")) {
457 if (e3.getY() - e4.getY() > SWIPE_MIN_DISTANCE
458 && Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {
462 np_swipe_gesture_db = mPreferences.getString("np_swipe_gesture",
464 if (np_swipe_gesture_db.equals("3")) {
465 if (e3.getY() - e4.getY() > SWIPE_MIN_DISTANCE
466 && Math.abs(vY) > SWIPE_THRESHOLD_VELOCITY) {
475 public boolean onDown(MotionEvent f) {
480 // Smaller now playing window OnClickListerners
481 private View.OnClickListener mMarketSearch = new View.OnClickListener() {
482 public void onClick(View v) {
483 Uri marketUri = null;
485 marketUri = Uri.parse("market://search?q= "
486 + mService.getArtistName());
487 } catch (RemoteException e) {
488 // TODO Auto-generated catch block
491 Intent marketIntent = new Intent(Intent.ACTION_VIEW)
493 startActivity(marketIntent);
498 private View.OnClickListener mShareSong = new View.OnClickListener() {
501 public void onClick(View v) {
502 // TODO Auto-generated method stub
505 } catch (RemoteException e) {
506 // TODO Auto-generated catch block
514 private String shareCurrentTrack() throws RemoteException {
515 if (mService.getTrackName() == null || mService.getArtistName() == null) {
519 Intent shareIntent = new Intent();
520 String currentTrackMessage = "Now listening to: "
521 + mService.getTrackName() + " by " + mService.getArtistName();
523 shareIntent.setAction(Intent.ACTION_SEND);
524 shareIntent.setType("text/plain");
525 shareIntent.putExtra(Intent.EXTRA_TEXT, currentTrackMessage);
527 startActivity(Intent.createChooser(shareIntent, "Share track using"));
528 return currentTrackMessage;
531 private View.OnClickListener mAddPlaylist = new View.OnClickListener() {
532 public void onClick(View v) {
534 intent = new Intent();
535 intent.setClass(PlaylistBrowserActivity.this, CreatePlaylist.class);
536 startActivityForResult(intent, NEW_PLAYLIST);
540 private View.OnClickListener mOverFlow = new View.OnClickListener() {
541 public void onClick(View v) {
545 private View.OnClickListener mDoSearchListener = new View.OnClickListener() {
546 public void onClick(View v) {
550 private View.OnClickListener mMediaPlay = new View.OnClickListener() {
551 public void onClick(View v) {
555 private View.OnClickListener mMediaNext = new View.OnClickListener() {
556 public void onClick(View v) {
560 private View.OnClickListener mMediaPrev = new View.OnClickListener() {
561 public void onClick(View v) {
567 public Object onRetainNonConfigurationInstance() {
568 PlaylistListAdapter a = mAdapter;
574 public void onDestroy() {
575 ListView lv = getListView();
577 mLastListPosCourse = lv.getFirstVisiblePosition();
578 View cv = lv.getChildAt(0);
580 mLastListPosFine = cv.getTop();
583 MusicUtils.unbindFromService(mToken);
584 MusicUtils.unbindFromService(mOSC);
585 // If we have an adapter and didn't send it off to another activity yet,
587 // close its cursor, which we do by assigning a null cursor to it. Doing
589 // instead of closing the cursor directly keeps the framework from
591 // the closed cursor later.
592 if (!mAdapterSent && mAdapter != null) {
593 mAdapter.changeCursor(null);
595 // Because we pass the adapter to the next activity, we need to make
596 // sure it doesn't keep a reference to this activity. We can do this
597 // by clearing its DatasetObservers, which setListAdapter(null) does.
598 setListAdapter(null);
600 unregisterReceiver(mScanListener);
604 private void startPlayback() {
606 if (mService == null)
608 Intent intent = getIntent();
609 String filename = "";
610 Uri uri = intent.getData();
611 if (uri != null && uri.toString().length() > 0) {
612 // If this is a file:// URI, just use the path directly instead
613 // of going through the open-from-filedescriptor codepath.
614 String scheme = uri.getScheme();
615 if ("file".equals(scheme)) {
616 filename = uri.getPath();
618 filename = uri.toString();
623 mService.openFile(filename);
625 setIntent(new Intent());
626 } catch (Exception ex) {
627 Log.d("MediaPlaybackActivity", "couldn't start playback: " + ex);
632 private ServiceConnection osc = new ServiceConnection() {
633 public void onServiceConnected(ComponentName classname, IBinder obj) {
634 mService = IMediaPlaybackService.Stub.asInterface(obj);
636 // This is for orientation changes or entering the tab from a
637 // different activity
638 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
639 setPauseButtonImage();
640 // This is so the controls will load correctly according to the
642 MusicUtils.updateNowPlaying(PlaylistBrowserActivity.this);
645 if (mService.getAudioId() >= 0 || mService.isPlaying()
646 || mService.getPath() != null) {
649 } catch (RemoteException ex) {
651 if (getIntent().getData() == null) {
652 Intent intent = new Intent(Intent.ACTION_MAIN);
653 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
654 intent.setClass(PlaylistBrowserActivity.this,
655 MusicBrowserActivity.class);
656 startActivity(intent);
660 public void onServiceDisconnected(ComponentName classname) {
666 public void onResume() {
669 IntentFilter f = new IntentFilter();
670 f.addAction(MediaPlaybackService.META_CHANGED);
671 f.addAction(MediaPlaybackService.QUEUE_CHANGED);
672 f.addAction(MediaPlaybackService.PROGRESSBAR_CHANGED);
673 f.addAction(MediaPlaybackService.PLAYSTATE_CHANGED);
674 registerReceiver(mTrackListListener, f);
676 MusicUtils.setSpinnerState(this);
680 public void onPause() {
682 unregisterReceiver(mTrackListListener);
683 mReScanHandler.removeCallbacksAndMessages(null);
687 private BroadcastReceiver mTrackListListener = new BroadcastReceiver() {
689 public void onReceive(Context context, Intent intent) {
690 String action = intent.getAction();
691 getListView().invalidateViews();
692 MusicUtils.updateNowPlaying(PlaylistBrowserActivity.this);
693 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
695 if (action.equals(MediaPlaybackService.PLAYSTATE_CHANGED)) {
696 setPauseButtonImage();
701 private BroadcastReceiver mScanListener = new BroadcastReceiver() {
703 public void onReceive(Context context, Intent intent) {
704 MusicUtils.setSpinnerState(PlaylistBrowserActivity.this);
705 mReScanHandler.sendEmptyMessage(0);
709 private Handler mReScanHandler = new Handler() {
711 public void handleMessage(Message msg) {
712 if (mAdapter != null) {
713 getPlaylistCursor(mAdapter.getQueryHandler(), null);
718 public void init(Cursor cursor) {
720 if (mAdapter == null) {
723 mAdapter.changeCursor(cursor);
725 if (mPlaylistCursor == null) {
726 MusicUtils.displayDatabaseError(this);
728 mReScanHandler.sendEmptyMessageDelayed(0, 1000);
732 // restore previous position
733 if (mLastListPosCourse >= 0) {
734 getListView().setSelectionFromTop(mLastListPosCourse,
736 mLastListPosCourse = -1;
738 MusicUtils.hideDatabaseError(this);
739 MusicUtils.updateButtonBar(this, R.id.playlisttab);
743 private void setTitle() {
744 setTitle(R.string.playlists_title);
748 public boolean onCreateOptionsMenu(Menu menu) {
749 if (!mCreateShortcut) {
750 menu.add(0, PARTY_SHUFFLE, 0, R.string.party_shuffle);
751 menu.add(0, SETTINGS, 0, R.string.settings);
754 return super.onCreateOptionsMenu(menu);
758 public boolean onPrepareOptionsMenu(Menu menu) {
759 MusicUtils.setPartyShuffleMenuIcon(menu);
760 return super.onPrepareOptionsMenu(menu);
764 public boolean onOptionsItemSelected(MenuItem item) {
766 switch (item.getItemId()) {
768 MusicUtils.togglePartyShuffle();
771 intent = new Intent();
772 intent.setClass(this, MusicSettingsActivity.class);
773 startActivityForResult(intent, SETTINGS);
776 return super.onOptionsItemSelected(item);
779 public void onCreateContextMenu(ContextMenu menu, View view,
780 ContextMenuInfo menuInfoIn) {
781 if (mCreateShortcut) {
785 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) menuInfoIn;
787 menu.add(0, PLAY_SELECTION, 0, R.string.play_selection);
789 if (mi.id >= 0 /* || mi.id == PODCASTS_PLAYLIST */) {
790 menu.add(0, DELETE_PLAYLIST, 0, R.string.delete_playlist_menu);
793 if (mi.id == RECENTLY_ADDED_PLAYLIST) {
794 menu.add(0, EDIT_PLAYLIST, 0, R.string.edit_playlist_menu);
798 menu.add(0, RENAME_PLAYLIST, 0, R.string.rename_playlist_menu);
801 mPlaylistCursor.moveToPosition(mi.position);
802 menu.setHeaderTitle(mPlaylistCursor.getString(mPlaylistCursor
803 .getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME)));
807 public boolean onContextItemSelected(MenuItem item) {
808 AdapterContextMenuInfo mi = (AdapterContextMenuInfo) item.getMenuInfo();
809 switch (item.getItemId()) {
811 if (mi.id == RECENTLY_ADDED_PLAYLIST) {
813 } else if (mi.id == PODCASTS_PLAYLIST) {
816 MusicUtils.playPlaylist(this, mi.id);
819 case DELETE_PLAYLIST:
820 Uri uri = ContentUris.withAppendedId(
821 MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, mi.id);
822 getContentResolver().delete(uri, null, null);
823 Toast.makeText(this, R.string.playlist_deleted_message,
824 Toast.LENGTH_SHORT).show();
825 if (mPlaylistCursor.getCount() == 0) {
826 setTitle(R.string.no_playlists_title);
830 if (mi.id == RECENTLY_ADDED_PLAYLIST) {
831 Intent intent = new Intent();
832 intent.setClass(this, WeekSelector.class);
833 startActivityForResult(intent, CHANGE_WEEKS);
836 Log.e(TAG, "should not be here");
839 case RENAME_PLAYLIST:
840 Intent intent = new Intent();
841 intent.setClass(this, RenamePlaylist.class);
842 intent.putExtra("rename", mi.id);
843 startActivityForResult(intent, RENAME_PLAYLIST);
850 protected void onActivityResult(int requestCode, int resultCode,
852 switch (requestCode) {
854 if (resultCode == RESULT_CANCELED) {
856 } else if (mAdapter != null) {
857 getPlaylistCursor(mAdapter.getQueryHandler(), null);
864 protected void onListItemClick(ListView l, View v, int position, long id) {
865 if (mCreateShortcut) {
866 final Intent shortcut = new Intent();
867 shortcut.setAction(Intent.ACTION_VIEW);
868 shortcut.setDataAndType(Uri.EMPTY,
869 "vnd.android.cursor.dir/playlist");
870 shortcut.putExtra("playlist", String.valueOf(id));
872 final Intent intent = new Intent();
873 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcut);
874 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME,
875 ((TextView) v.findViewById(R.id.line1)).getText());
876 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
877 Intent.ShortcutIconResource.fromContext(this,
878 R.drawable.ic_launcher_shortcut_music_playlist));
880 setResult(RESULT_OK, intent);
884 if (id == RECENTLY_ADDED_PLAYLIST) {
885 Intent intent = new Intent(Intent.ACTION_PICK);
886 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
887 intent.putExtra("playlist", "recentlyadded");
888 startActivity(intent);
889 } else if (id == PODCASTS_PLAYLIST) {
890 Intent intent = new Intent(Intent.ACTION_PICK);
891 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
892 intent.putExtra("playlist", "podcasts");
893 startActivity(intent);
895 Intent intent = new Intent(Intent.ACTION_EDIT);
896 intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
897 intent.putExtra("playlist", Long.valueOf(id).toString());
898 startActivity(intent);
902 private void playRecentlyAdded() {
903 // do a query for all songs added in the last X weeks
904 int X = MusicUtils.getIntPref(this, "numweeks", 2) * (3600 * 24 * 7);
905 final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
906 String where = MediaStore.MediaColumns.DATE_ADDED + ">"
907 + (System.currentTimeMillis() / 1000 - X);
908 Cursor cursor = MusicUtils.query(this,
909 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols, where,
910 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
912 if (cursor == null) {
913 // Todo: show a message
917 int len = cursor.getCount();
918 long[] list = new long[len];
919 for (int i = 0; i < len; i++) {
921 list[i] = cursor.getLong(0);
923 MusicUtils.playAll(this, list, 0);
924 } catch (SQLiteException ex) {
930 private void playPodcasts() {
931 // do a query for all files that are podcasts
932 final String[] ccols = new String[] { MediaStore.Audio.Media._ID };
933 Cursor cursor = MusicUtils.query(this,
934 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, ccols,
935 MediaStore.Audio.Media.IS_PODCAST + "=1", null,
936 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
938 if (cursor == null) {
939 // Todo: show a message
943 int len = cursor.getCount();
944 long[] list = new long[len];
945 for (int i = 0; i < len; i++) {
947 list[i] = cursor.getLong(0);
949 MusicUtils.playAll(this, list, 0);
950 } catch (SQLiteException ex) {
956 String[] mCols = new String[] { MediaStore.Audio.Playlists._ID,
957 MediaStore.Audio.Playlists.NAME };
959 private Cursor getPlaylistCursor(AsyncQueryHandler async,
960 String filterstring) {
962 StringBuilder where = new StringBuilder();
963 where.append(MediaStore.Audio.Playlists.NAME + " != ''");
965 // Add in the filtering constraints
966 String[] keywords = null;
967 if (filterstring != null) {
968 String[] searchWords = filterstring.split(" ");
969 keywords = new String[searchWords.length];
970 Collator col = Collator.getInstance();
971 col.setStrength(Collator.PRIMARY);
972 for (int i = 0; i < searchWords.length; i++) {
973 keywords[i] = '%' + searchWords[i] + '%';
975 for (int i = 0; i < searchWords.length; i++) {
976 where.append(" AND ");
977 where.append(MediaStore.Audio.Playlists.NAME + " LIKE ?");
981 String whereclause = where.toString();
984 async.startQuery(0, null,
985 MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, mCols,
986 whereclause, keywords, MediaStore.Audio.Playlists.NAME);
990 c = MusicUtils.query(this,
991 MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, mCols,
992 whereclause, keywords, MediaStore.Audio.Playlists.NAME);
994 return mergedCursor(c);
997 private Cursor mergedCursor(Cursor c) {
1001 if (c instanceof MergeCursor) {
1002 // this shouldn't happen, but fail gracefully
1003 Log.d("PlaylistBrowserActivity", "Already wrapped");
1006 MatrixCursor autoplaylistscursor = new MatrixCursor(mCols);
1007 if (mCreateShortcut) {
1008 ArrayList<Object> all = new ArrayList<Object>(2);
1009 all.add(ALL_SONGS_PLAYLIST);
1010 all.add(getString(R.string.play_all));
1011 autoplaylistscursor.addRow(all);
1013 ArrayList<Object> recent = new ArrayList<Object>(2);
1014 recent.add(RECENTLY_ADDED_PLAYLIST);
1015 recent.add(getString(R.string.recentlyadded));
1016 autoplaylistscursor.addRow(recent);
1018 // check if there are any podcasts
1019 Cursor counter = MusicUtils.query(this,
1020 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1021 new String[] { "count(*)" }, "is_podcast=1", null, null);
1022 if (counter != null) {
1023 counter.moveToFirst();
1024 int numpodcasts = counter.getInt(0);
1026 if (numpodcasts > 0) {
1027 ArrayList<Object> podcasts = new ArrayList<Object>(2);
1028 podcasts.add(PODCASTS_PLAYLIST);
1029 podcasts.add(getString(R.string.podcasts_listitem));
1030 autoplaylistscursor.addRow(podcasts);
1034 Cursor cc = new MergeCursor(new Cursor[] { autoplaylistscursor, c });
1038 static class PlaylistListAdapter extends SimpleCursorAdapter {
1041 private PlaylistBrowserActivity mActivity = null;
1042 private AsyncQueryHandler mQueryHandler;
1043 private String mConstraint = null;
1044 private boolean mConstraintIsValid = false;
1046 class QueryHandler extends AsyncQueryHandler {
1047 QueryHandler(ContentResolver res) {
1052 protected void onQueryComplete(int token, Object cookie,
1054 // Log.i("@@@", "query complete: " + cursor.getCount() + " " +
1056 if (cursor != null) {
1057 cursor = mActivity.mergedCursor(cursor);
1059 mActivity.init(cursor);
1063 PlaylistListAdapter(Context context,
1064 PlaylistBrowserActivity currentactivity, int layout,
1065 Cursor cursor, String[] from, int[] to) {
1066 super(context, layout, cursor, from, to);
1067 mActivity = currentactivity;
1068 getColumnIndices(cursor);
1069 mQueryHandler = new QueryHandler(context.getContentResolver());
1072 private void getColumnIndices(Cursor cursor) {
1073 if (cursor != null) {
1075 .getColumnIndexOrThrow(MediaStore.Audio.Playlists.NAME);
1077 .getColumnIndexOrThrow(MediaStore.Audio.Playlists._ID);
1081 public void setActivity(PlaylistBrowserActivity newactivity) {
1082 mActivity = newactivity;
1085 public AsyncQueryHandler getQueryHandler() {
1086 return mQueryHandler;
1089 private View.OnClickListener mCML = new View.OnClickListener() {
1090 public void onClick(View v) {
1091 v.showContextMenu();
1097 public void bindView(View view, Context context, Cursor cursor) {
1099 // ADW: Load the specified theme
1100 String themePackage = MusicUtils.getThemePackageName(context,
1101 MusicSettingsActivity.THEME_DEFAULT);
1102 PackageManager pm = context.getPackageManager();
1103 Resources themeResources = null;
1104 if (!themePackage.equals(MusicSettingsActivity.THEME_DEFAULT)) {
1107 .getResourcesForApplication(themePackage);
1108 } catch (NameNotFoundException e) {
1109 // ADW The saved theme was uninstalled so we save the
1111 MusicUtils.setThemePackageName(context,
1112 MusicSettingsActivity.THEME_DEFAULT);
1116 TextView tv = (TextView) view.findViewById(R.id.line1);
1118 String name = cursor.getString(mTitleIdx);
1121 long id = cursor.getLong(mIdIdx);
1123 ImageView iv = (ImageView) view.findViewById(R.id.icon);
1125 ViewGroup.LayoutParams p = iv.getLayoutParams();
1126 p.width = ViewGroup.LayoutParams.WRAP_CONTENT;
1127 p.height = ViewGroup.LayoutParams.WRAP_CONTENT;
1129 iv = (ImageView) view.findViewById(R.id.play_indicator);
1130 iv.setVisibility(View.GONE);
1132 view.findViewById(R.id.line2).setVisibility(View.GONE);
1134 FrameLayout mContextMenu = (FrameLayout) view
1135 .findViewById(R.id.second_column_icon);
1136 iv.setVisibility(View.GONE);
1137 mContextMenu.setOnClickListener(mCML);
1138 ImageView cM = (ImageView) view.findViewById(R.id.CM);
1139 if (themeResources != null) {
1141 int line1 = themeResources.getIdentifier(
1142 "playlist_tab_playlist_name_color", "color",
1145 tv.setTextColor(themeResources.getColor(line1));
1147 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
1148 themePackage, "bt_context_menu", cM,
1149 THEME_ITEM_FOREGROUND);
1154 public void changeCursor(Cursor cursor) {
1155 if (mActivity.isFinishing() && cursor != null) {
1159 if (cursor != mActivity.mPlaylistCursor) {
1160 mActivity.mPlaylistCursor = cursor;
1161 super.changeCursor(cursor);
1162 getColumnIndices(cursor);
1167 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
1168 String s = constraint.toString();
1169 if (mConstraintIsValid
1170 && ((s == null && mConstraint == null) || (s != null && s
1171 .equals(mConstraint)))) {
1174 Cursor c = mActivity.getPlaylistCursor(null, s);
1176 mConstraintIsValid = true;
1181 private Cursor mPlaylistCursor;
1183 // Methods for media control
1184 private void doPauseResume() {
1186 if (mService != null) {
1187 if (mService.isPlaying()) {
1192 if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
1193 setPauseButtonImage();
1196 } catch (RemoteException ex) {
1200 private void setPauseButtonImage() {
1201 // ADW: Load the specified theme
1202 String themePackage = MusicUtils.getThemePackageName(this,
1203 MusicSettingsActivity.THEME_DEFAULT);
1204 PackageManager pm = getPackageManager();
1205 Resources themeResources = null;
1206 if (!themePackage.equals(MusicSettingsActivity.THEME_DEFAULT)) {
1208 themeResources = pm.getResourcesForApplication(themePackage);
1209 } catch (NameNotFoundException e) {
1210 // ADW The saved theme was uninstalled so we save the
1212 MusicUtils.setThemePackageName(this,
1213 MusicSettingsActivity.THEME_DEFAULT);
1217 if (mService != null && mService.isPlaying()) {
1218 mPlay.setImageResource(R.drawable.ic_media_pause);
1219 ArtistAlbumBrowserActivity
1220 .loadThemeResource(themeResources, themePackage,
1221 "snp_pause", mPlay, THEME_ITEM_FOREGROUND);
1223 mPlay.setImageResource(R.drawable.ic_appwidget_music_play);
1224 ArtistAlbumBrowserActivity.loadThemeResource(themeResources,
1225 themePackage, "snp_play", mPlay, THEME_ITEM_FOREGROUND);
1227 } catch (RemoteException ex) {
1231 private void refreshProgress() {
1232 ProgressBar mProgress = (ProgressBar) findViewById(R.id.progress);
1233 mProgress.setMax(1000);
1235 if ((MusicUtils.sService.position() >= 0)
1236 && (MusicUtils.sService.duration() > 0)) {
1237 mProgress.setProgress((int) (1000 * MusicUtils.sService
1238 .position() / MusicUtils.sService.duration()));
1240 mProgress.setProgress(1000);
1242 } catch (Exception e) {
1243 // TODO Auto-generated catch block
1244 e.printStackTrace();
1249 private void doPrev() {
1250 if (mService == null)
1253 if (mService.position() < 2000) {
1259 setPauseButtonImage();
1260 } catch (RemoteException ex) {
1264 private void doNext() {
1265 if (mService == null)
1269 setPauseButtonImage();
1270 } catch (RemoteException ex) {
1275 public boolean onKeyLongPress(int keyCode, KeyEvent event) {
1276 if (keyCode == KeyEvent.KEYCODE_BACK) {
1277 back_button_db = mPreferences.getString("back_button_db", "0");
1278 if (back_button_db.equals("0")) {
1279 MusicUtils.togglePartyShuffle();
1282 if (keyCode == KeyEvent.KEYCODE_BACK) {
1283 back_button_db = mPreferences.getString("back_button_db", "1");
1284 if (back_button_db.equals("1")) {
1285 playRecentlyAdded();
1290 if (keyCode == KeyEvent.KEYCODE_BACK) {
1291 back_button_db = mPreferences.getString("back_button_db", "2");
1292 if (back_button_db.equals("2")) {
1294 cursor = MusicUtils.query(this,
1295 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
1296 new String[] { BaseColumns._ID },
1297 AudioColumns.IS_MUSIC + "=1", null,
1298 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
1299 if (cursor != null) {
1300 MusicUtils.shuffleAll(this, cursor);
1307 return super.onKeyLongPress(keyCode, event);