package com.kure.musicplayer.activities;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.MediaController.MediaPlayerControl;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
import com.kure.musicplayer.MusicController;
import com.kure.musicplayer.R;
import com.kure.musicplayer.kMP;
import com.kure.musicplayer.adapters.AdapterSong;
/**
* It is te "Now Playing List" - shows all songs that will be played and lets
* the user interact with them.
*
* Tasks:
*
* - List all currently playing songs. - Has a MediaController, little widgets
* with buttons to play, pause, skip, etc. - Lets the user append songs to it at
* any time. - Allows the user to select any song inside it to start playing
* right away.
*
* Interface:
*
* If you want to play a set of musics, set the ArrayList<Song> on
* `kmP.nowPlayingList` with all the songs you want.
*
* Then, send an Extra called "song" that contains the global ID of the Song you
* want to start playing.
*
* Another thing you can do is to send an extra of key "sort" with any value
* accepted by the function `MusicService.sortBy()`. Then, the list will get
* sorted that way before it starts playing.
*
* - If we don't find that ID on the list, we start playing from the beginning.
* - The Extra is optional: if you don't provide it it does nothing.
*/
public class ActivityNowPlaying extends ActivityMaster implements
MediaPlayerControl, OnItemClickListener, OnItemLongClickListener {
/**
* List that will display all the songs.
*/
private ListView songListView;
private boolean paused = false;
private boolean playbackPaused = false;
private MusicController musicController;
/**
* Thing that maps songs to items on the ListView.
*
* We're keeping track of it so we can refresh the ListView if the user
* wishes to change it's order.
*
* Check out the leftmost menu and it's options.
*/
private AdapterSong songAdapter;
/**
* Little menu that will show when the user
* clicks the ActionBar.
* It serves to sort the current song list.
*/
private PopupMenu popup;
/**
* Gets called when the Activity is getting initialized.
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_now_playing);
songListView = (ListView) findViewById(R.id.activity_now_playing_song_list);
// We'll play this pre-defined list.
// By default we play the first track, although an
// extra can change this. Look below.
kMP.musicService.setList(kMP.nowPlayingList);
kMP.musicService.setSong(0);
// Connects the song list to an adapter
// (thing that creates several Layouts from the song list)
songAdapter = new AdapterSong(this, kMP.nowPlayingList);
songListView.setAdapter(songAdapter);
// Looking for optional extras
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
if (bundle != null) {
// There's the other optional extra - sorting rule
if (bundle.containsKey("sort"))
kMP.musicService.sortBy((String) bundle.get("sort"));
// If we received an extra with the song position
// inside the now playing list, start playing it
if (bundle.containsKey("song")) {
int songToPlayIndex = (int) bundle.get("song");
// Prepare the music service to play the song.
// `setSong` does limit-checking
kMP.musicService.setSong(songToPlayIndex);
}
kMP.musicService.playSong();
}
// Scroll the list view to the current song.
songListView.setSelection(kMP.musicService.currentSongPosition);
// We'll get warned when the user clicks on an item
// and when he long selects an item.
songListView.setOnItemClickListener(this);
songListView.setOnItemLongClickListener(this);
setMusicController();
if (playbackPaused) {
setMusicController();
playbackPaused = false;
}
// While we're playing music, add an item to the
// Main Menu that returns here.
ActivityMenuMain.addNowPlayingItem(this);
// Customizing the ActionBar
// (menu on top)
createActionBar();
}
/**
* Initializes and customizes the ActionBar
* (menu on top).
*
* Instead of showing an Icon and the classic Title and Subtitle,
* we'll display a single button that spawns a submenu.
*/
private void createActionBar() {
ActionBar actionBar = getActionBar();
if (actionBar == null)
return;
// Alright, this is a long one...
//
// First, we create the submenu that will appear
// when the user clicks on the ActionBar button.
//
// Then we create the ActionBar.
//
// Then we attach the submenu to the ActionBar.
// To create the dropdown menu, I need to get a
// reference to the leftmost button's View...
//
// (Source: http://stackoverflow.com/a/21125631)
Window window = getWindow();
View view = window.getDecorView();
int resID = getResources().getIdentifier("action_bar_container", "id", "android");
// ...and create the PopupMenu, populating with the options...
popup = new PopupMenu(this, view.findViewById(resID));
MenuInflater menuInflater = popup.getMenuInflater();
menuInflater.inflate(R.menu.activity_now_playing_action_bar_submenu, popup.getMenu());
// ... then we tell what happens when the user
// selects any of it's items.
PopupMenu.OnMenuItemClickListener listener = new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
// If we're going to scroll
// the list after sorting it.
boolean updateList = false;
switch (item.getItemId()) {
// Sorting options - after changing the now playing list
// order, we must refresh the ListView and scroll to the
// currently playing song.
// Will sort current songs by title
case R.id.action_bar_submenu_title:
kMP.musicService.sortBy("title");
updateList = true;
break;
// Will sort current songs by artist
case R.id.action_bar_submenu_artist:
kMP.musicService.sortBy("artist");
updateList = true;
break;
// Will sort current songs by album
case R.id.action_bar_submenu_album:
kMP.musicService.sortBy("album");
updateList = true;
break;
// Will sort current songs by track number
case R.id.action_bar_submenu_track:
kMP.musicService.sortBy("track");
updateList = true;
break;
// Will sort current songs randomly
case R.id.action_bar_submenu_random:
kMP.musicService.sortBy("random");
updateList = true;
break;
// Will ask the user for a new Playlist name, creating
// it with the current songs.
//
// If there's already a playlist with that name, we'll
// append a silly string to the new Playlist name.
case R.id.action_bar_submenu_new_playlist:
newPlaylist();
return false;
}
// Finally, updating the list if it
// just got sorted
if (updateList) {
songAdapter.notifyDataSetChanged();
songListView.setSelection(kMP.musicService.currentSongPosition);
}
return false;
}
};
// Phew! Activating the callbacks when someone
// clicks the menu and showing it.
popup.setOnMenuItemClickListener(listener);
// Making sure the leftmost button (Home Button) is
// not there
actionBar.setHomeButtonEnabled(false);
// Custom layout - customize it there
actionBar.setCustomView(R.layout.activity_now_playing_action_bar);
// The default text for the "Title"
TextView textTop = (TextView) actionBar
.getCustomView()
.findViewById(R.id.action_bar_title);
textTop.setText(getString(R.string.now_playing));
// Our "Subtitle" will have the name of the currently
// playing song. For now, it's empty
TextView textBottom = (TextView) actionBar
.getCustomView()
.findViewById(R.id.action_bar_subtitle);
textBottom.setText("");
// From now on, every time we update our custom
// layout, the ActionBar will get refreshed
// immediately.
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
// And when we click on the custom layout
// (our button with "Title" and "Subtitle")...
actionBar
.getCustomView()
.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
popup.show();
}
});
}
/**
* Activates the ActionBar's leftmost drop-down menu.
*
* @note We're creating the menu EVERY TIME you call this function! Hope it
* doesn't become too slow on some phones.
*
* @note All of it's items are defined on
* `res/menu/activity_now_playing_action_bar_submenu.xml`.
*/
public void showSubmenu() {
// The menu can't possibly work if there's no ActionBar
ActionBar actionBar = getActionBar();
if (actionBar == null)
return;
popup.show();
// popup.show();
}
/**
* Shows a Dialog asking the user for a new Playlist name,
* creating it if so possible.
*/
private void newPlaylist() {
// The input box where user will type new name
final EditText input = new EditText(ActivityNowPlaying.this);
// Labels
String dialogTitle = ActivityNowPlaying.this.getString(R.string.menu_now_playing_dialog_create_playlist_title);
String dialogText = ActivityNowPlaying.this.getString(R.string.menu_now_playing_dialog_create_playlist_subtitle);
String buttonOK = ActivityNowPlaying.this.getString(R.string.menu_now_playing_dialog_create_playlist_button_ok);
String buttonCancel = ActivityNowPlaying.this.getString(R.string.menu_now_playing_dialog_create_playlist_button_cancel);
// Creating the dialog box that asks the user,
// with the question and options.
new AlertDialog.Builder(ActivityNowPlaying.this)
.setTitle(dialogTitle)
.setMessage(dialogText)
.setView(input)
// Creates the OK button, attaching the action to create the Playlist
.setPositiveButton(buttonOK, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
String playlistName = input.getText().toString();
// TODO: Must somehow update the Playlist Activity if it's
// on the background!
// The ListView only updates when Playlist Menu gets
// created from scratch.
kMP.songs.newPlaylist(ActivityNowPlaying.this, "external", playlistName, kMP.nowPlayingList);
String createPlaylistText = ActivityNowPlaying.this.getString(R.string.menu_now_playing_dialog_create_playlist_success, playlistName);
// Congratulating the user with the
// new Playlist name
Toast.makeText(ActivityNowPlaying.this,
createPlaylistText,
Toast.LENGTH_SHORT).show();
}
// Creates the CANCEL button, that
// doesn't do nothing
// (since a Playlist is only created
// when pressing OK).
})
.setNegativeButton(buttonCancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Do nothing, yay!
}
// Lol, this is where we actually call the Dialog.
// Note for newcomers: The code continues to execute.
// This is an asynchronous task.
}).show();
}
/**
* Icon that will show on the top menu showing if `shuffle` is on/off and
* allowing the user to change it.
*/
private MenuItem shuffleItem;
/**
* Icon that will show on the top menu showing if `repeat` is on/off and
* allowing the user to change it.
*/
private MenuItem repeatItem;
/**
* Let's create the ActionBar (menu on the top).
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.activity_now_playing_action_bar, menu);
shuffleItem = menu.findItem(R.id.action_bar_shuffle);
repeatItem = menu.findItem(R.id.action_bar_repeat);
refreshActionBarItems();
refreshActionBarSubtitle();
return super.onCreateOptionsMenu(menu);
}
/**
* Refreshes the icons on the Action Bar based on the status of `shuffle`
* and `repeat`.
*
* Source: http://stackoverflow.com/a/11006878
*/
private void refreshActionBarItems() {
shuffleItem
.setIcon((kMP.musicService.isShuffle()) ? R.drawable.ic_menu_shuffle_on
: R.drawable.ic_menu_shuffle_off);
repeatItem
.setIcon((kMP.musicService.isRepeat()) ? R.drawable.ic_menu_repeat_on
: R.drawable.ic_menu_repeat_off);
}
/**
* Sets the Action Bar subtitle to the currently playing song title.
*/
public void refreshActionBarSubtitle() {
ActionBar actionBar = getActionBar();
if (actionBar == null)
return;
/*
* actionBar.setCustomView(R.layout.menu_item_double);
*
* TextView textTop =
* (TextView)actionBar.getCustomView().findViewById(R.
* id.menu_item_title); textTop.setText("Now Playing List");
*/
if (kMP.musicService.currentSong == null)
return;
TextView textBottom = (TextView) actionBar.getCustomView()
.findViewById(R.id.action_bar_subtitle);
textBottom.setText(kMP.musicService.currentSong.getTitle());
/*
* actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
*/
}
/**
* This method gets called whenever the user clicks an item on the
* ActionBar.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_bar_shuffle:
kMP.musicService.toggleShuffle();
refreshActionBarItems();
return true;
case R.id.action_bar_repeat:
kMP.musicService.toggleRepeat();
refreshActionBarItems();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onStart() {
super.onStart();
// WHY CANT I SET THE MUSIC CONTROLLER HERE AND LET IT BE
// FOREVER?
// if (!this.isFinishing()) {
// musicController.show(5000);
// }
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN)
if (keyCode == KeyEvent.KEYCODE_MENU)
musicController.show();
return super.onKeyDown(keyCode, event);
}
/**
* Another Activity is taking focus. (either from user going to another
* Activity or home)
*/
@Override
protected void onPause() {
super.onPause();
paused = true;
playbackPaused = true;
}
/**
* Activity has become visible.
*
* @see onPause()
*/
@Override
protected void onResume() {
super.onResume();
refreshActionBarSubtitle();
if (paused) {
// Ensure that the controller
// is shown when the user returns to the app
setMusicController();
paused = false;
}
// Scroll the list view to the current song.
if (kMP.settings.get("scroll_on_focus", true))
songListView.setSelection(kMP.musicService.currentSongPosition);
}
/**
* Activity is no longer visible.
*/
@Override
protected void onStop() {
musicController.hide();
super.onStop();
}
/**
* (Re)Starts the musicController.
*/
private void setMusicController() {
musicController = new MusicController(ActivityNowPlaying.this);
// What will happen when the user presses the
// next/previous buttons?
musicController.setPrevNextListeners(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Calling method defined on ActivityNowPlaying
playNext();
}
}, new View.OnClickListener() {
@Override
public void onClick(View v) {
// Calling method defined on ActivityNowPlaying
playPrevious();
}
});
// Binding to our media player
musicController.setMediaPlayer(this);
musicController
.setAnchorView(findViewById(R.id.activity_now_playing_song_list));
musicController.setEnabled(true);
}
@Override
public void start() {
kMP.musicService.unpausePlayer();
}
/**
* Callback to when the user pressed the `pause` button.
*/
@Override
public void pause() {
kMP.musicService.pausePlayer();
}
@Override
public int getDuration() {
if (kMP.musicService != null && kMP.musicService.musicBound
&& kMP.musicService.isPlaying())
return kMP.musicService.getDuration();
else
return 0;
}
@Override
public int getCurrentPosition() {
if (kMP.musicService != null && kMP.musicService.musicBound
&& kMP.musicService.isPlaying())
return kMP.musicService.getPosition();
else
return 0;
}
@Override
public void seekTo(int position) {
kMP.musicService.seekTo(position);
}
@Override
public boolean isPlaying() {
if (kMP.musicService != null && kMP.musicService.musicBound)
return kMP.musicService.isPlaying();
return false;
}
@Override
public int getBufferPercentage() {
// TODO Auto-generated method stub
return 0;
}
@Override
public boolean canPause() {
return true;
}
@Override
public boolean canSeekBackward() {
return true;
}
@Override
public boolean canSeekForward() {
return true;
}
@Override
public int getAudioSessionId() {
// TODO Auto-generated method stub
return 0;
}
// Back to the normal methods
/**
* Jumps to the next song and starts playing it right now.
*/
public void playNext() {
kMP.musicService.next(true);
kMP.musicService.playSong();
refreshActionBarSubtitle();
// To prevent the MusicPlayer from behaving
// unexpectedly when we pause the song playback.
if (playbackPaused) {
setMusicController();
playbackPaused = false;
}
musicController.show();
}
/**
* Jumps to the previous song and starts playing it right now.
*/
public void playPrevious() {
kMP.musicService.previous(true);
kMP.musicService.playSong();
refreshActionBarSubtitle();
// To prevent the MusicPlayer from behaving
// unexpectedly when we pause the song playback.
if (playbackPaused) {
setMusicController();
playbackPaused = false;
}
musicController.show();
}
/**
* When the user selects a music inside the "Now Playing List", we'll start
* playing it right away.
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// Prepare the music service to play the song.
kMP.musicService.setSong(position);
// Scroll the list view to the current song.
songListView.setSelection(position);
kMP.musicService.playSong();
refreshActionBarSubtitle();
if (playbackPaused) {
setMusicController();
playbackPaused = false;
}
}
/**
* When the user long clicks a music inside the "Now Playing List".
*/
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Toast.makeText(this, kMP.musicService.getSong(position).getGenre(),
Toast.LENGTH_LONG).show();
// Just a catch - if we return `false`, when an user
// long clicks an item, the list will react as if
// we've long clicked AND clicked.
//
// So by returning `false`, it will call both
// `onItemLongClick` and `onItemClick`!
return true;
}
}