Java tutorial
/* * YAMMP - Yet Another Multi Media Player for android * Copyright (C) 2011-2012 Mariotaku Lee <mariotaku.lee@gmail.com> * * This file is part of YAMMP. * * YAMMP is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * YAMMP is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with YAMMP. If not, see <http://www.gnu.org/licenses/>. */ package org.mariotaku.harmony.activity; import org.mariotaku.harmony.Constants; import org.mariotaku.harmony.R; import org.mariotaku.harmony.activity.BaseActivity; import org.mariotaku.harmony.app.HarmonyApplication; import org.mariotaku.harmony.fragment.LyricsFragment; import org.mariotaku.harmony.fragment.QueueFragment; import org.mariotaku.harmony.model.AlbumInfo; import org.mariotaku.harmony.model.TrackInfo; import org.mariotaku.harmony.util.ImageLoaderWrapper; import org.mariotaku.harmony.util.MusicUtils; import org.mariotaku.harmony.util.PreferencesEditor; import org.mariotaku.harmony.util.ServiceWrapper; import org.mariotaku.harmony.util.Utils; import org.mariotaku.harmony.view.AlbumArtView; import org.mariotaku.harmony.view.ExtendedRelativeLayout; import org.mariotaku.harmony.view.RepeatingImageButton; import java.util.ArrayList; import android.app.ActionBar; import android.app.Activity; import android.app.Fragment; import android.app.SearchManager; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.media.AudioManager; import android.media.audiofx.AudioEffect; import android.os.Bundle; import android.os.Handler; import android.provider.MediaStore; import android.support.v13.app.FragmentStatePagerAdapter; import android.support.v4.app.NavUtils; import android.support.v4.view.ViewPager; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager.LayoutParams; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import android.view.GestureDetector; public class MusicPlaybackActivity extends BaseActivity implements Constants, View.OnClickListener, SeekBar.OnSeekBarChangeListener, ViewPager.OnPageChangeListener, RepeatingImageButton.RepeatListener, ExtendedRelativeLayout.TouchInterceptor, ActionBar.OnMenuVisibilityListener, GestureDetector.OnGestureListener { private static final float ALBUM_ART_ALPHA_INACTIVE = 0.3f; private static final int REQUEST_EQUALIZER = 1; private ActionBar mActionBar; private boolean mIsShowingMenu; private boolean mSeeking = false; private boolean mDeviceHasDpad; private long mStartSeekPos = 0; private long mLastSeekEventTime; private boolean mIntentDeRegistered = false; private long mPosOverride = -1; private boolean mFromTouch = false; private long mDuration; private boolean mPaused; private TextView mTrackName, mTrackDetail; private TextView mCurrentTime, mTotalTime; private SeekBar mSeekBar; private AlbumArtView mAlbumArt; private ViewPager mViewPager; private RepeatingImageButton mPrevButton, mNextButton; private ImageButton mPlayPauseButton; private ExtendedRelativeLayout mPlaybackContainer; private PagerAdapter mAdapter; private ServiceWrapper mService; private PreferencesEditor mPreferences; private ImageLoaderWrapper mImageLoader; private final Handler mHandler = new Handler(); private Runnable mRefreshRunnable, mHideActionBarRunnable; private GestureDetector mGestureDector; @Override public boolean dispatchTouchEvent(final ViewGroup view, final MotionEvent event) { mGestureDector.onTouchEvent(event); return false; } public void onClick(final View view) { switch (view.getId()) { case R.id.play_pause: { doPauseResume(); break; } case R.id.next: { doNext(); break; } case R.id.prev: { doPrev(); break; } } } public boolean onDown(MotionEvent e) { return false; } public boolean onFling(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return false; } public void onShowPress(MotionEvent e) { } public boolean onSingleTapUp(MotionEvent e) { mActionBar.show(); mHandler.removeCallbacks(mHideActionBarRunnable); if (!mIsShowingMenu) { mHandler.postDelayed(mHideActionBarRunnable, 3000); } return true; } @Override public boolean onInterceptTouchEvent(ViewGroup view, MotionEvent event) { return false; } public void onLongPress(MotionEvent event) { } @Override public void onMenuVisibilityChanged(boolean visible) { mIsShowingMenu = visible; mHandler.removeCallbacks(mHideActionBarRunnable); if (!visible) { mHandler.postDelayed(mHideActionBarRunnable, 3000); } } @Override public boolean onTouchEvent(ViewGroup view, MotionEvent event) { return false; } @Override public void onPageSelected(final int position) { mAlbumArt.setAlpha(position != 1 && mAdapter.getCount() >= 3 ? ALBUM_ART_ALPHA_INACTIVE : 1.0f); } @Override public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) { if (position == 0) { mAlbumArt.setAlpha(ALBUM_ART_ALPHA_INACTIVE + (1.0f - ALBUM_ART_ALPHA_INACTIVE) * positionOffset); } else if (position == 1) { mAlbumArt.setAlpha(1.0f - (1.0f - ALBUM_ART_ALPHA_INACTIVE) * positionOffset); } else { mAlbumArt.setAlpha(ALBUM_ART_ALPHA_INACTIVE); } } @Override public void onPageScrollStateChanged(final int state) { } @Override public void onProgressChanged(final SeekBar bar, final int progress, final boolean fromuser) { if (!fromuser || mService == null) return; mPosOverride = mDuration * progress / 1000; mService.seek(mPosOverride); refreshNow(); // trackball event, allow progress updates if (!mFromTouch) { refreshNow(); mPosOverride = -1; } } @Override public void onStartTrackingTouch(SeekBar bar) { mLastSeekEventTime = 0; mFromTouch = true; mHandler.removeCallbacks(mRefreshRunnable); } @Override public void onStopTrackingTouch(SeekBar bar) { mPosOverride = -1; mFromTouch = false; // Ensure that progress is properly updated in the future, mHandler.post(mRefreshRunnable); } @Override public void onRepeat(View v, long howlong, int repcnt) { switch (v.getId()) { case R.id.prev: { scanBackward(repcnt, howlong); break; } case R.id.next: { scanForward(repcnt, howlong); break; } } } /** Called when the activity is first created. */ @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); mImageLoader = HarmonyApplication.getInstance(this).getImageLoaderWrapper(); mPreferences = new PreferencesEditor(this); mRefreshRunnable = new RefreshRunnable(this); mHideActionBarRunnable = new HideActionBarRunnable(this); setVolumeControlStream(AudioManager.STREAM_MUSIC); setContentView(R.layout.music_playback); mActionBar = getActionBar(); mActionBar.addOnMenuVisibilityListener(this); mActionBar.hide(); mSeekBar.setMax(1000); mSeekBar.setOnSeekBarChangeListener(this); mPlayPauseButton.setOnClickListener(this); mPrevButton.setOnClickListener(this); mNextButton.setOnClickListener(this); mPrevButton.setRepeatListener(this, 260); mNextButton.setRepeatListener(this, 260); mDeviceHasDpad = getResources().getConfiguration().navigation == Configuration.NAVIGATION_DPAD; mGestureDector = new GestureDetector(this, this); mPlaybackContainer.setTouchInterceptor(this); mAdapter = new PagerAdapter(this); mViewPager.setAdapter(mAdapter); mAdapter.addFragment(QueueFragment.class); mAdapter.addFragment(Fragment.class); mAdapter.addFragment(LyricsFragment.class); mViewPager.setOnPageChangeListener(this); mViewPager.setOffscreenPageLimit(3); mViewPager.setCurrentItem(1, false); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_music_playback, menu); return true; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (mService == null) return super.onKeyDown(keyCode, event); final int repcnt = event.getRepeatCount(); switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: if (!useDpadMusicControl()) { break; } if (!mPrevButton.hasFocus()) { mPrevButton.requestFocus(); } scanBackward(repcnt, event.getEventTime() - event.getDownTime()); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!useDpadMusicControl()) { break; } if (!mNextButton.hasFocus()) { mNextButton.requestFocus(); } scanForward(repcnt, event.getEventTime() - event.getDownTime()); return true; // case KeyEvent.KEYCODE_R: // toggleRepeat(); // return true; // // case KeyEvent.KEYCODE_S: // toggleShuffle(); // return true; case KeyEvent.KEYCODE_N: { doNext(); return true; } case KeyEvent.KEYCODE_P: { doPrev(); return true; } case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_SPACE: { doPauseResume(); return true; } } return super.onKeyDown(keyCode, event); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: if (!useDpadMusicControl()) { break; } if (mService != null) { if (!mSeeking && mStartSeekPos >= 0) { mPlayPauseButton.requestFocus(); if (mStartSeekPos < 1000) { mService.prev(); } else { mService.seek(0); } } else { scanBackward(-1, event.getEventTime() - event.getDownTime()); mPlayPauseButton.requestFocus(); mStartSeekPos = -1; } } mSeeking = false; mPosOverride = -1; return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (!useDpadMusicControl()) { break; } if (mService != null) { if (!mSeeking && mStartSeekPos >= 0) { mPlayPauseButton.requestFocus(); mService.next(); } else { scanForward(-1, event.getEventTime() - event.getDownTime()); mPlayPauseButton.requestFocus(); mStartSeekPos = -1; } } mSeeking = false; mPosOverride = -1; return true; } return super.onKeyUp(keyCode, event); } public boolean onLongClick(final View v) { // TODO search media info String track = getTitle().toString(); String artist = "";// mArtistNameView.getText().toString(); String album = "";// mAlbumNameView.getText().toString(); CharSequence title = getString(R.string.mediasearch, track); Intent i = new Intent(); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); i.setAction(MediaStore.INTENT_ACTION_MEDIA_SEARCH); i.putExtra(MediaStore.EXTRA_MEDIA_TITLE, track); String query = track; if (!getString(R.string.unknown_artist).equals(artist) && !getString(R.string.unknown_album).equals(album)) { query = artist + " " + track; i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album); i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist); } else if (getString(R.string.unknown_artist).equals(artist) && !getString(R.string.unknown_album).equals(album)) { query = album + " " + track; i.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album); } else if (!getString(R.string.unknown_artist).equals(artist) && getString(R.string.unknown_album).equals(album)) { query = artist + " " + track; i.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist); } i.putExtra(SearchManager.QUERY, query); i.putExtra(MediaStore.EXTRA_MEDIA_FOCUS, "audio/*"); startActivity(Intent.createChooser(i, title)); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (mService == null) return true; switch (item.getItemId()) { case MENU_ADD_TO_PLAYLIST: { final Intent intent = new Intent(INTENT_ADD_TO_PLAYLIST); long[] list_to_be_added = new long[1]; //list_to_be_added[0] = MusicUtils.getCurrentAudioId(); intent.putExtra(INTENT_KEY_LIST, list_to_be_added); startActivity(intent); break; } case EQUALIZER: { final Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL); intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName()); intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mService.getAudioSessionId()); intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC); startActivityForResult(intent, REQUEST_EQUALIZER); break; } case MENU_SLEEP_TIMER: { final Intent intent = new Intent(INTENT_SLEEP_TIMER); startActivity(intent); break; } case DELETE_ITEMS: { final Intent intent = new Intent(INTENT_DELETE_ITEMS); Bundle bundle = new Bundle(); // bundle.putString( // INTENT_KEY_PATH, // Uri.withAppendedPath(Audio.Media.EXTERNAL_CONTENT_URI, // Uri.encode(String.valueOf(MusicUtils.getCurrentAudioId()))) // .toString()); intent.putExtras(bundle); startActivity(intent); break; } case SETTINGS: { final Intent intent = new Intent(INTENT_APPEARANCE_SETTINGS); startActivity(intent); break; } case MENU_HOME: { overridePendingTransition(R.anim.music_playback_activity_enter, R.anim.music_playback_activity_exit); NavUtils.navigateUpTo(this, new Intent(this, MusicBrowserActivity.class)); break; } case ADD_TO_FAVORITES: { toggleFavorite(); break; } case MENU_SHUFFLE_MODE_NONE: { mService.setShuffleMode(SHUFFLE_MODE_NONE); break; } case MENU_SHUFFLE_MODE_ALL: { mService.setShuffleMode(SHUFFLE_MODE_ALL); break; } case MENU_REPEAT_MODE_NONE: { mService.setRepeatMode(REPEAT_MODE_NONE); break; } case MENU_REPEAT_MODE_ALL: { mService.setRepeatMode(REPEAT_MODE_ALL); break; } case MENU_REPEAT_MODE_CURRENT: { mService.setRepeatMode(REPEAT_MODE_CURRENT); break; } } return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { final MenuItem equalizer = menu.findItem(EQUALIZER); if (equalizer != null) { final PackageManager pm = getPackageManager(); final Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL); equalizer.setVisible(!pm.queryIntentActivities(intent, 0).isEmpty()); } // final MenuItem item = menu.findItem(ADD_TO_FAVORITES); // try { // if (item != null && mService != null) // item.setIcon(mService.isFavorite(mService.getAudioId()) ? R.drawable.ic_menu_star // : R.drawable.ic_menu_star_off); // } catch (RemoteException e) { // e.printStackTrace(); // } if (mService != null) { switch (mService.getShuffleMode()) { case SHUFFLE_MODE_ALL: { menu.findItem(MENU_SHUFFLE_MODE_ALL).setChecked(true); break; } default: { menu.findItem(MENU_SHUFFLE_MODE_NONE).setChecked(true); break; } } switch (mService.getRepeatMode()) { case REPEAT_MODE_ALL: { menu.findItem(MENU_REPEAT_MODE_ALL).setChecked(true); break; } case REPEAT_MODE_CURRENT: { menu.findItem(MENU_REPEAT_MODE_CURRENT).setChecked(true); break; } default: { menu.findItem(MENU_REPEAT_MODE_NONE).setChecked(true); break; } } } return true; } @Override public void onResume() { super.onResume(); if (mIntentDeRegistered) { mPaused = false; } } @Override protected void onServiceConnected(final ServiceWrapper service) { mService = service; // something went wrong if (service == null) { Toast.makeText(this, R.string.service_start_error_msg, Toast.LENGTH_SHORT); finish(); return; } if (service.getTrackInfo() == null) { // Navigate to music browser finish(); return; } updateTrackInfo(); updatePlayPauseButton(); queueNextRefresh(refreshNow()); } @Override protected void onStart() { super.onStart(); mPaused = false; final boolean lyrics_wakelock = mPreferences.getBooleanPref(KEY_LYRICS_WAKELOCK, DEFAULT_LYRICS_WAKELOCK); if (lyrics_wakelock) { getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); } else { getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); } final int page_pos = mPreferences.getIntState(STATE_KEY_PAGE_POSITION_PLAYBACK, 1); mViewPager.setCurrentItem(Utils.limit(page_pos, 0, mAdapter.getCount())); } @Override protected void onStop() { mPaused = true; mAlbumArt.loadAlbumArt(null); mPreferences.setIntState(STATE_KEY_PAGE_POSITION_PLAYBACK, mViewPager.getCurrentItem()); getWindow().clearFlags(LayoutParams.FLAG_KEEP_SCREEN_ON); super.onStop(); } @Override protected void onDestroy() { mActionBar.removeOnMenuVisibilityListener(this); super.onDestroy(); } protected void onCurrentMediaChanged() { updateTrackInfo(); updatePlayPauseButton(); } protected void onPlayStateChanged() { updatePlayPauseButton(); } protected void onRepeatModeChanged() { invalidateOptionsMenu(); } protected void onShuffleModeChanged() { invalidateOptionsMenu(); } @Override public void onContentChanged() { super.onContentChanged(); mPlaybackContainer = (ExtendedRelativeLayout) findViewById(R.id.music_playback); mAlbumArt = (AlbumArtView) findViewById(R.id.album_art); mViewPager = (ViewPager) findViewById(R.id.pager); mTrackName = (TextView) findViewById(R.id.track_name); mTrackDetail = (TextView) findViewById(R.id.track_detail); mCurrentTime = (TextView) findViewById(R.id.current_time); mTotalTime = (TextView) findViewById(R.id.total_time); mSeekBar = (SeekBar) findViewById(R.id.seek_bar); mPrevButton = (RepeatingImageButton) findViewById(R.id.prev); mPlayPauseButton = (ImageButton) findViewById(R.id.play_pause); mNextButton = (RepeatingImageButton) findViewById(R.id.next); } private void doNext() { if (mService == null) return; mService.next(); } private void doPauseResume() { if (mService == null) return; if (mService.isPlaying()) { mService.pause(); } else { mService.play(); } refreshNow(); updatePlayPauseButton(); } private void doPrev() { if (mService == null) return; if (mService.getPosition() < 2000) { mService.prev(); } else { mService.seek(0); mService.play(); } } private void queueNextRefresh(long delay) { if (!mPaused && !mFromTouch) { mHandler.postDelayed(mRefreshRunnable, delay); } } private long refreshNow() { if (mService == null) return 500; final long pos = mPosOverride < 0 ? mService.getPosition() : mPosOverride; long remaining = 1000 - pos % 1000; if (pos >= 0 && mDuration > 0) { mCurrentTime.setText(MusicUtils.makeTimeString(this, pos / 1000)); if (mService.isPlaying()) { mCurrentTime.setVisibility(View.VISIBLE); } else { // blink the counter // If the progress bar is still been dragged, then we do not // want to blink the // currentTime. It would cause flickering due to change in // the visibility. if (mFromTouch) { mCurrentTime.setVisibility(View.VISIBLE); } else { int vis = mCurrentTime.getVisibility(); mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE : View.INVISIBLE); } remaining = 500; } // Normalize our progress along the progress bar's scale mSeekBar.setProgress((int) (1000 * pos / mDuration)); } else { mCurrentTime.setText("--:--"); mSeekBar.setProgress(0); } // return the number of milliseconds until the next full second, so // the counter can be updated at just the right time return remaining; } private void scanBackward(int repcnt, long delta) { if (mService == null) return; if (repcnt == 0) { mStartSeekPos = mService.getPosition(); mLastSeekEventTime = 0; mSeeking = false; } else { mSeeking = true; if (delta < 5000) { // seek at 10x speed for the first 5 seconds delta = delta * 10; } else { // seek at 40x after that delta = 50000 + (delta - 5000) * 40; } long newpos = mStartSeekPos - delta; if (newpos < 0) { // move to previous track mService.prev(); long duration = mService.getDuration(); mStartSeekPos += duration; newpos += duration; } if (delta - mLastSeekEventTime > 250 || repcnt < 0) { mService.seek(newpos); mLastSeekEventTime = delta; } if (repcnt >= 0) { mPosOverride = newpos; } else { mPosOverride = -1; } refreshNow(); } } private void scanForward(int repcnt, long delta) { if (mService == null) return; if (repcnt == 0) { mStartSeekPos = mService.getPosition(); mLastSeekEventTime = 0; mSeeking = false; } else { mSeeking = true; if (delta < 5000) { // seek at 10x speed for the first 5 seconds delta = delta * 10; } else { // seek at 40x after that delta = 50000 + (delta - 5000) * 40; } long newpos = mStartSeekPos + delta; long duration = mService.getDuration(); if (newpos >= duration) { // move to next track mService.next(); mStartSeekPos -= duration; // is OK to go negative newpos -= duration; } if (delta - mLastSeekEventTime > 250 || repcnt < 0) { mService.seek(newpos); mLastSeekEventTime = delta; } if (repcnt >= 0) { mPosOverride = newpos; } else { mPosOverride = -1; } refreshNow(); } } private void updatePlayPauseButton() { if (mService != null && mService.isPlaying()) { mPlayPauseButton.setImageResource(R.drawable.btn_playback_ic_pause); } else { mPlayPauseButton.setImageResource(R.drawable.btn_playback_ic_play); } } private void toggleFavorite() { if (mService == null) return; } private void updateTrackInfo() { if (mService == null) return; final TrackInfo track = mService.getTrackInfo(); if (track == null) return; mTrackName.setText(track.title); if (!TrackInfo.isUnknownArtist(track)) { mTrackDetail.setText(track.artist); } else if (!TrackInfo.isUnknownAlbum(track)) { mTrackDetail.setText(track.album); } else { mTrackDetail.setText(R.string.unknown_artist); } mSeekBar.setProgress(0); mDuration = mService.getDuration(); mTotalTime.setText(MusicUtils.makeTimeString(this, mDuration / 1000)); mAlbumArt.loadAlbumArt(track); } private boolean useDpadMusicControl() { return mDeviceHasDpad && (mPrevButton.isFocused() || mNextButton.isFocused() || mPlayPauseButton.isFocused()); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { if (resultCode != RESULT_OK) return; switch (requestCode) { case DELETE_ITEMS: if (resultCode == RESULT_DELETE_MUSIC) { finish(); } break; } } private static class PagerAdapter extends FragmentStatePagerAdapter { private final Context mContext; private final ArrayList<Class<? extends Fragment>> mFragments = new ArrayList<Class<? extends Fragment>>(); public PagerAdapter(final Activity activity) { super(activity.getFragmentManager()); mContext = activity; } public void addFragment(Class<? extends Fragment> fragment) { mFragments.add(fragment); notifyDataSetChanged(); } @Override public int getCount() { return mFragments.size(); } @Override public Fragment getItem(int position) { return Fragment.instantiate(mContext, mFragments.get(position).getName()); } } private static final class RefreshRunnable implements Runnable { private final MusicPlaybackActivity mActivity; private RefreshRunnable(final MusicPlaybackActivity activity) { mActivity = activity; } @Override public void run() { mActivity.queueNextRefresh(mActivity.refreshNow()); } } private static final class HideActionBarRunnable implements Runnable { private final ActionBar mActionBar; private HideActionBarRunnable(final Activity activity) { mActionBar = activity.getActionBar(); } @Override public void run() { mActionBar.hide(); } } }