OSDN Git Service

Eleven: request record audio permission for visualizer
[android-x86/packages-apps-Eleven.git] / src / com / cyanogenmod / eleven / ui / fragments / AudioPlayerFragment.java
1 /*
2 * Copyright (C) 2014 The CyanogenMod 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 package com.cyanogenmod.eleven.ui.fragments;
17
18 import android.content.BroadcastReceiver;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.ServiceConnection;
24 import android.graphics.Outline;
25 import android.media.AudioManager;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Message;
30 import android.os.PowerManager;
31 import android.support.v4.app.Fragment;
32 import android.support.v4.view.ViewPager;
33 import android.text.Html;
34 import android.text.Spanned;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.ContextMenu;
38 import android.view.LayoutInflater;
39 import android.view.Menu;
40 import android.view.MenuInflater;
41 import android.view.MenuItem;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.view.ViewOutlineProvider;
45 import android.widget.ImageView;
46 import android.widget.PopupMenu;
47 import android.widget.TextView;
48
49 import com.cyanogenmod.eleven.MusicPlaybackService;
50 import com.cyanogenmod.eleven.R;
51 import com.cyanogenmod.eleven.adapters.AlbumArtPagerAdapter;
52 import com.cyanogenmod.eleven.cache.ImageFetcher;
53 import com.cyanogenmod.eleven.loaders.NowPlayingCursor;
54 import com.cyanogenmod.eleven.loaders.QueueLoader;
55 import com.cyanogenmod.eleven.menu.CreateNewPlaylist;
56 import com.cyanogenmod.eleven.menu.DeleteDialog;
57 import com.cyanogenmod.eleven.menu.FragmentMenuItems;
58 import com.cyanogenmod.eleven.ui.activities.HomeActivity;
59 import com.cyanogenmod.eleven.utils.ApolloUtils;
60 import com.cyanogenmod.eleven.utils.MusicUtils;
61 import com.cyanogenmod.eleven.utils.NavUtils;
62 import com.cyanogenmod.eleven.utils.PreferenceUtils;
63 import com.cyanogenmod.eleven.widgets.BrowseButton;
64 import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
65 import com.cyanogenmod.eleven.widgets.NoResultsContainer;
66 import com.cyanogenmod.eleven.widgets.PlayPauseProgressButton;
67 import com.cyanogenmod.eleven.widgets.QueueButton;
68 import com.cyanogenmod.eleven.widgets.RepeatButton;
69 import com.cyanogenmod.eleven.widgets.RepeatingImageButton;
70 import com.cyanogenmod.eleven.widgets.ShuffleButton;
71 import com.cyanogenmod.eleven.widgets.VisualizerView;
72
73 import java.lang.ref.WeakReference;
74
75 import static com.cyanogenmod.eleven.utils.MusicUtils.mService;
76
77 public class AudioPlayerFragment extends Fragment implements ServiceConnection {
78     private static final String TAG = AudioPlayerFragment.class.getSimpleName();
79
80     /**
81      * Used to keep context menu items from bleeding into other fragments
82      */
83     private static final int GROUP_ID = 15;
84
85     // fragment view
86     private ViewGroup mRootView;
87
88     // Header views
89     private TextView mSongTitle;
90     private TextView mArtistName;
91
92     // Playlist Button
93     private ImageView mAddToPlaylistButton;
94
95     // Menu Button
96     private ImageView mMenuButton;
97
98     // Message to refresh the time
99     private static final int REFRESH_TIME = 1;
100
101     // The service token
102     private MusicUtils.ServiceToken mToken;
103
104     // Play pause and progress button
105     private PlayPauseProgressButton mPlayPauseProgressButton;
106
107     // Repeat button
108     private RepeatButton mRepeatButton;
109
110     // Shuffle button
111     private ShuffleButton mShuffleButton;
112
113     // Previous button
114     private RepeatingImageButton mPreviousButton;
115
116     // Next button
117     private RepeatingImageButton mNextButton;
118
119     // Album art ListView
120     private ViewPager mAlbumArtViewPager;
121     private LoadingEmptyContainer mQueueEmpty;
122
123     private AlbumArtPagerAdapter mAlbumArtPagerAdapter;
124
125     // Current time
126     private TextView mCurrentTime;
127
128     // Total time
129     private TextView mTotalTime;
130
131     // Visualizer View
132     private VisualizerView mVisualizerView;
133
134     // Broadcast receiver
135     private PlaybackStatus mPlaybackStatus;
136
137     // Handler used to update the current time
138     private TimeHandler mTimeHandler;
139
140     // Image cache
141     private ImageFetcher mImageFetcher;
142
143     // popup menu for pressing the menu icon
144     private PopupMenu mPopupMenu;
145
146     // Lyrics text view
147     private TextView mLyricsText;
148
149     private long mSelectedId = -1;
150
151     private boolean mIsPaused = false;
152
153     @Override
154     public void onActivityCreated(Bundle savedInstanceState) {
155         super.onActivityCreated(savedInstanceState);
156
157         // Control the media volume
158         getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
159
160         // Bind Apollo's service
161         mToken = MusicUtils.bindToService(getActivity(), this);
162
163         // Initialize the image fetcher/cache
164         mImageFetcher = ApolloUtils.getImageFetcher(getActivity());
165
166         // Initialize the handler used to update the current time
167         mTimeHandler = new TimeHandler(this);
168
169         // Initialize the broadcast receiver
170         mPlaybackStatus = new PlaybackStatus(this);
171     }
172
173     /**
174      * {@inheritDoc}
175      */
176     @Override
177     public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
178                              final Bundle savedInstanceState) {
179         // The View for the fragment's UI
180         mRootView = (ViewGroup) inflater.inflate(R.layout.activity_player_fragment, null);
181
182         // Header title values
183         initHeaderBar();
184
185         initPlaybackControls();
186
187         mVisualizerView = (VisualizerView) mRootView.findViewById(R.id.visualizerView);
188         mVisualizerView.initialize(getActivity());
189         updateVisualizerPowerSaveMode();
190
191         mLyricsText = (TextView) mRootView.findViewById(R.id.audio_player_lyrics);
192
193         return mRootView;
194     }
195
196     /**
197      * {@inheritDoc}
198      */
199     @Override
200     public void onServiceConnected(final ComponentName name, final IBinder service) {
201         // Set the playback drawables
202         updatePlaybackControls();
203         // Setup the adapter
204         createAndSetAdapter();
205         // Current info
206         updateNowPlayingInfo();
207     }
208
209     @Override
210     public void onServiceDisconnected(ComponentName name) {
211     }
212
213     @Override
214     public void onStart() {
215         super.onStart();
216
217         final IntentFilter filter = new IntentFilter();
218         // Play and pause changes
219         filter.addAction(MusicPlaybackService.PLAYSTATE_CHANGED);
220         // Shuffle and repeat changes
221         filter.addAction(MusicPlaybackService.SHUFFLEMODE_CHANGED);
222         filter.addAction(MusicPlaybackService.REPEATMODE_CHANGED);
223         // Track changes
224         filter.addAction(MusicPlaybackService.META_CHANGED);
225         // Update a list, probably the playlist fragment's
226         filter.addAction(MusicPlaybackService.REFRESH);
227         // Listen to changes to the entire queue
228         filter.addAction(MusicPlaybackService.QUEUE_CHANGED);
229         // Listen for lyrics text for the audio track
230         filter.addAction(MusicPlaybackService.NEW_LYRICS);
231         // Listen for power save mode changed
232         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
233         // Register the intent filters
234         getActivity().registerReceiver(mPlaybackStatus, filter);
235         // Refresh the current time
236         final long next = refreshCurrentTime();
237         queueNextRefresh(next);
238
239         // resumes the update callback for the play pause progress button
240         mPlayPauseProgressButton.resume();
241     }
242
243     @Override
244     public void onStop() {
245         super.onStop();
246
247         // pause the update callback for the play pause progress button
248         mPlayPauseProgressButton.pause();
249
250         mImageFetcher.flush();
251     }
252
253     @Override
254     public void onDestroy() {
255         super.onDestroy();
256
257         mIsPaused = false;
258         mTimeHandler.removeMessages(REFRESH_TIME);
259         // Unbind from the service
260         if (mService != null) {
261             MusicUtils.unbindFromService(mToken);
262             mToken = null;
263         }
264
265         // Unregister the receiver
266         try {
267             getActivity().unregisterReceiver(mPlaybackStatus);
268         } catch (final Throwable e) {
269             //$FALL-THROUGH$
270         }
271     }
272
273     /**
274      * Initializes the header bar
275      */
276     private void initHeaderBar() {
277         View headerBar = mRootView.findViewById(R.id.audio_player_header);
278         final int bottomActionBarHeight =
279                 getResources().getDimensionPixelSize(R.dimen.bottom_action_bar_height);
280
281         headerBar.setOutlineProvider(new ViewOutlineProvider() {
282             @Override
283             public void getOutline(View view, Outline outline) {
284                 // since we only want the top and bottom shadows, pad the horizontal width
285                 // to hide the shadows. Can't seem to find a better way to do this
286                 int padWidth = (int)(0.2f * view.getWidth());
287                 outline.setRect(-padWidth, -bottomActionBarHeight, view.getWidth() + padWidth,
288                         view.getHeight());
289             }
290         });
291
292         // Title text
293         mSongTitle = (TextView) mRootView.findViewById(R.id.header_bar_song_title);
294         mArtistName = (TextView) mRootView.findViewById(R.id.header_bar_artist_title);
295
296         // Buttons
297         // Search Button
298         View v = mRootView.findViewById(R.id.header_bar_search_button);
299         v.setOnClickListener(new View.OnClickListener() {
300             @Override
301             public void onClick(View v) {
302                 NavUtils.openSearch(getActivity(), "");
303             }
304         });
305
306         // Add to Playlist Button
307         // Setup the playlist button - add a click listener to show the context
308         mAddToPlaylistButton = (ImageView) mRootView.findViewById(R.id.header_bar_add_button);
309
310         // Create the context menu when requested
311         mAddToPlaylistButton.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
312             @Override
313             public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
314                 MusicUtils.makePlaylistMenu(getActivity(), GROUP_ID, menu);
315                 menu.setHeaderTitle(R.string.add_to_playlist);
316             }
317         });
318
319         // add a click listener to show the context
320         mAddToPlaylistButton.setOnClickListener(new View.OnClickListener() {
321             @Override
322             public void onClick(View v) {
323                 // save the current track id
324                 mSelectedId = MusicUtils.getCurrentAudioId();
325                 mAddToPlaylistButton.showContextMenu();
326             }
327         });
328
329         // Add the menu button
330         // menu button
331         mMenuButton = (ImageView) mRootView.findViewById(R.id.header_bar_menu_button);
332         mMenuButton.setOnClickListener(new View.OnClickListener() {
333             @Override
334             public void onClick(View v) {
335                 showPopupMenu();
336             }
337         });
338     }
339
340     /**
341      * Initializes the items in the now playing screen
342      */
343     private void initPlaybackControls() {
344         mPlayPauseProgressButton = (PlayPauseProgressButton)mRootView.findViewById(R.id.playPauseProgressButton);
345         mPlayPauseProgressButton.setDragEnabled(true);
346         mShuffleButton = (ShuffleButton)mRootView.findViewById(R.id.action_button_shuffle);
347         mRepeatButton = (RepeatButton)mRootView.findViewById(R.id.action_button_repeat);
348         mPreviousButton = (RepeatingImageButton)mRootView.findViewById(R.id.action_button_previous);
349         mNextButton = (RepeatingImageButton)mRootView.findViewById(R.id.action_button_next);
350
351         // Album art view pager
352         mAlbumArtViewPager = (ViewPager)mRootView.findViewById(R.id.audio_player_album_art_viewpager);
353         mAlbumArtViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
354             @Override
355             public void onPageSelected(int position) {
356                 super.onPageSelected(position);
357
358                 int currentPosition = 0;
359                 if (MusicUtils.getShuffleMode() == MusicPlaybackService.SHUFFLE_NONE) {
360                     // if we aren't shuffling, base the position on the queue position
361                     currentPosition = MusicUtils.getQueuePosition();
362                 } else {
363                     // if we are shuffling, use the history size as the position
364                     currentPosition = MusicUtils.getQueueHistorySize();
365                 }
366
367                 // check if we are going to next or previous track
368                 if (position - currentPosition == 1) {
369                     MusicUtils.asyncNext(getActivity());
370                 } else if (position - currentPosition == -1) {
371                     MusicUtils.previous(getActivity(), true);
372                 } else if (currentPosition != position) {
373                     Log.w(TAG, "Unexpected page position of " + position
374                             + " when current is: " + currentPosition);
375                 }
376             }
377         });
378         // view to show in place of album art if queue is empty
379         mQueueEmpty = (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
380         setupNoResultsContainer(mQueueEmpty.getNoResultsContainer());
381
382         // Current time
383         mCurrentTime = (TextView)mRootView.findViewById(R.id.audio_player_current_time);
384         // Total time
385         mTotalTime = (TextView)mRootView.findViewById(R.id.audio_player_total_time);
386
387         // Set the repeat listener for the previous button
388         mPreviousButton.setRepeatListener(mRewindListener);
389         // Set the repeat listener for the next button
390         mNextButton.setRepeatListener(mFastForwardListener);
391
392         mPlayPauseProgressButton.enableAndShow();
393     }
394
395     private void setupNoResultsContainer(NoResultsContainer empty) {
396         int color = getResources().getColor(R.color.no_results_light);
397         empty.setTextColor(color);
398         empty.setMainText(R.string.empty_queue_main);
399         empty.setSecondaryText(R.string.empty_queue_secondary);
400     }
401
402     /**
403      * Sets the track name, album name, and album art.
404      */
405     private void updateNowPlayingInfo() {
406         // Set the track name
407         mSongTitle.setText(MusicUtils.getTrackName());
408         mArtistName.setText(MusicUtils.getArtistName());
409
410         // Set the total time
411         String totalTime = MusicUtils.makeShortTimeString(getActivity(), MusicUtils.duration() / 1000);
412         if (!mTotalTime.getText().equals(totalTime)) {
413             mTotalTime.setText(totalTime);
414         }
415
416         if (MusicUtils.getRepeatMode() == MusicPlaybackService.REPEAT_CURRENT) {
417             // we are repeating 1 so just jump to the 1st and only item
418             mAlbumArtViewPager.setCurrentItem(0, false);
419         } else if (MusicUtils.getShuffleMode() == MusicPlaybackService.SHUFFLE_NONE) {
420             // we are playing in-order, base the position on the queue position
421             mAlbumArtViewPager.setCurrentItem(MusicUtils.getQueuePosition(), true);
422         } else {
423             // if we are shuffling, just based our index based on the history
424             mAlbumArtViewPager.setCurrentItem(MusicUtils.getQueueHistorySize(), true);
425         }
426
427         // Update the current time
428         queueNextRefresh(1);
429     }
430
431     /**
432      * This creates the adapter based on the repeat and shuffle configuration and sets it into the
433      * page adapter
434      */
435     private void createAndSetAdapter() {
436         mAlbumArtPagerAdapter = new AlbumArtPagerAdapter(getChildFragmentManager());
437
438         int repeatMode = MusicUtils.getRepeatMode();
439         int targetSize = 0;
440         int targetIndex = 0;
441         int queueSize = MusicUtils.getQueueSize();
442
443         if (repeatMode == MusicPlaybackService.REPEAT_CURRENT) {
444             targetSize = 1;
445             targetIndex = 0;
446         } else if (MusicUtils.getShuffleMode() == MusicPlaybackService.SHUFFLE_NONE) {
447             // if we aren't shuffling, use the queue to determine where we are
448             targetSize = queueSize;
449             targetIndex = MusicUtils.getQueuePosition();
450         } else {
451             // otherwise, set it to the max history size
452             targetSize = MusicPlaybackService.MAX_HISTORY_SIZE;
453             targetIndex = MusicUtils.getQueueHistorySize();
454         }
455
456         mAlbumArtPagerAdapter.setPlaylistLength(targetSize);
457         mAlbumArtViewPager.setAdapter(mAlbumArtPagerAdapter);
458         mAlbumArtViewPager.setCurrentItem(targetIndex);
459
460         if(queueSize == 0) {
461             mAlbumArtViewPager.setVisibility(View.GONE);
462             mQueueEmpty.showNoResults();
463             mAddToPlaylistButton.setVisibility(View.GONE);
464         } else {
465             mAlbumArtViewPager.setVisibility(View.VISIBLE);
466             mQueueEmpty.hideAll();
467             mAddToPlaylistButton.setVisibility(View.VISIBLE);
468         }
469     }
470
471     /**
472      * Sets the correct drawable states for the playback controls.
473      */
474     private void updatePlaybackControls() {
475         // Set the play and pause image
476         mPlayPauseProgressButton.getPlayPauseButton().updateState();
477         // Set the shuffle image
478         mShuffleButton.updateShuffleState();
479         // Set the repeat image
480         mRepeatButton.updateRepeatState();
481     }
482
483     /**
484      * @param delay When to update
485      */
486     private void queueNextRefresh(final long delay) {
487         if (!mIsPaused) {
488             final Message message = mTimeHandler.obtainMessage(REFRESH_TIME);
489             mTimeHandler.removeMessages(REFRESH_TIME);
490             mTimeHandler.sendMessageDelayed(message, delay);
491         }
492     }
493
494     /**
495      * Used to seek forwards or backwards in time through the current track
496      *
497      * @param repcnt The repeat count
498      * @param delta The long press duration
499      * @param forwards Whether it was seeking forwards or backwards
500      */
501     private void seekRelative(final int repcnt, long delta, boolean forwards) {
502         if (mService == null) {
503             return;
504         }
505         if (repcnt > 0) {
506             final long EXTRA_FAST_CUTOFF = 10000;
507             if (delta < EXTRA_FAST_CUTOFF) {
508                 // seek at 10x speed for the first 5 seconds
509                 delta = delta * 10;
510             } else {
511                 // seek at 40x after that
512                 delta = EXTRA_FAST_CUTOFF * 10 + (delta - EXTRA_FAST_CUTOFF) * 40;
513             }
514
515             MusicUtils.seekRelative(forwards ? delta : -delta);
516
517             refreshCurrentTime();
518         }
519     }
520
521     private void refreshCurrentTimeText(final long pos) {
522         if (mPlayPauseProgressButton.isDragging()) {
523             mCurrentTime.setText(MusicUtils.makeShortTimeString(getActivity(),
524                     mPlayPauseProgressButton.getDragProgressInMs() / 1000));
525         } else {
526             mCurrentTime.setText(MusicUtils.makeShortTimeString(getActivity(), pos / 1000));
527         }
528     }
529
530     /* Used to update the current time string */
531     private long refreshCurrentTime() {
532         if (mService == null) {
533             return MusicUtils.UPDATE_FREQUENCY_MS;
534         }
535         try {
536             final long pos = MusicUtils.position();
537             if (pos >= 0 && MusicUtils.duration() > 0) {
538                 refreshCurrentTimeText(pos);
539
540                 if (mPlayPauseProgressButton.isDragging()) {
541                     mCurrentTime.setVisibility(View.VISIBLE);
542                     return MusicUtils.UPDATE_FREQUENCY_FAST_MS;
543                 } else if (MusicUtils.isPlaying()) {
544                     mCurrentTime.setVisibility(View.VISIBLE);
545
546                     // calculate the number of milliseconds until the next full second,
547                     // so the counter can be updated at just the right time
548                     return Math.max(20, 1000 - pos % 1000);
549                 } else {
550                     // blink the counter
551                     final int vis = mCurrentTime.getVisibility();
552                     mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE
553                             : View.INVISIBLE);
554                 }
555             } else {
556                 mCurrentTime.setText("--:--");
557             }
558         } catch (final Exception ignored) {
559             if (ignored.getMessage() != null) {
560                 Log.e(TAG, ignored.getMessage());
561             }
562         }
563         return MusicUtils.UPDATE_FREQUENCY_MS;
564     }
565
566     /**
567      * Used to scan backwards through the track
568      */
569     private final RepeatingImageButton.RepeatListener mRewindListener = new RepeatingImageButton.RepeatListener() {
570         /**
571          * {@inheritDoc}
572          */
573         @Override
574         public void onRepeat(final View v, final long howlong, final int repcnt) {
575             seekRelative(repcnt, howlong, false);
576         }
577     };
578
579     /**
580      * Used to scan ahead through the track
581      */
582     private final RepeatingImageButton.RepeatListener mFastForwardListener = new RepeatingImageButton.RepeatListener() {
583         /**
584          * {@inheritDoc}
585          */
586         @Override
587         public void onRepeat(final View v, final long howlong, final int repcnt) {
588             seekRelative(repcnt, howlong, true);
589         }
590     };
591
592     public void showPopupMenu() {
593         // create the popup menu
594         if (mPopupMenu == null) {
595             mPopupMenu = new PopupMenu(getActivity(), mMenuButton);
596             mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
597                 @Override
598                 public boolean onMenuItemClick(MenuItem item) {
599                     return onPopupMenuItemClick(item);
600                 }
601             });
602         }
603
604         final Menu menu = mPopupMenu.getMenu();
605         final MenuInflater inflater = mPopupMenu.getMenuInflater();
606         menu.clear();
607
608         // Shuffle all
609         inflater.inflate(R.menu.shuffle_all, menu);
610         if (MusicUtils.getQueueSize() > 0) {
611             // ringtone, and equalizer
612             inflater.inflate(R.menu.audio_player, menu);
613
614             if (!NavUtils.hasEffectsPanel(getActivity())) {
615                 menu.removeItem(R.id.menu_audio_player_equalizer);
616             }
617
618             // save queue/clear queue
619             inflater.inflate(R.menu.queue, menu);
620         }
621         // Settings
622         inflater.inflate(R.menu.activity_base, menu);
623
624         // show the popup
625         mPopupMenu.show();
626     }
627
628     public boolean onPopupMenuItemClick(final MenuItem item) {
629         switch (item.getItemId()) {
630             case R.id.menu_shuffle_all:
631                 // Shuffle all the songs
632                 MusicUtils.shuffleAll(getActivity());
633                 return true;
634             case R.id.menu_audio_player_ringtone:
635                 // Set the current track as a ringtone
636                 MusicUtils.setRingtone(getActivity(), MusicUtils.getCurrentAudioId());
637                 return true;
638             case R.id.menu_audio_player_equalizer:
639                 // Sound effects
640                 NavUtils.openEffectsPanel(getActivity(), HomeActivity.EQUALIZER);
641                 return true;
642             case R.id.menu_settings:
643                 // Settings
644                 NavUtils.openSettings(getActivity());
645                 return true;
646             case R.id.menu_audio_player_more_by_artist:
647                 NavUtils.openArtistProfile(getActivity(), MusicUtils.getArtistName());
648                 return true;
649             case R.id.menu_audio_player_delete:
650                 // Delete current song
651                 DeleteDialog.newInstance(MusicUtils.getTrackName(), new long[]{
652                         MusicUtils.getCurrentAudioId()
653                 }, null).show(getActivity().getSupportFragmentManager(), "DeleteDialog");
654                 return true;
655             case R.id.menu_save_queue:
656                 NowPlayingCursor queue = (NowPlayingCursor) QueueLoader
657                         .makeQueueCursor(getActivity());
658                 CreateNewPlaylist.getInstance(MusicUtils.getSongListForCursor(queue)).show(
659                         getFragmentManager(), "CreatePlaylist");
660                 queue.close();
661                 return true;
662             case R.id.menu_clear_queue:
663                 MusicUtils.clearQueue();
664                 return true;
665             default:
666                 break;
667         }
668
669         return false;
670     }
671
672     public void dismissPopupMenu() {
673         if (mPopupMenu != null) {
674             mPopupMenu.dismiss();
675         }
676     }
677
678     @Override
679     public boolean onContextItemSelected(MenuItem item) {
680         if (item.getGroupId() == GROUP_ID) {
681             switch (item.getItemId()) {
682                 case FragmentMenuItems.NEW_PLAYLIST:
683                     CreateNewPlaylist.getInstance(new long[]{
684                             mSelectedId
685                     }).show(getFragmentManager(), "CreatePlaylist");
686                     return true;
687                 case FragmentMenuItems.PLAYLIST_SELECTED:
688                     final long mPlaylistId = item.getIntent().getLongExtra("playlist", 0);
689                     MusicUtils.addToPlaylist(getActivity(), new long[]{
690                             mSelectedId
691                     }, mPlaylistId);
692                     return true;
693                 default:
694                     break;
695             }
696         }
697
698         return super.onContextItemSelected(item);
699     }
700
701     public void onLyrics(String lyrics) {
702         if (TextUtils.isEmpty(lyrics)
703                 || !PreferenceUtils.getInstance(getActivity()).getShowLyrics()) {
704             mLyricsText.animate().alpha(0).setDuration(200);
705         } else {
706             lyrics = lyrics.replace("\n", "<br/>");
707             Spanned span = Html.fromHtml(lyrics);
708             mLyricsText.setText(span);
709
710             mLyricsText.animate().alpha(1).setDuration(200);
711         }
712     }
713
714     public void setVisualizerVisible(boolean visible) {
715         if (visible && PreferenceUtils.getInstance(getActivity()).getShowVisualizer()) {
716             if (PreferenceUtils.canRecordAudio(getActivity())) {
717                 mVisualizerView.setVisible(true);
718             } else {
719                 PreferenceUtils.requestRecordAudio(getActivity());
720             }
721         } else {
722             mVisualizerView.setVisible(false);
723         }
724     }
725
726     public void updateVisualizerPowerSaveMode() {
727         PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
728         mVisualizerView.setPowerSaveMode(pm.isPowerSaveMode());
729     }
730
731     public void setVisualizerColor(int color) {
732         mVisualizerView.setColor(color);
733     }
734
735     /**
736      * Used to update the current time string
737      */
738     private static final class TimeHandler extends Handler {
739
740         private final WeakReference<AudioPlayerFragment> mAudioPlayer;
741
742         /**
743          * Constructor of <code>TimeHandler</code>
744          */
745         public TimeHandler(final AudioPlayerFragment player) {
746             mAudioPlayer = new WeakReference<AudioPlayerFragment>(player);
747         }
748
749         @Override
750         public void handleMessage(final Message msg) {
751             switch (msg.what) {
752                 case REFRESH_TIME:
753                     final long next = mAudioPlayer.get().refreshCurrentTime();
754                     mAudioPlayer.get().queueNextRefresh(next);
755                     break;
756                 default:
757                     break;
758             }
759         }
760     };
761
762     /**
763      * Used to monitor the state of playback
764      */
765     private static final class PlaybackStatus extends BroadcastReceiver {
766
767         private final WeakReference<AudioPlayerFragment> mReference;
768
769         /**
770          * Constructor of <code>PlaybackStatus</code>
771          */
772         public PlaybackStatus(final AudioPlayerFragment fragment) {
773             mReference = new WeakReference<AudioPlayerFragment>(fragment);
774         }
775
776         /**
777          * {@inheritDoc}
778          */
779         @Override
780         public void onReceive(final Context context, final Intent intent) {
781             final AudioPlayerFragment audioPlayerFragment = mReference.get();
782             final String action = intent.getAction();
783             if (action.equals(MusicPlaybackService.META_CHANGED)) {
784                 // if we are repeating current and the track has changed, re-create the adapter
785                 if (MusicUtils.getRepeatMode() == MusicPlaybackService.REPEAT_CURRENT) {
786                     mReference.get().createAndSetAdapter();
787                 }
788
789                 // Current info
790                 audioPlayerFragment.updateNowPlayingInfo();
791                 audioPlayerFragment.dismissPopupMenu();
792             } else if (action.equals(MusicPlaybackService.PLAYSTATE_CHANGED)) {
793                 // Set the play and pause image
794                 audioPlayerFragment.mPlayPauseProgressButton.getPlayPauseButton().updateState();
795                 audioPlayerFragment.mVisualizerView.setPlaying(MusicUtils.isPlaying());
796             } else if (action.equals(MusicPlaybackService.REPEATMODE_CHANGED)
797                     || action.equals(MusicPlaybackService.SHUFFLEMODE_CHANGED)) {
798                 // Set the repeat image
799                 audioPlayerFragment.mRepeatButton.updateRepeatState();
800                 // Set the shuffle image
801                 audioPlayerFragment.mShuffleButton.updateShuffleState();
802
803                 // Update the queue
804                 audioPlayerFragment.createAndSetAdapter();
805             } else if (action.equals(MusicPlaybackService.QUEUE_CHANGED)) {
806                 audioPlayerFragment.createAndSetAdapter();
807             } else if (action.equals(MusicPlaybackService.NEW_LYRICS)) {
808                 audioPlayerFragment.onLyrics(intent.getStringExtra("lyrics"));
809             } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
810                 audioPlayerFragment.updateVisualizerPowerSaveMode();
811             }
812         }
813     }
814 }