/**
* This work is licensed under the Creative Commons Attribution-NonCommercial-
* NoDerivs 3.0 Unported License. To view a copy of this license, visit
* http://creativecommons.org/licenses/by-nc-nd/3.0/ or send a letter to
* Creative Commons, 444 Castro Street, Suite 900, Mountain View, California,
* 94041, USA.
*
* Use of this work is permitted only in accordance with license rights granted.
* Materials provided "AS IS"; no representations or warranties provided.
*
* Copyright � 2012 Marcus Parkkinen, Aki K�kel�, Fredrik �hs.
**/
package edu.chalmers.dat255.audiobookplayer.view;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import edu.chalmers.dat255.audiobookplayer.R;
import edu.chalmers.dat255.audiobookplayer.constants.Constants;
import edu.chalmers.dat255.audiobookplayer.constants.PlaybackStatus;
import edu.chalmers.dat255.audiobookplayer.ctrl.BookshelfController;
import edu.chalmers.dat255.audiobookplayer.ctrl.PlayerController;
import edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents;
import edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfGUIEvents;
import edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents;
import edu.chalmers.dat255.audiobookplayer.model.Book;
import edu.chalmers.dat255.audiobookplayer.model.Bookshelf;
import edu.chalmers.dat255.audiobookplayer.util.BookCreator;
import edu.chalmers.dat255.audiobookplayer.util.BookshelfHandler;
/**
* The main activity of the application.
*
* @author Aki K�kel�, Marcus Parkkinen
* @version 0.6
*
*/
public class MainActivity extends FragmentActivity implements IPlayerEvents,
IBookshelfEvents, IBookshelfGUIEvents, PropertyChangeListener {
private static final String TAG = "MainActivity";
private static final String USERNAME = "Default";
private static final int PLAYER = 0;
private static final int BOOKSHELF = 1;
// ViewPager
private ViewPager pager;
// Fragments
private PlayerFragment playerFragment = new PlayerFragment();
private BookshelfFragment bookshelfFragment = new BookshelfFragment();
// Controllers
private BookshelfController bookshelfController;
private PlayerController playerController;
/*
* (non-Javadoc)
*
* @see android.support.v4.app.FragmentActivity#onCreate(android.os.Bundle)
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initPager();
// Load a bookshelf specified by the username
Bookshelf bs = BookshelfHandler.loadBookshelf(this, USERNAME);
// Create controllers with the bookshelf reference
playerController = new PlayerController(bs);
bookshelfController = new BookshelfController(bs);
BookCreator.getInstance().setBookshelf(bs);
// Provide a reference to the bookshelf as an argument in the bundle
Bundle bsReference = new Bundle();
bsReference.putSerializable(Constants.Reference.BOOKSHELF, bs);
bookshelfFragment.setArguments(bsReference);
}
/**
* Initializes the ViewPager and its adapter.
*/
private void initPager() {
// create a list of our fragments
List<Fragment> fragments = new ArrayList<Fragment>();
fragments.add(playerFragment);
fragments.add(bookshelfFragment);
// create the adapter
// NOTE: it is a local variable since it will not be used.
ViewPagerAdapter adapter = new ViewPagerAdapter(
super.getSupportFragmentManager(), fragments);
// create the view pager and set the adapter
pager = (ViewPager) super.findViewById(R.id.twopanelviewpager);
pager.setAdapter(adapter);
// default selected screen
pager.setCurrentItem(BOOKSHELF);
}
/*
* (non-Javadoc)
*
* @see android.support.v4.app.FragmentActivity#onStop()
*/
@Override
protected void onStop() {
Log.d(TAG, "onStop()");
super.onStop();
// Disable updates
bookshelfController.removeListeners();
}
/*
* (non-Javadoc)
*
* @see android.support.v4.app.FragmentActivity#onResume()
*/
@Override
protected void onResume() {
Log.d(TAG, "onResume()");
super.onResume();
/*
* if the application is stopped, it will go through onStart() and then
* onResume(), so just start the updates again here.
*/
bookshelfController.addPropertyChangeListener(this);
/*
* This method is run every time the application is created, starts or
* resumes. The updates should only start when returning from a paused
* or stopped state.
*/
}
/*
* (non-Javadoc)
*
* @see android.support.v4.app.FragmentActivity#onStart()
*/
@Override
protected void onStart() {
Log.d(TAG, "onStart()");
super.onStart();
}
/*
* (non-Javadoc)
*
* @see android.support.v4.app.FragmentActivity#onDestroy()
*/
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy()");
// Stop the audio player
playerController.stop();
/*
* Whenever the application is about to quit, save a bookmark.
*/
save();
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.main_menu, menu);
return true;
}
/**
* Saves a bookmark (the bookshelf).
*/
private boolean save() {
return bookshelfController.saveBookshelf(this, USERNAME);
}
/*
* Handle the menu item selection. Every item has a unique id.
*
* (non-Javadoc)
*
* @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.menu_save:
// save a bookmark and show whether it was successful
if (save()) {
Toast.makeText(this, "Saved", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Saving failed", Toast.LENGTH_SHORT);
}
return true;
default:
Toast.makeText(this, "Unknown", Toast.LENGTH_SHORT).show();
return super.onOptionsItemSelected(item);
}
}
/* IPlayerEvents */
/*
* The methods below relay user-initiated events from the fragments to
* relevant controller methods.
*/
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#previousTrack
* ()
*/
public void previousTrack() {
playerController.previousTrack();
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#resume()
*/
public void resume() {
playerController.resume();
}
/*
* (non-Javadoc)
*
* @see edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#pause()
*/
public void pause() {
playerController.pause();
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#nextTrack()
*/
public void nextTrack() {
playerController.nextTrack();
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#seekLeft
* (boolean)
*/
public void seekLeft(boolean seek) {
playerController.seekLeft(seek);
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#seekRight
* (boolean)
*/
public void seekRight(boolean seek) {
playerController.seekRight(seek);
}
/*
* (non-Javadoc)
*
* @see edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#
* seekToPercentageInBook(double)
*/
public void seekToPercentageInBook(double percentage) {
playerController.seekToPercentageInBook(percentage);
}
/*
* (non-Javadoc)
*
* @see edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#
* seekToPercentageInTrack(double)
*/
public void seekToPercentageInTrack(double percentage) {
playerController.seekToPercentageInTrack(percentage);
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#isPlaying()
*/
public boolean isPlaying() {
return playerController.isPlaying();
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IPlayerEvents#isStarted()
*/
public boolean isStarted() {
return playerController.isStarted();
}
/* End IPlayerEvents */
/* BookshelfUIListener */
public void setSelectedBook(int index) {
// set the selected book to the new index
bookshelfController.setSelectedBook(index);
}
/*
* (non-Javadoc)
*
* @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfGUIEvents#
* addBookButtonPressed()
*/
public void addBookButtonPressed() {
Intent intent = new Intent(MainActivity.this, BrowserActivity.class);
startActivity(intent);
}
/* End BookshelfUIListener */
/*
* (non-Javadoc)
*
* @see android.support.v4.app.FragmentActivity#onBackPressed()
*/
@Override
public void onBackPressed() {
super.onBackPressed();
Log.i(TAG, "Back pressed in Main Activity");
Intent intent = new Intent(Intent.ACTION_MAIN);
// move to the home screen when pressing back
intent.addCategory(Intent.CATEGORY_HOME);
// start a new task
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
/**
* Whenever the model component changes, an event is received here. The new
* value property of the event contains a reference to a copy of the model.
*
* @param event
* Event object that contains information about the change.
*/
public void propertyChange(PropertyChangeEvent event) {
if (event.getNewValue() instanceof Bookshelf) {
// Get the model copy from the update
Bookshelf bs = (Bookshelf) event.getNewValue();
// Update the fragments
updateFragments(event.getPropertyName(), bs);
}
}
/**
* Handles the updates of the fragments given a model to represent
* graphically.
*
* @param eventName
* The property name of the fired event.
* @param bs
* Model copy.
* @precondition The bookshelf instance must not be null.
*/
private void updateFragments(String eventName, Bookshelf bs) {
/*
* Check which event was fired, and do relevant updates in the
* fragments.
*/
if (eventName.equals(Constants.Event.BOOK_LIST_CHANGED)) {
// update the bookshelf GUI
bookshelfFragment.bookshelfUpdated(bs);
} else if (eventName.equals(Constants.Event.BOOK_SELECTED)) {
if (bs.getSelectedBookIndex() != Constants.Value.NO_BOOK_SELECTED) {
Book b = bs.getSelectedBook();
// reset the player controls to standard values
playerFragment.resetComponents();
// update the player GUI components
updatePlayerGUI(b);
// make sure the player GUI knows it is playing
playerFragment.setPlaybackStatus(PlaybackStatus.PLAYING);
// show the player UI
pager.setCurrentItem(PLAYER);
// if a track is selected, start it
if (bs.getSelectedTrackIndex() != Constants.Value.NO_TRACK_SELECTED) {
// start at the saved time
playerController.setStartPosition(b
.getSelectedTrackElapsedTime());
// select the stored track to start playing in
bookshelfController.setSelectedTrack(
bs.getSelectedBookIndex(),
bs.getSelectedTrackIndex());
} else {
// if there is no track selected, start from the beginning.
playerController.setStartPosition(0);
bookshelfController.setSelectedTrack(
bs.getSelectedBookIndex(), 0);
}
}
} else if (eventName.equals(Constants.Event.ELAPSED_TIME_CHANGED)) {
Book b = bs.getSelectedBook();
// Bookshelf
updateSelectedBookElapsedTime(b);
// Player
// recalculate the track seekbar
updateTrackSeekbar(b);
// recalculate the book seekbar
updateBookSeekbar(b);
// update time labels
updateElapsedTimeLabels(b);
} else if (eventName.equals(Constants.Event.TRACK_LIST_CHANGED)) {
Book b = bs.getSelectedBook();
// Update the bookshelf GUI.
bookshelfFragment.bookshelfUpdated(bs);
// Player
// update book duration label
updateBookDurationLabel(b);
// show the correct number of tracks
updateTrackCounterLabel(b);
// Update book duration label
updateBookDurationLabel(b);
} else if (eventName.equals(Constants.Event.TRACK_INDEX_CHANGED)) {
pager.setCurrentItem(PLAYER);
if (bs.getSelectedTrackIndex() == Constants.Value.NO_TRACK_SELECTED) {
/*
* Do not play audio.
*/
// reset the controls to 'stopped'
playerFragment.resetComponents();
// stop the audio player
playerController.stop();
} else {
/*
* Play audio.
*/
// restart the player
playerController.start();
// set the status
playerFragment.setPlaybackStatus(PlaybackStatus.PLAYING);
}
Book b = bs.getSelectedBook();
// Player
// update track title label
updateTrackTitleLabel(b);
// update track book duration label
updateTrackDurationLabel(b);
// show the correct number of tracks, illegal track index or not
updateTrackCounterLabel(b);
} else if (eventName.equals(Constants.Event.BOOK_TITLE_CHANGED)) {
Book b = bs.getSelectedBook();
// Bookshelf
// Player
// update the book title label
updateBookTitleLabel(b);
} else if (eventName.equals(Constants.Event.BOOKSHELF_UPDATED)) {
bookshelfFragment.bookshelfUpdated(bs);
}
/*
* Eventually tag updates would have been handled here as well, calling
* the private method updateTags. Tags are, however, not implemented in
* the GUI so they will not be written here, either.
*/
}
/**
* Updates the book title/duration, track title/duration and track counter
* (i.e. no changes to seek bars).
* <p>
* Does nothing if the given book is null.
*
* @param b
* Book given with the data to be represented by the Player UI.
*/
private void updatePlayerGUI(Book b) {
if (b != null) {
// show the title of the book
updateBookTitleLabel(b);
// display its duration
updateBookDurationLabel(b);
// show the title of the track
updateTrackTitleLabel(b);
// and display its duration
updateTrackDurationLabel(b);
// update the track counter
updateTrackCounterLabel(b);
}
}
/**
* Updates the tags in the player UI (unimplemented in GUI).
*
* @param selectedBook
*/
@SuppressWarnings("unused")
private void updateTags(final Book selectedBook) {
playerFragment.getActivity().runOnUiThread(new Runnable() {
public void run() {
playerFragment.updateTagTimes(selectedBook.getTagTimes());
}
});
}
/**
* Updates the count of the tracks in the player UI.
*
* @param b
*/
private void updateTrackCounterLabel(final Book b) {
playerFragment.getActivity().runOnUiThread(new Runnable() {
public void run() {
int currentTrack = b.getSelectedTrackIndex();
int numberOfTracks = b.getNumberOfTracks();
playerFragment.updateTrackCounterLabel(currentTrack,
numberOfTracks);
}
});
}
/*
* Titles
*/
/**
* UI mutator method that updates the book title label in the player
* fragment.
*
* @param b
* Book that specifies the change
*/
private void updateBookTitleLabel(final Book b) {
playerFragment.getActivity().runOnUiThread(new Runnable() {
public void run() {
playerFragment.updateBookTitleLabel(b.getSelectedBookTitle());
}
});
}
/**
* UI mutator method that updates the track title in the player fragment.
*
* @param b
* Book that specifies the change
*/
private void updateTrackTitleLabel(final Book b) {
playerFragment.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (b.getSelectedTrackIndex() != -1) {
playerFragment.updateTrackTitleLabel(b.getTrackTitle());
}
}
});
}
/*
* Duration times
*/
/**
* UI mutator method that updates the book duration label in the player
* fragment.
*
* @param b
* Book that specifies the change
*/
private void updateBookDurationLabel(final Book b) {
playerFragment.getActivity().runOnUiThread(new Runnable() {
public void run() {
playerFragment.updateBookDurationLabel(b.getDuration());
}
});
}
/**
* UI mutator method that updates the track duration label in the player
* fragment.
*
* @param b
* Book that specifies the change
*/
private void updateTrackDurationLabel(final Book b) {
playerFragment.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (b.getSelectedTrackIndex() != -1) {
int trackDur = 0;
try {
trackDur = b.getSelectedTrackDuration();
} catch (IllegalArgumentException e) {
Log.e(TAG, "No track selected when trying to update "
+ "track duration label. "
+ "Setting duration to 0.");
}
playerFragment.updateTrackDurationLabel(trackDur);
}
}
});
}
/*
* Elapsed times
*/
/**
* UI mutator method that updates the track duration label in the player
* fragment.
*
* @param b
* Book that specifies the change
*/
private void updateElapsedTimeLabels(final Book b) {
if (playerFragment.getActivity() != null) {
playerFragment.getActivity().runOnUiThread(new Runnable() {
public void run() {
if (b.getSelectedTrackIndex() != -1) {
playerFragment.updateTrackElapsedTimeLabel(b
.getSelectedTrackElapsedTime());
playerFragment.updateBookElapsedTimeLabel(b
.getBookElapsedTime());
}
}
});
}
}
/**
* UI mutator method that updates the book position of the selected book in
* bookshelf fragment
*
* @param b
* The selected book.
*/
private void updateSelectedBookElapsedTime(final Book b) {
if (b.getSelectedTrackIndex() != -1
&& bookshelfFragment.getActivity() != null) {
bookshelfFragment.getActivity().runOnUiThread(new Runnable() {
public void run() {
// update
bookshelfFragment.selectedBookElapsedTimeUpdated(b
.getBookElapsedTime());
}
});
}
}
/**
* UI mutator method that updates the track seekbar in the player fragment.
*
* @param b
* Book that specifies the change
*/
private void updateTrackSeekbar(Book b) {
if (b.getSelectedTrackIndex() != -1) {
// elapsed time
int trackElapsedTime = b.getSelectedTrackElapsedTime();
// total duration
int trackDuration = b.getSelectedTrackDuration();
double progress = getProgress(trackElapsedTime, trackDuration);
playerFragment.updateTrackSeekBar(progress);
}
}
/**
* UI mutator method that updates the book seekbar in the player fragment.
*
* @param b
* Book that specifies the change
*/
private void updateBookSeekbar(Book b) {
// elapsed time
int bookElapsedTime = b.getBookElapsedTime();
// total duration
int bookDuration = b.getDuration();
double progress = getProgress(bookElapsedTime, bookDuration);
playerFragment.updateBookSeekBar(progress);
}
/**
* Private utility method that calculates the progress of a book or track.
*
* @param int Elapsed time of the book or track in milliseconds.
* @param int Duration of the book or track in milliseconds.
* @return The ratio between the elapsed time and the duration.
*/
private double getProgress(int elapsedTime, int duration) {
return ((double) elapsedTime) / duration;
}
/*
* (non-Javadoc)
*
* @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#
* setSelectedTrack(int, int)
*/
public void setSelectedTrack(int bookIndex, int trackIndex) {
bookshelfController.setSelectedTrack(bookIndex, trackIndex);
// restart the player
playerController.start();
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#removeBook
* (int)
*/
public void removeBook(int bookIndex) {
bookshelfController.removeBook(bookIndex);
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#removeTrack
* (int, int)
*/
public void removeTrack(int bookIndex, int trackIndex) {
bookshelfController.removeTrack(bookIndex, trackIndex);
}
/*
* (non-Javadoc)
*
* @see edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#
* setBookTitleAt(int, java.lang.String)
*/
public void setBookTitleAt(int bookIndex, String newTitle) {
bookshelfController.setBookTitleAt(bookIndex, newTitle);
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#removeTrack
* (int)
*/
public void removeTrack(int trackIndex) {
bookshelfController.removeTrack(trackIndex);
}
/*
* (non-Javadoc)
*
* @see
* edu.chalmers.dat255.audiobookplayer.interfaces.IBookshelfEvents#moveTrack
* (int, int, int)
*/
public void moveTrack(int bookIndex, int trackIndex, int offset) {
bookshelfController.moveTrack(bookIndex, trackIndex, offset);
}
}