OSDN Git Service

Repackaged com.andrew.apollo to com.cyngn.eleven
[android-x86/packages-apps-Eleven.git] / src / com / cyngn / eleven / ui / activities / AudioPlayerActivity.java
1 /*
2  * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0
3  * (the "License"); you may not use this file except in compliance with the
4  * License. You may obtain a copy of the License at
5  * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6  * or agreed to in writing, software distributed under the License is
7  * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8  * KIND, either express or implied. See the License for the specific language
9  * governing permissions and limitations under the License.
10  */
11
12 package com.cyngn.eleven.ui.activities;
13
14 import static com.cyngn.eleven.utils.MusicUtils.mService;
15
16 import android.animation.ObjectAnimator;
17 import android.app.ActionBar;
18 import android.app.SearchManager;
19 import android.app.SearchableInfo;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.ServiceConnection;
26 import android.media.AudioManager;
27 import android.media.audiofx.AudioEffect;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.IBinder;
32 import android.os.Message;
33 import android.os.SystemClock;
34 import android.provider.MediaStore.Audio.Playlists;
35 import android.provider.MediaStore.Audio.Albums;
36 import android.provider.MediaStore.Audio.Artists;
37 import android.support.v4.app.FragmentActivity;
38 import android.support.v4.view.ViewPager;
39 import android.view.Menu;
40 import android.view.MenuItem;
41 import android.view.View;
42 import android.view.View.OnClickListener;
43 import android.view.animation.AnimationUtils;
44 import android.widget.FrameLayout;
45 import android.widget.ImageView;
46 import android.widget.LinearLayout;
47 import android.widget.SearchView;
48 import android.widget.SearchView.OnQueryTextListener;
49 import android.widget.SeekBar;
50 import android.widget.SeekBar.OnSeekBarChangeListener;
51 import android.widget.TextView;
52
53 import com.cyngn.eleven.IElevenService;
54 import com.cyngn.eleven.MusicPlaybackService;
55 import com.cyngn.eleven.R;
56 import com.cyngn.eleven.adapters.PagerAdapter;
57 import com.cyngn.eleven.cache.ImageFetcher;
58 import com.cyngn.eleven.ui.fragments.QueueFragment;
59 import com.cyngn.eleven.menu.DeleteDialog;
60 import com.cyngn.eleven.utils.ApolloUtils;
61 import com.cyngn.eleven.utils.MusicUtils;
62 import com.cyngn.eleven.utils.MusicUtils.ServiceToken;
63 import com.cyngn.eleven.utils.NavUtils;
64 import com.cyngn.eleven.utils.ThemeUtils;
65 import com.cyngn.eleven.widgets.PlayPauseButton;
66 import com.cyngn.eleven.widgets.RepeatButton;
67 import com.cyngn.eleven.widgets.RepeatingImageButton;
68 import com.cyngn.eleven.widgets.ShuffleButton;
69
70 import java.lang.ref.WeakReference;
71
72 /**
73  * Apollo's "now playing" interface.
74  * 
75  * @author Andrew Neal (andrewdneal@gmail.com)
76  */
77 public class AudioPlayerActivity extends FragmentActivity implements ServiceConnection,
78         OnSeekBarChangeListener, DeleteDialog.DeleteDialogCallback {
79
80     // Message to refresh the time
81     private static final int REFRESH_TIME = 1;
82
83     // The service token
84     private ServiceToken mToken;
85
86     // Play and pause button
87     private PlayPauseButton mPlayPauseButton;
88
89     // Repeat button
90     private RepeatButton mRepeatButton;
91
92     // Shuffle button
93     private ShuffleButton mShuffleButton;
94
95     // Previous button
96     private RepeatingImageButton mPreviousButton;
97
98     // Next button
99     private RepeatingImageButton mNextButton;
100
101     // Track name
102     private TextView mTrackName;
103
104     // Artist name
105     private TextView mArtistName;
106
107     // Album art
108     private ImageView mAlbumArt;
109
110     // Tiny artwork
111     private ImageView mAlbumArtSmall;
112
113     // Current time
114     private TextView mCurrentTime;
115
116     // Total time
117     private TextView mTotalTime;
118
119     // Queue switch
120     private ImageView mQueueSwitch;
121
122     // Progess
123     private SeekBar mProgress;
124
125     // Broadcast receiver
126     private PlaybackStatus mPlaybackStatus;
127
128     // Handler used to update the current time
129     private TimeHandler mTimeHandler;
130
131     // View pager
132     private ViewPager mViewPager;
133
134     // Pager adpater
135     private PagerAdapter mPagerAdapter;
136
137     // ViewPager container
138     private FrameLayout mPageContainer;
139
140     // Header
141     private LinearLayout mAudioPlayerHeader;
142
143     // Image cache
144     private ImageFetcher mImageFetcher;
145
146     // Theme resources
147     private ThemeUtils mResources;
148
149     private long mPosOverride = -1;
150
151     private long mStartSeekPos = 0;
152
153     private long mLastSeekEventTime;
154
155     private long mLastShortSeekEventTime;
156
157     private boolean mIsPaused = false;
158
159     private boolean mFromTouch = false;
160
161     /**
162      * {@inheritDoc}
163      */
164     @Override
165     protected void onCreate(final Bundle savedInstanceState) {
166         super.onCreate(savedInstanceState);
167
168         // Initialze the theme resources
169         mResources = new ThemeUtils(this);
170         // Set the overflow style
171         mResources.setOverflowStyle(this);
172
173         // Fade it in
174         overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
175
176         // Control the media volume
177         setVolumeControlStream(AudioManager.STREAM_MUSIC);
178
179         // Bind Apollo's service
180         mToken = MusicUtils.bindToService(this, this);
181
182         // Initialize the image fetcher/cache
183         mImageFetcher = ApolloUtils.getImageFetcher(this);
184
185         // Initialize the handler used to update the current time
186         mTimeHandler = new TimeHandler(this);
187
188         // Initialize the broadcast receiver
189         mPlaybackStatus = new PlaybackStatus(this);
190
191         // Theme the action bar
192         final ActionBar actionBar = getActionBar();
193         mResources.themeActionBar(actionBar, getString(R.string.app_name));
194         actionBar.setDisplayHomeAsUpEnabled(true);
195
196         // Set the layout
197         setContentView(R.layout.activity_player_base);
198
199         // Cache all the items
200         initPlaybackControls();
201     }
202
203     /**
204      * {@inheritDoc}
205      */
206     @Override
207     public void onNewIntent(Intent intent) {
208         setIntent(intent);
209         startPlayback();
210     }
211
212     /**
213      * {@inheritDoc}
214      */
215     @Override
216     public void onServiceConnected(final ComponentName name, final IBinder service) {
217         mService = IElevenService.Stub.asInterface(service);
218         // Check whether we were asked to start any playback
219         startPlayback();
220         // Set the playback drawables
221         updatePlaybackControls();
222         // Current info
223         updateNowPlayingInfo();
224         // Update the favorites icon
225         invalidateOptionsMenu();
226     }
227
228     /**
229      * {@inheritDoc}
230      */
231     @Override
232     public void onServiceDisconnected(final ComponentName name) {
233         mService = null;
234     }
235
236     /**
237      * {@inheritDoc}
238      */
239     @Override
240     public void onProgressChanged(final SeekBar bar, final int progress, final boolean fromuser) {
241         if (!fromuser || mService == null) {
242             return;
243         }
244         final long now = SystemClock.elapsedRealtime();
245         if (now - mLastSeekEventTime > 250) {
246             mLastSeekEventTime = now;
247             mLastShortSeekEventTime = now;
248             mPosOverride = MusicUtils.duration() * progress / 1000;
249             MusicUtils.seek(mPosOverride);
250             if (!mFromTouch) {
251                 // refreshCurrentTime();
252                 mPosOverride = -1;
253             }
254         } else if (now - mLastShortSeekEventTime > 5) {
255             mLastShortSeekEventTime = now;
256             mPosOverride = MusicUtils.duration() * progress / 1000;
257             refreshCurrentTimeText(mPosOverride);
258         }
259     }
260
261     /**
262      * {@inheritDoc}
263      */
264     @Override
265     public void onStartTrackingTouch(final SeekBar bar) {
266         mLastSeekEventTime = 0;
267         mFromTouch = true;
268         mCurrentTime.setVisibility(View.VISIBLE);
269     }
270
271     /**
272      * {@inheritDoc}
273      */
274     @Override
275     public void onStopTrackingTouch(final SeekBar bar) {
276         if (mPosOverride != -1) {
277             MusicUtils.seek(mPosOverride);
278         }
279         mPosOverride = -1;
280         mFromTouch = false;
281     }
282
283     /**
284      * {@inheritDoc}
285      */
286     @Override
287     public boolean onPrepareOptionsMenu(final Menu menu) {
288         // Hide the EQ option if it can't be opened
289         final Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
290         if (getPackageManager().resolveActivity(intent, 0) == null) {
291             final MenuItem effects = menu.findItem(R.id.menu_audio_player_equalizer);
292             effects.setVisible(false);
293         }
294         mResources.setFavoriteIcon(menu);
295         return true;
296     }
297
298     /**
299      * {@inheritDoc}
300      */
301     @Override
302     public boolean onCreateOptionsMenu(final Menu menu) {
303         // Search view
304         getMenuInflater().inflate(R.menu.search, menu);
305         // Theme the search icon
306         mResources.setSearchIcon(menu);
307
308         final SearchView searchView = (SearchView)menu.findItem(R.id.menu_search).getActionView();
309         // Add voice search
310         final SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
311         final SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
312         searchView.setSearchableInfo(searchableInfo);
313         // Perform the search
314         searchView.setOnQueryTextListener(new OnQueryTextListener() {
315
316             @Override
317             public boolean onQueryTextSubmit(final String query) {
318                 // Open the search activity
319                 NavUtils.openSearch(AudioPlayerActivity.this, query);
320                 return true;
321             }
322
323             @Override
324             public boolean onQueryTextChange(final String newText) {
325                 // Nothing to do
326                 return false;
327             }
328         });
329
330         // Favorite action
331         getMenuInflater().inflate(R.menu.favorite, menu);
332         // Shuffle all
333         getMenuInflater().inflate(R.menu.shuffle, menu);
334         // Share, ringtone, and equalizer
335         getMenuInflater().inflate(R.menu.audio_player, menu);
336         // Settings
337         getMenuInflater().inflate(R.menu.activity_base, menu);
338         return true;
339     }
340
341     /**
342      * {@inheritDoc}
343      */
344     @Override
345     public boolean onOptionsItemSelected(final MenuItem item) {
346         switch (item.getItemId()) {
347             case android.R.id.home:
348                 // Go back to the home activity
349                 NavUtils.goHome(this);
350                 return true;
351             case R.id.menu_shuffle:
352                 // Shuffle all the songs
353                 MusicUtils.shuffleAll(this);
354                 // Refresh the queue
355                 ((QueueFragment)mPagerAdapter.getFragment(0)).refreshQueue();
356                 return true;
357             case R.id.menu_favorite:
358                 // Toggle the current track as a favorite and update the menu
359                 // item
360                 MusicUtils.toggleFavorite();
361                 invalidateOptionsMenu();
362                 return true;
363             case R.id.menu_audio_player_ringtone:
364                 // Set the current track as a ringtone
365                 MusicUtils.setRingtone(this, MusicUtils.getCurrentAudioId());
366                 return true;
367             case R.id.menu_audio_player_share:
368                 // Share the current meta data
369                 shareCurrentTrack();
370                 return true;
371             case R.id.menu_audio_player_equalizer:
372                 // Sound effects
373                 NavUtils.openEffectsPanel(this);
374                 return true;
375             case R.id.menu_settings:
376                 // Settings
377                 NavUtils.openSettings(this);
378                 return true;
379             case R.id.menu_audio_player_delete:
380                 // Delete current song
381                 DeleteDialog.newInstance(MusicUtils.getTrackName(), new long[] {
382                     MusicUtils.getCurrentAudioId()
383                 }, null).show(getSupportFragmentManager(), "DeleteDialog");
384                 return true;
385             default:
386                 break;
387         }
388         return super.onOptionsItemSelected(item);
389     }
390
391     @Override
392     public void onDelete(long[] ids) {
393         ((QueueFragment)mPagerAdapter.getFragment(0)).refreshQueue();
394         if (MusicUtils.getQueue().length == 0) {
395             NavUtils.goHome(this);
396         }
397     }
398
399     /**
400      * {@inheritDoc}
401      */
402     @Override
403     public void onBackPressed() {
404         super.onBackPressed();
405         NavUtils.goHome(this);
406     }
407
408     /**
409      * {@inheritDoc}
410      */
411     @Override
412     protected void onResume() {
413         super.onResume();
414         // Set the playback drawables
415         updatePlaybackControls();
416         // Current info
417         updateNowPlayingInfo();
418         // Refresh the queue
419         ((QueueFragment)mPagerAdapter.getFragment(0)).refreshQueue();
420     }
421
422     /**
423      * {@inheritDoc}
424      */
425     @Override
426     protected void onStart() {
427         super.onStart();
428         final IntentFilter filter = new IntentFilter();
429         // Play and pause changes
430         filter.addAction(MusicPlaybackService.PLAYSTATE_CHANGED);
431         // Shuffle and repeat changes
432         filter.addAction(MusicPlaybackService.SHUFFLEMODE_CHANGED);
433         filter.addAction(MusicPlaybackService.REPEATMODE_CHANGED);
434         // Track changes
435         filter.addAction(MusicPlaybackService.META_CHANGED);
436         // Update a list, probably the playlist fragment's
437         filter.addAction(MusicPlaybackService.REFRESH);
438         registerReceiver(mPlaybackStatus, filter);
439         // Refresh the current time
440         final long next = refreshCurrentTime();
441         queueNextRefresh(next);
442         MusicUtils.notifyForegroundStateChanged(this, true);
443     }
444
445     /**
446      * {@inheritDoc}
447      */
448     @Override
449     protected void onStop() {
450         super.onStop();
451         MusicUtils.notifyForegroundStateChanged(this, false);
452         mImageFetcher.flush();
453     }
454
455     /**
456      * {@inheritDoc}
457      */
458     @Override
459     protected void onDestroy() {
460         super.onDestroy();
461         mIsPaused = false;
462         mTimeHandler.removeMessages(REFRESH_TIME);
463         // Unbind from the service
464         if (mService != null) {
465             MusicUtils.unbindFromService(mToken);
466             mToken = null;
467         }
468
469         // Unregister the receiver
470         try {
471             unregisterReceiver(mPlaybackStatus);
472         } catch (final Throwable e) {
473             //$FALL-THROUGH$
474         }
475     }
476
477     /**
478      * Initializes the items in the now playing screen
479      */
480     @SuppressWarnings("deprecation")
481     private void initPlaybackControls() {
482         // ViewPager container
483         mPageContainer = (FrameLayout)findViewById(R.id.audio_player_pager_container);
484         // Theme the pager container background
485         mPageContainer
486                 .setBackgroundDrawable(mResources.getDrawable("audio_player_pager_container"));
487
488         // Now playing header
489         mAudioPlayerHeader = (LinearLayout)findViewById(R.id.audio_player_header);
490         // Opens the currently playing album profile
491         mAudioPlayerHeader.setOnClickListener(mOpenAlbumProfile);
492
493         // Used to hide the artwork and show the queue
494         final FrameLayout mSwitch = (FrameLayout)findViewById(R.id.audio_player_switch);
495         mSwitch.setOnClickListener(mToggleHiddenPanel);
496
497         // Initialize the pager adapter
498         mPagerAdapter = new PagerAdapter(this);
499         // Queue
500         mPagerAdapter.add(QueueFragment.class, null);
501
502         // Initialize the ViewPager
503         mViewPager = (ViewPager)findViewById(R.id.audio_player_pager);
504         // Attch the adapter
505         mViewPager.setAdapter(mPagerAdapter);
506         // Offscreen pager loading limit
507         mViewPager.setOffscreenPageLimit(mPagerAdapter.getCount() - 1);
508         // Play and pause button
509         mPlayPauseButton = (PlayPauseButton)findViewById(R.id.action_button_play);
510         // Shuffle button
511         mShuffleButton = (ShuffleButton)findViewById(R.id.action_button_shuffle);
512         // Repeat button
513         mRepeatButton = (RepeatButton)findViewById(R.id.action_button_repeat);
514         // Previous button
515         mPreviousButton = (RepeatingImageButton)findViewById(R.id.action_button_previous);
516         // Next button
517         mNextButton = (RepeatingImageButton)findViewById(R.id.action_button_next);
518         // Track name
519         mTrackName = (TextView)findViewById(R.id.audio_player_track_name);
520         // Artist name
521         mArtistName = (TextView)findViewById(R.id.audio_player_artist_name);
522         // Album art
523         mAlbumArt = (ImageView)findViewById(R.id.audio_player_album_art);
524         // Small album art
525         mAlbumArtSmall = (ImageView)findViewById(R.id.audio_player_switch_album_art);
526         // Current time
527         mCurrentTime = (TextView)findViewById(R.id.audio_player_current_time);
528         // Total time
529         mTotalTime = (TextView)findViewById(R.id.audio_player_total_time);
530         // Used to show and hide the queue fragment
531         mQueueSwitch = (ImageView)findViewById(R.id.audio_player_switch_queue);
532         // Theme the queue switch icon
533         mQueueSwitch.setImageDrawable(mResources.getDrawable("btn_switch_queue"));
534         // Progress
535         mProgress = (SeekBar)findViewById(android.R.id.progress);
536
537         // Set the repeat listner for the previous button
538         mPreviousButton.setRepeatListener(mRewindListener);
539         // Set the repeat listner for the next button
540         mNextButton.setRepeatListener(mFastForwardListener);
541         // Update the progress
542         mProgress.setOnSeekBarChangeListener(this);
543     }
544
545     /**
546      * Sets the track name, album name, and album art.
547      */
548     private void updateNowPlayingInfo() {
549         // Set the track name
550         mTrackName.setText(MusicUtils.getTrackName());
551         // Set the artist name
552         mArtistName.setText(MusicUtils.getArtistName());
553         // Set the total time
554         mTotalTime.setText(MusicUtils.makeTimeString(this, MusicUtils.duration() / 1000));
555         // Set the album art
556         mImageFetcher.loadCurrentArtwork(mAlbumArt);
557         // Set the small artwork
558         mImageFetcher.loadCurrentArtwork(mAlbumArtSmall);
559         // Update the current time
560         queueNextRefresh(1);
561
562     }
563
564     private long parseIdFromIntent(Intent intent, String longKey,
565         String stringKey, long defaultId) {
566         long id = intent.getLongExtra(longKey, -1);
567         if (id < 0) {
568             String idString = intent.getStringExtra(stringKey);
569             if (idString != null) {
570                 try {
571                     id = Long.parseLong(idString);
572                 } catch (NumberFormatException e) {
573                     // ignore
574                 }
575             }
576         }
577         return id;
578     }
579
580     /**
581      * Checks whether the passed intent contains a playback request,
582      * and starts playback if that's the case
583      */
584     private void startPlayback() {
585         Intent intent = getIntent();
586
587         if (intent == null || mService == null) {
588             return;
589         }
590
591         Uri uri = intent.getData();
592         String mimeType = intent.getType();
593         boolean handled = false;
594
595         if (uri != null && uri.toString().length() > 0) {
596             MusicUtils.playFile(this, uri);
597             handled = true;
598         } else if (Playlists.CONTENT_TYPE.equals(mimeType)) {
599             long id = parseIdFromIntent(intent, "playlistId", "playlist", -1);
600             if (id >= 0) {
601                 MusicUtils.playPlaylist(this, id);
602                 handled = true;
603             }
604         } else if (Albums.CONTENT_TYPE.equals(mimeType)) {
605             long id = parseIdFromIntent(intent, "albumId", "album", -1);
606             if (id >= 0) {
607                 int position = intent.getIntExtra("position", 0);
608                 MusicUtils.playAlbum(this, id, position);
609                 handled = true;
610             }
611         } else if (Artists.CONTENT_TYPE.equals(mimeType)) {
612             long id = parseIdFromIntent(intent, "artistId", "artist", -1);
613             if (id >= 0) {
614                 int position = intent.getIntExtra("position", 0);
615                 MusicUtils.playArtist(this, id, position);
616                 handled = true;
617             }
618         }
619
620         if (handled) {
621             // Make sure to process intent only once
622             setIntent(new Intent());
623             // Refresh the queue
624             ((QueueFragment)mPagerAdapter.getFragment(0)).refreshQueue();
625         }
626     }
627
628     /**
629      * Sets the correct drawable states for the playback controls.
630      */
631     private void updatePlaybackControls() {
632         // Set the play and pause image
633         mPlayPauseButton.updateState();
634         // Set the shuffle image
635         mShuffleButton.updateShuffleState();
636         // Set the repeat image
637         mRepeatButton.updateRepeatState();
638     }
639
640     /**
641      * @param delay When to update
642      */
643     private void queueNextRefresh(final long delay) {
644         if (!mIsPaused) {
645             final Message message = mTimeHandler.obtainMessage(REFRESH_TIME);
646             mTimeHandler.removeMessages(REFRESH_TIME);
647             mTimeHandler.sendMessageDelayed(message, delay);
648         }
649     }
650
651     /**
652      * Used to scan backwards in time through the curren track
653      * 
654      * @param repcnt The repeat count
655      * @param delta The long press duration
656      */
657     private void scanBackward(final int repcnt, long delta) {
658         if (mService == null) {
659             return;
660         }
661         if (repcnt == 0) {
662             mStartSeekPos = MusicUtils.position();
663             mLastSeekEventTime = 0;
664         } else {
665             if (delta < 5000) {
666                 // seek at 10x speed for the first 5 seconds
667                 delta = delta * 10;
668             } else {
669                 // seek at 40x after that
670                 delta = 50000 + (delta - 5000) * 40;
671             }
672             long newpos = mStartSeekPos - delta;
673             if (newpos < 0) {
674                 // move to previous track
675                 MusicUtils.previous(this);
676                 final long duration = MusicUtils.duration();
677                 mStartSeekPos += duration;
678                 newpos += duration;
679             }
680             if (delta - mLastSeekEventTime > 250 || repcnt < 0) {
681                 MusicUtils.seek(newpos);
682                 mLastSeekEventTime = delta;
683             }
684             if (repcnt >= 0) {
685                 mPosOverride = newpos;
686             } else {
687                 mPosOverride = -1;
688             }
689             refreshCurrentTime();
690         }
691     }
692
693     /**
694      * Used to scan forwards in time through the curren track
695      * 
696      * @param repcnt The repeat count
697      * @param delta The long press duration
698      */
699     private void scanForward(final int repcnt, long delta) {
700         if (mService == null) {
701             return;
702         }
703         if (repcnt == 0) {
704             mStartSeekPos = MusicUtils.position();
705             mLastSeekEventTime = 0;
706         } else {
707             if (delta < 5000) {
708                 // seek at 10x speed for the first 5 seconds
709                 delta = delta * 10;
710             } else {
711                 // seek at 40x after that
712                 delta = 50000 + (delta - 5000) * 40;
713             }
714             long newpos = mStartSeekPos + delta;
715             final long duration = MusicUtils.duration();
716             if (newpos >= duration) {
717                 // move to next track
718                 MusicUtils.next();
719                 mStartSeekPos -= duration; // is OK to go negative
720                 newpos -= duration;
721             }
722             if (delta - mLastSeekEventTime > 250 || repcnt < 0) {
723                 MusicUtils.seek(newpos);
724                 mLastSeekEventTime = delta;
725             }
726             if (repcnt >= 0) {
727                 mPosOverride = newpos;
728             } else {
729                 mPosOverride = -1;
730             }
731             refreshCurrentTime();
732         }
733     }
734
735     private void refreshCurrentTimeText(final long pos) {
736         mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000));
737     }
738
739     /* Used to update the current time string */
740     private long refreshCurrentTime() {
741         if (mService == null) {
742             return 500;
743         }
744         try {
745             final long pos = mPosOverride < 0 ? MusicUtils.position() : mPosOverride;
746             if (pos >= 0 && MusicUtils.duration() > 0) {
747                 refreshCurrentTimeText(pos);
748                 final int progress = (int)(1000 * pos / MusicUtils.duration());
749                 mProgress.setProgress(progress);
750
751                 if (mFromTouch) {
752                     return 500;
753                 } else if (MusicUtils.isPlaying()) {
754                     mCurrentTime.setVisibility(View.VISIBLE);
755                 } else {
756                     // blink the counter
757                     final int vis = mCurrentTime.getVisibility();
758                     mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE
759                             : View.INVISIBLE);
760                     return 500;
761                 }
762             } else {
763                 mCurrentTime.setText("--:--");
764                 mProgress.setProgress(1000);
765             }
766             // calculate the number of milliseconds until the next full second,
767             // so
768             // the counter can be updated at just the right time
769             final long remaining = 1000 - pos % 1000;
770             // approximate how often we would need to refresh the slider to
771             // move it smoothly
772             int width = mProgress.getWidth();
773             if (width == 0) {
774                 width = 320;
775             }
776             final long smoothrefreshtime = MusicUtils.duration() / width;
777             if (smoothrefreshtime > remaining) {
778                 return remaining;
779             }
780             if (smoothrefreshtime < 20) {
781                 return 20;
782             }
783             return smoothrefreshtime;
784         } catch (final Exception ignored) {
785
786         }
787         return 500;
788     }
789
790     /**
791      * @param v The view to animate
792      * @param alpha The alpha to apply
793      */
794     private void fade(final View v, final float alpha) {
795         final ObjectAnimator fade = ObjectAnimator.ofFloat(v, "alpha", alpha);
796         fade.setInterpolator(AnimationUtils.loadInterpolator(this,
797                 android.R.anim.accelerate_decelerate_interpolator));
798         fade.setDuration(400);
799         fade.start();
800     }
801
802     /**
803      * Called to show the album art and hide the queue
804      */
805     private void showAlbumArt() {
806         mPageContainer.setVisibility(View.INVISIBLE);
807         mAlbumArtSmall.setVisibility(View.GONE);
808         mQueueSwitch.setVisibility(View.VISIBLE);
809         // Fade out the pager container
810         fade(mPageContainer, 0f);
811         // Fade in the album art
812         fade(mAlbumArt, 1f);
813     }
814
815     /**
816      * Called to hide the album art and show the queue
817      */
818     public void hideAlbumArt() {
819         mPageContainer.setVisibility(View.VISIBLE);
820         mQueueSwitch.setVisibility(View.GONE);
821         mAlbumArtSmall.setVisibility(View.VISIBLE);
822         // Fade out the artwork
823         fade(mAlbumArt, 0f);
824         // Fade in the pager container
825         fade(mPageContainer, 1f);
826     }
827
828     /**
829      * /** Used to shared what the user is currently listening to
830      */
831     private void shareCurrentTrack() {
832         if (MusicUtils.getTrackName() == null || MusicUtils.getArtistName() == null) {
833             return;
834         }
835         final Intent shareIntent = new Intent();
836         final String shareMessage = getString(R.string.now_listening_to,
837                 MusicUtils.getTrackName(), MusicUtils.getArtistName());
838
839         shareIntent.setAction(Intent.ACTION_SEND);
840         shareIntent.setType("text/plain");
841         shareIntent.putExtra(Intent.EXTRA_TEXT, shareMessage);
842         startActivity(Intent.createChooser(shareIntent, getString(R.string.share_track_using)));
843     }
844
845     /**
846      * Used to scan backwards through the track
847      */
848     private final RepeatingImageButton.RepeatListener mRewindListener = new RepeatingImageButton.RepeatListener() {
849         /**
850          * {@inheritDoc}
851          */
852         @Override
853         public void onRepeat(final View v, final long howlong, final int repcnt) {
854             scanBackward(repcnt, howlong);
855         }
856     };
857
858     /**
859      * Used to scan ahead through the track
860      */
861     private final RepeatingImageButton.RepeatListener mFastForwardListener = new RepeatingImageButton.RepeatListener() {
862         /**
863          * {@inheritDoc}
864          */
865         @Override
866         public void onRepeat(final View v, final long howlong, final int repcnt) {
867             scanForward(repcnt, howlong);
868         }
869     };
870
871     /**
872      * Switches from the large album art screen to show the queue and lyric
873      * fragments, then back again
874      */
875     private final OnClickListener mToggleHiddenPanel = new OnClickListener() {
876
877         /**
878          * {@inheritDoc}
879          */
880         @Override
881         public void onClick(final View v) {
882             if (mPageContainer.getVisibility() == View.VISIBLE) {
883                 // Open the current album profile
884                 mAudioPlayerHeader.setOnClickListener(mOpenAlbumProfile);
885                 // Show the artwork, hide the queue
886                 showAlbumArt();
887             } else {
888                 // Scroll to the current track
889                 mAudioPlayerHeader.setOnClickListener(mScrollToCurrentSong);
890                 // Show the queue, hide the artwork
891                 hideAlbumArt();
892             }
893         }
894     };
895
896     /**
897      * Opens to the current album profile
898      */
899     private final OnClickListener mOpenAlbumProfile = new OnClickListener() {
900
901         @Override
902         public void onClick(final View v) {
903             NavUtils.openAlbumProfile(AudioPlayerActivity.this, MusicUtils.getAlbumName(),
904                     MusicUtils.getArtistName(), MusicUtils.getCurrentAlbumId());
905         }
906     };
907
908     /**
909      * Scrolls the queue to the currently playing song
910      */
911     private final OnClickListener mScrollToCurrentSong = new OnClickListener() {
912
913         @Override
914         public void onClick(final View v) {
915             ((QueueFragment)mPagerAdapter.getFragment(0)).scrollToCurrentSong();
916         }
917     };
918
919     /**
920      * Used to update the current time string
921      */
922     private static final class TimeHandler extends Handler {
923
924         private final WeakReference<AudioPlayerActivity> mAudioPlayer;
925
926         /**
927          * Constructor of <code>TimeHandler</code>
928          */
929         public TimeHandler(final AudioPlayerActivity player) {
930             mAudioPlayer = new WeakReference<AudioPlayerActivity>(player);
931         }
932
933         @Override
934         public void handleMessage(final Message msg) {
935             switch (msg.what) {
936                 case REFRESH_TIME:
937                     final long next = mAudioPlayer.get().refreshCurrentTime();
938                     mAudioPlayer.get().queueNextRefresh(next);
939                     break;
940                 default:
941                     break;
942             }
943         }
944     };
945
946     /**
947      * Used to monitor the state of playback
948      */
949     private static final class PlaybackStatus extends BroadcastReceiver {
950
951         private final WeakReference<AudioPlayerActivity> mReference;
952
953         /**
954          * Constructor of <code>PlaybackStatus</code>
955          */
956         public PlaybackStatus(final AudioPlayerActivity activity) {
957             mReference = new WeakReference<AudioPlayerActivity>(activity);
958         }
959
960         /**
961          * {@inheritDoc}
962          */
963         @Override
964         public void onReceive(final Context context, final Intent intent) {
965             final String action = intent.getAction();
966             if (action.equals(MusicPlaybackService.META_CHANGED)) {
967                 // Current info
968                 mReference.get().updateNowPlayingInfo();
969                 // Update the favorites icon
970                 mReference.get().invalidateOptionsMenu();
971             } else if (action.equals(MusicPlaybackService.PLAYSTATE_CHANGED)) {
972                 // Set the play and pause image
973                 mReference.get().mPlayPauseButton.updateState();
974             } else if (action.equals(MusicPlaybackService.REPEATMODE_CHANGED)
975                     || action.equals(MusicPlaybackService.SHUFFLEMODE_CHANGED)) {
976                 // Set the repeat image
977                 mReference.get().mRepeatButton.updateRepeatState();
978                 // Set the shuffle image
979                 mReference.get().mShuffleButton.updateShuffleState();
980             }
981         }
982     }
983
984 }