diff --git a/.classpath b/.classpath
index 609aa00..6e9239f 100644
--- a/.classpath
+++ b/.classpath
@@ -1,7 +1,7 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 8847d89..6ff8bbe 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,19 +1,23 @@
+ android:versionCode="3" android:versionName="1.0">
-
+
+
+
+
diff --git a/res/layout-land/player.xml b/res/layout-land/player.xml
new file mode 100644
index 0000000..c300b44
--- /dev/null
+++ b/res/layout-land/player.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/player.xml b/res/layout/player.xml
index ded3af9..4da52ff 100644
--- a/res/layout/player.xml
+++ b/res/layout/player.xml
@@ -1,46 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+ android:width="60dip" android:textSize="20sp" android:layout_toRightOf="@+id/station_list">
-
+ android:width="60dip" android:textSize="20sp">
-
+ android:width="60dip" android:textSize="20sp">
-
+ android:width="60dip" android:textSize="20sp">
diff --git a/res/layout/search_item.xml b/res/layout/search_item.xml
new file mode 100644
index 0000000..adce17b
--- /dev/null
+++ b/res/layout/search_item.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/station_creator.xml b/res/layout/station_creator.xml
new file mode 100644
index 0000000..f8664d3
--- /dev/null
+++ b/res/layout/station_creator.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/stations_item.xml b/res/layout/stations_item.xml
index 96be11f..8cc04e6 100644
--- a/res/layout/stations_item.xml
+++ b/res/layout/stations_item.xml
@@ -8,6 +8,6 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
- android:textSize="16sp" android:id="@+id/stations_name">
+ android:textSize="8pt" android:id="@+id/stations_name">
\ No newline at end of file
diff --git a/res/menu/player_menu.xml b/res/menu/player_menu.xml
index 16dfcf6..3242e01 100644
--- a/res/menu/player_menu.xml
+++ b/res/menu/player_menu.xml
@@ -2,5 +2,6 @@
diff --git a/res/menu/station_select_menu.xml b/res/menu/station_select_menu.xml
new file mode 100644
index 0000000..4e7757b
--- /dev/null
+++ b/res/menu/station_select_menu.xml
@@ -0,0 +1,4 @@
+
+
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 8440815..4a25865 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -10,4 +10,19 @@
- mp3
- mp3-hifi
+
+ - 0
+ - 1
+ - 2
+
+
+ - Low
+ - Normal
+ - High
+
+
+ - Song
+ - Artist
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f4fd2d3..898adca 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -6,4 +6,8 @@
Loading %s...
Buzz-kill...
Rock on!
+ No song currently playing...
+ Loading next song...
+ Search
+ Searching...
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 3195e1b..fc35c28 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -24,6 +24,7 @@
+
diff --git a/src/com/aregner/android/pandoid/AlbumArtDownloader.java b/src/com/aregner/android/pandoid/AlbumArtDownloader.java
new file mode 100644
index 0000000..e51de1d
--- /dev/null
+++ b/src/com/aregner/android/pandoid/AlbumArtDownloader.java
@@ -0,0 +1,149 @@
+package com.aregner.android.pandoid;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import android.util.Log;
+
+import com.aregner.pandora.Song;
+
+
+public class AlbumArtDownloader {
+
+ public static final String SMALL = "small";
+ public static final String MEDIUM = "medium";
+ public static final String LARGE = "large";
+ public static final String XLARGE = "extralarge";
+ public static final String MEGA = "mega";
+
+ public static final int PREF_NORMAL = 0;
+ public static final int PREF_HIRES = 1;
+ public static final int PREF_MEGA = 2;
+
+
+ private static final int ALBUM_ARTIST = 1; //look for album art with the album and the artist
+ private static final int ARTIST_TITLE = 2; //look for album art using the artist and title
+
+ private static final String LOG_TAG = "AlbumArtDownloader";
+
+ private String apiKey = "3f6527e63fa7ab4771a687ce39377cf8";
+ private String artist, title, album;
+
+ private Map urls = new HashMap();
+
+ public AlbumArtDownloader(Song song){
+ String request;
+ artist = song.getArtist();
+ title = song.getTitle();
+ album = song.getAlbum();
+
+ request = formRequest(ALBUM_ARTIST);
+ if(!sendRequest(request)) {
+ request = formRequest(ARTIST_TITLE);
+ sendRequest(request);
+ }
+ }
+
+ private String formRequest(int method){
+ String methodCall, request, parameters = "";
+
+ if(method == ALBUM_ARTIST) {
+ methodCall = "album.getinfo";
+ parameters += "&album=" + album.replaceAll(" ", "%20");
+ }
+ else if(method == ARTIST_TITLE) {
+ methodCall = "track.getinfo";
+ Log.i("PandoidPlayer", "Could not find using album and artist, using track and artist");
+ parameters += "&track=" + title.replaceAll(" ", "%20");
+ }
+ else {
+ return null;
+ }
+
+ parameters +="&artist=" + artist.replaceAll(" ", "%20");
+ request = "http://ws.audioscrobbler.com/2.0/?method=" + methodCall + "&api_key="+apiKey;
+ request += parameters;
+
+ Log.i(LOG_TAG, request);
+ return request;
+ }
+
+ private boolean sendRequest(String request) {
+ String imgUrl = null;
+ boolean success = true;
+ try {
+ URL url = new URL(request);
+ InputStream is = url.openStream();
+ DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ Document doc = db.parse(is);
+
+ NodeList nl = doc.getElementsByTagName("image");
+ for (int i = 0; i < nl.getLength(); i++) {
+
+ Node n = nl.item(i);
+ String imageSize = n.getAttributes().item(0).getNodeValue();
+ Node fc = n.getFirstChild();
+
+ if (fc != null){
+ imgUrl = fc.getNodeValue();
+
+ if(imgUrl.trim().length() > 0) {
+ //get rid of urls that the app passes when there is no artwork available: noimage
+ //and if you get repeated bad requests: /serve/174s
+ if(!imgUrl.contains("noimage")) {
+ urls.put(imageSize, imgUrl);
+ }
+ }
+ }
+ }
+ if(urls.size() == 0){
+ success = false;
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ success = false;
+ }
+ return success;
+ }
+
+ public String getAlbumUrl(String exactSize) {
+ Log.i(LOG_TAG,"Returning url:" + urls.get(exactSize));
+ return urls.get(exactSize);
+ }
+
+ public String getAlbumUrl(int preference){
+ if(preference == PREF_NORMAL){
+ return urls.get(LARGE);
+ }
+ else if(preference == PREF_HIRES){
+ if(urls.get(XLARGE)!= null){
+ return urls.get(XLARGE);
+ }
+ else if(urls.get(LARGE) != null){
+ return urls.get(LARGE);
+ }
+ }
+ else if(preference == PREF_MEGA) {
+ if(urls.get(MEGA) != null){
+ return urls.get(MEGA);
+ }
+ else if(urls.get(XLARGE)!= null){
+ return urls.get(XLARGE);
+ }
+ else if(urls.get(LARGE) != null) {
+ return urls.get(LARGE);
+ }
+ else return null;
+ }
+ return null;
+ }
+}
diff --git a/src/com/aregner/android/pandoid/ImageDownloader.java b/src/com/aregner/android/pandoid/ImageDownloader.java
index ffc2733..f6c1715 100644
--- a/src/com/aregner/android/pandoid/ImageDownloader.java
+++ b/src/com/aregner/android/pandoid/ImageDownloader.java
@@ -55,9 +55,10 @@ public class ImageDownloader {
private static final int HARD_CACHE_CAPACITY = 40;
private static final int DELAY_BEFORE_PURGE = 30 * 1000; // in milliseconds
-
+ private boolean done = false;
// Hard cache, with a fixed maximum capacity and a life duration
- private final HashMap sHardBitmapCache =
+ @SuppressWarnings("serial")
+ private final HashMap sHardBitmapCache =
new LinkedHashMap(HARD_CACHE_CAPACITY / 2, 0.75f, true) {
@Override
protected boolean removeEldestEntry(LinkedHashMap.Entry eldest) {
@@ -91,6 +92,7 @@ public void run() {
* @param imageView The ImageView to bind the downloaded image to.
*/
public void download(String url, ImageView imageView) {
+ done = false;
download(url, imageView, null);
}
@@ -103,6 +105,7 @@ public void download(String url, ImageView imageView) {
* @param cookie A cookie String that will be used by the http connection.
*/
public void download(String url, ImageView imageView, String cookie) {
+ done = false;
resetPurgeTimer();
Bitmap bitmap = getBitmapFromCache(url);
@@ -111,8 +114,14 @@ public void download(String url, ImageView imageView, String cookie) {
} else {
cancelPotentialDownload(url, imageView);
imageView.setImageBitmap(bitmap);
+ done = true;
+ PandoidPlayer.imageDownloadFinished();
+
}
}
+ public boolean isDoneDownloading(){
+ return done;
+ }
/*
* Same as download but the image is always downloaded and the cache is not used.
@@ -130,6 +139,7 @@ private void forceDownload(String url, ImageView imageView, String cookie) {
// State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys.
if (url == null) {
imageView.setImageDrawable(null);
+ done = true;
return;
}
@@ -137,6 +147,7 @@ private void forceDownload(String url, ImageView imageView, String cookie) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
+
task.execute(url, cookie);
}
}
@@ -327,6 +338,8 @@ protected void onPostExecute(Bitmap bitmap) {
// Change bitmap only if this process is still associated with it
if (this == bitmapDownloaderTask) {
imageView.setImageBitmap(bitmap);
+ done = true;
+ PandoidPlayer.imageDownloadFinished();
}
}
}
diff --git a/src/com/aregner/android/pandoid/PandoidLogin.java b/src/com/aregner/android/pandoid/PandoidLogin.java
index cd1c887..28bc3f2 100644
--- a/src/com/aregner/android/pandoid/PandoidLogin.java
+++ b/src/com/aregner/android/pandoid/PandoidLogin.java
@@ -18,6 +18,7 @@
package com.aregner.android.pandoid;
import android.app.Activity;
+import android.media.AudioManager;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.Menu;
@@ -34,6 +35,8 @@ public class PandoidLogin extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login);
+
+ this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
((Button)findViewById(R.id.login_button)).setOnClickListener(new OnClickListener() {
public void onClick(View viewParam) {
@@ -50,8 +53,6 @@ public void onClick(View viewParam) {
if(success) {
setResult(RESULT_OK);
finish();
- //finishActivityFromChild(child, PandoidPlayer.REQUIRE_LOGIN_CREDS);
- //finishActivity(PandoidPlayer.REQUIRE_LOGIN_CREDS);
}
}
}
diff --git a/src/com/aregner/android/pandoid/PandoidPlayer.java b/src/com/aregner/android/pandoid/PandoidPlayer.java
index 88e41c8..bba28e0 100644
--- a/src/com/aregner/android/pandoid/PandoidPlayer.java
+++ b/src/com/aregner/android/pandoid/PandoidPlayer.java
@@ -17,66 +17,141 @@
*/
package com.aregner.android.pandoid;
+import com.aregner.android.pandoid.PandoraRadioService;
import com.aregner.pandora.Song;
import android.app.Activity;
-import android.app.Dialog;
import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnPreparedListener;
+import android.content.pm.ActivityInfo;
+import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.widget.Button;
import android.widget.ImageView;
+import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
-
public class PandoidPlayer extends Activity {
public static final int REQUIRE_SELECT_STATION = 0x10;
public static final int REQUIRE_LOGIN_CREDS = 0x20;
+ public static final int GET_STATIONS_FAILED = 3;
+ public static final int PLAY_RECENT = 4;
+ public static final int SEARCH_ACTIVITY = 5;
public static final String RATING_BAN = "ban";
public static final String RATING_LOVE = "love";
- public static final String RATING_NONE = null;
-
+ public static final String RATING_NONE = null;
+
+ private static ImageDownloader imageDownloader = new ImageDownloader();
+ private static PandoraRadioService pandora;
+ private static ProgressBar progress;
private static ProgressDialog waiting;
- private PandoraRadioService pandora;
+ private static Button playButton;
+
+ private boolean initialLogin = false;
+ private String lastPlayedSong = "";
+ private IntentFilter intentFilter;
private SharedPreferences prefs;
- private ImageDownloader imageDownloader = new ImageDownloader();
+ private ImageView image, cache;
+
+ private static String LOG_TAG = "PandoidPlayer";
+ private static String SETUP_TAG = "InitialSetupTask";
+ private static String STATION_TAG = "PlayStationTask";
+
+ private BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(LOG_TAG, "Song Change Broadcast Received");
+
+ cache = null;
+ updateForNewSong();
+ }
+ };
+
+
+ public static void imageDownloadFinished() {
+ if(progress != null){
+ progress.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public static void dismissWaiting() {
+ if(waiting != null && waiting.isShowing()) {
+ Log.i(LOG_TAG, "Called dismissWaiting() with positive result.");
+ waiting.dismiss();
+ }
+ }
+
+ public static void togglePlayButton(){
+ playButton.post(new Runnable() {
+ @Override
+ public void run() {
+ if(playButton != null){
+ if(pandora.isPlaying()){
+ playButton.setText("||");
+ }
+ else{
+ playButton.setText(">");
+ }
+ }
+ }
+ });
+ }
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ Log.i(LOG_TAG, "Activity Created");
+ super.onCreate(savedInstanceState);
setContentView(R.layout.player);
-
- if(PandoraRadioService.getInstance(false) == null) {
- // handle for the preferences for us to use everywhere
- prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
-
+
+ this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ //initializations
+ //if there is a cached album cover use it.
+ cache = (ImageView) getLastNonConfigurationInstance();
+ playButton = (Button) findViewById(R.id.player_pause);
+ prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
+ progress = (ProgressBar)findViewById(R.id.progress);
+ pandora = PandoraRadioService.getInstance(false);
+
+ if(pandora == null) {
+ Log.i(LOG_TAG, "Service is null. Getting credentials from prefs");
// look for what we need to continue with pandora auth
String username = prefs.getString("pandora_username", null);
String password = prefs.getString("pandora_password", null);
if(username == null || password == null) {
// bring them to the login screen so they can enter what we need
- startActivityForResult(new Intent(getApplicationContext(), PandoidLogin.class), REQUIRE_LOGIN_CREDS);
+ Log.i(LOG_TAG, "Calling PandoidLogin.class");
+ initialLogin = true;
+ startActivityForResult(new Intent(getApplicationContext(), PandoidLogin.class).addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP), REQUIRE_LOGIN_CREDS);
}
}
- else {
- pandora = PandoraRadioService.getInstance(false);
- updateForNewSong(pandora.getCurrentSong());
+ }
+ /**
+ * Returns the current image being use for the album cover.
+ */
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ if(image != null && imageDownloader.isDoneDownloading()){
+ return image;
}
+ return null;
}
-
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
@@ -84,68 +159,141 @@ public boolean onCreateOptionsMenu(Menu menu) {
return true;
}
- @Override
- protected void onStart() {
- super.onStart();
- }
-
@Override
protected void onResume() {
super.onResume();
- // The activity has become visible (it is now "resumed").
- serviceSetup();
+ Log.i(LOG_TAG, "Resuming Activity...");
+
+ if(!initialLogin){
+ serviceSetup();
+ }
+ updateForNewSong();
+
+ //register the receiver for song changes
+ Log.i(LOG_TAG, "Registering Receiver...");
+ intentFilter = new IntentFilter();
+ intentFilter.addAction(PandoraRadioService.SONG_CHANGE);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON); //check for need of UI update if screen has been off
+ registerReceiver(receiver, intentFilter);
}
private void serviceSetup() {
if(pandora == null || !(pandora instanceof PandoraRadioService)) {
- (new InitialSetupTask()).execute();
+ Log.i(LOG_TAG, "Executing InitialSetupTask");
+ new InitialSetupTask().execute();
}
}
-
- protected void updateForNewSong(Song song) {
- TextView top = (TextView) findViewById(R.id.player_topText);
- TextView bottom = (TextView) findViewById(R.id.player_bottomText);
- ImageView image = (ImageView) findViewById(R.id.player_image);
-
- top.setText(String.format("%s by %s", song.getTitle(), song.getArtist()));
- imageDownloader.download(song.getAlbumCoverUrl(), image);
- bottom.setText(String.format("%s", song.getAlbum()));
+
+ protected void updateForNewSong() {
+ Log.i(LOG_TAG, "updateForNewSong() called..");
+ if(pandora != null && pandora.isReadytoUpdateUI()){
+
+ TextView top, bottom, middle;
+ Song song;
+
+ song = pandora.getCurrentSong();
+
+ top = (TextView) findViewById(R.id.player_topText);
+ bottom = (TextView) findViewById(R.id.player_bottomText);
+ middle = (TextView)findViewById(R.id.player_middleText);
+ image = (ImageView) findViewById(R.id.player_image);
+
+ togglePlayButton();
+
+ if(cache == null) {
+ new GetAlbumArtUrlTask().execute(song);
+ }
+ else
+ image.setImageDrawable(cache.getDrawable());
+
+ top.setText(String.format("%s", song.getTitle()));
+ middle.setText(String.format("%s", song.getArtist()));
+ bottom.setText(String.format("%s", song.getAlbum()));
+
+ }
}
-
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+
if(requestCode == REQUIRE_SELECT_STATION && resultCode == RESULT_OK) {
+ Log.i(LOG_TAG, "PandoraStationSelect returned with result_ok");
pandora.setCurrentStationId(data.getLongExtra("stationId", -1));
- (new PlayStationTask()).execute();
+ new PlayStationTask().execute();
+ }
+ else if(requestCode == REQUIRE_SELECT_STATION && resultCode == GET_STATIONS_FAILED ) {
+ Log.i(LOG_TAG, "Reauthentication necessary...attempting");
+ reauthenticate();
}
else if(requestCode == REQUIRE_LOGIN_CREDS && resultCode == RESULT_OK) {
+ Log.i(LOG_TAG, "PandoraLogin.class returned ok...");
serviceSetup();
}
}
+
+ protected void reauthenticate() {
+ pandora.signOut();
+ pandora = null;
+ initialLogin = true;
+ serviceSetup();
+ }
public void controlButtonPressed(View button) {
+ String toastMessage;
+
switch(button.getId()) {
- case R.id.player_ban:
- pandora.rate(RATING_BAN);
- Toast.makeText(getApplicationContext(), getString(R.string.baned_song), Toast.LENGTH_SHORT).show();
- if(prefs.getBoolean("behave_nextOnBan", true)) {
- updateForNewSong(pandora.next());
- }
- break;
-
- case R.id.player_love:
- pandora.rate(RATING_LOVE);
- Toast.makeText(getApplicationContext(), getString(R.string.loved_song), Toast.LENGTH_SHORT).show();
- break;
-
- case R.id.player_pause:
- pandora.pause();
- break;
+ case R.id.station_list:
+ startActivityForResult(new Intent(getApplicationContext(), PandoidStationSelect.class), REQUIRE_SELECT_STATION);
+ break;
+
+ case R.id.player_ban:
+ if(pandora.isPlaying()) {
+
+ pandora.rate(RATING_BAN);
+ toastMessage = getString(R.string.baned_song);
+
+ if(prefs.getBoolean("behave_nextOnBan", true)) {
+ new PlayNextTask().execute();
+ }
+ }
+
+ else {
+ toastMessage = getString(R.string.no_song);
+ }
+
+ Toast.makeText(getApplicationContext(), toastMessage, Toast.LENGTH_SHORT).show();
+ break;
+
+ case R.id.player_love:
+ if(pandora.isPlaying()){
+ pandora.rate(RATING_LOVE);
+ toastMessage = getString(R.string.loved_song);
+ }
+ else {
+ toastMessage = getString(R.string.no_song);
+ }
+ Toast.makeText(getApplicationContext(), toastMessage, Toast.LENGTH_SHORT).show();
+ break;
+
+ case R.id.player_pause:
+ if(pandora.isPlayable()){
+ pandora.pause();
+ }
+ else {
+ startActivityForResult(new Intent(PandoidPlayer.this, PandoidStationSelect.class), REQUIRE_SELECT_STATION);
+ }
+ break;
- case R.id.player_next:
- updateForNewSong(pandora.next());
- break;
+
+ case R.id.player_next:
+ if(pandora.isPlayable()) {
+ new PlayNextTask().execute();
+ }
+ else {
+ startActivityForResult(new Intent(this, PandoidStationSelect.class), REQUIRE_SELECT_STATION);
+ }
+ break;
}
}
@@ -163,12 +311,15 @@ public boolean onOptionsItemSelected(MenuItem item) {
.putString("pandora_username", null)
.putString("pandora_password", null)
.commit();
- startActivityForResult(new Intent(getApplicationContext(), PandoidLogin.class), REQUIRE_LOGIN_CREDS);
+ finish();
return true;
case R.id.menu_settings:
startActivity(new Intent(getApplicationContext(), PandoidSettings.class));
return true;
+
+ case R.id.menu_recently_played:
+ startActivity(new Intent(getApplicationContext(), PandoidRecentlyPlayedList.class));
default:
return super.onOptionsItemSelected(item);
@@ -177,23 +328,27 @@ public boolean onOptionsItemSelected(MenuItem item) {
/** Signs in the user and loads their initial data
* -> brings them toward a station */
- private class InitialSetupTask extends AsyncTask {
+ class InitialSetupTask extends AsyncTask{
@Override
protected void onPreExecute() {
- waiting = ProgressDialog.show(PandoidPlayer.this, "", getString(R.string.signing_in));
+ Log.i(SETUP_TAG,"Starting task...");
+ lockOrientation();
+ waiting = ProgressDialog.show(PandoidPlayer.this, "", getString(R.string.signing_in));
}
@Override
protected Boolean doInBackground(Void... arg) {
- PandoraRadioService.createPandoraRadioService(getApplicationContext());
+ PandoraRadioService.createPandoraRadioService(PandoidPlayer.this);
pandora = PandoraRadioService.getInstance(true);
String username = prefs.getString("pandora_username", null);
String password = prefs.getString("pandora_password", null);
try {
+ Log.i(SETUP_TAG, "Attempting to sign in using prefs credentials...");
pandora.signIn(username, password);
} catch(Exception ex) {
+ Log.e(SETUP_TAG, "Failed to sign in...", ex);
ex.printStackTrace();
}
return pandora.isAlive();
@@ -201,75 +356,141 @@ protected Boolean doInBackground(Void... arg) {
@Override
protected void onPostExecute(Boolean result) {
-
+
+ Log.i(SETUP_TAG, "Finished signin...checking results");
+ Log.i(SETUP_TAG, "calling dismissWaiting()");
+ unlockOrientation();
dismissWaiting();
- if(result.booleanValue()) {
-
+ if(result.booleanValue() && result) {
+ Log.i(SETUP_TAG, "Sign in success...");
if(!pandora.isPlaying()) {
if(pandora.isPlayable()) {
// play it or resume playback or something smart like that
+ Log.i(SETUP_TAG, "Calling new PlayStationTask...");
(new PlayStationTask()).execute();
}
else {
// ask them to select a station
- startActivityForResult(new Intent(getApplicationContext(), PandoidStationSelect.class), REQUIRE_SELECT_STATION);
+ Log.i(SETUP_TAG, "Need station. Calling PandoidStationSelect.class...");
+ startActivityForResult(new Intent(PandoidPlayer.this, PandoidStationSelect.class), REQUIRE_SELECT_STATION);
}
}
}
else {
// failed to sign in for some reason
- Toast.makeText(getApplicationContext(), getString(R.string.signin_failed), Toast.LENGTH_SHORT).show();
- startActivityForResult(new Intent(getApplicationContext(), PandoidLogin.class), REQUIRE_LOGIN_CREDS);
+ Log.e(SETUP_TAG, "Sign in failed...Calling PandoidLogin.class");
+ Toast.makeText(PandoidPlayer.this, getString(R.string.signin_failed), Toast.LENGTH_SHORT).show();
+ startActivityForResult(new Intent(getApplicationContext(), PandoidLogin.class).addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP), REQUIRE_LOGIN_CREDS);
}
}
}
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if ((keyCode == KeyEvent.KEYCODE_SEARCH)) {
+ Log.d(this.getClass().getName(), "Search Key pressed");
+ startActivityForResult(new Intent(getApplicationContext(), PandoidSearchActivity.class), REQUIRE_SELECT_STATION);
+ }
+ return super.onKeyDown(keyCode, event);
+ }
/** Prepares a selected station to be played */
private class PlayStationTask extends AsyncTask {
@Override
protected void onPreExecute() {
+ Log.i(STATION_TAG, "Starting...ataching activity");
+ lockOrientation();
waiting = ProgressDialog.show(PandoidPlayer.this, "", getString(R.string.loading, pandora.getCurrentStation().getName()));
}
@Override
protected Void doInBackground(Void... arg0) {
- pandora.setListener(OnCompletionListener.class, new OnCompletionListener() {
- public void onCompletion(MediaPlayer mp) {
- updateForNewSong(pandora.next());
- }
- });
- pandora.setListener(OnPreparedListener.class, new OnPreparedListener() {
- public void onPrepared(MediaPlayer mp) {
- }
- });
pandora.prepare();
-
+
return null;
}
@Override
protected void onPostExecute(Void result) {
- updateForNewSong(pandora.play());
+ Log.i(STATION_TAG, "In postExecute");
+ unlockOrientation();
+ pandora.play();
dismissWaiting();
}
+ }
+ class PlayNextTask extends AsyncTask{
+
+ @Override
+ protected void onPreExecute() {
+ lockOrientation();
+ waiting = ProgressDialog.show(PandoidPlayer.this, "", getString(R.string.next_song));
+ }
+ @Override
+ protected Void doInBackground(Void ...voids) {
+ pandora.next();
+ return null;
+ }
+ @Override
+ protected void onPostExecute(Void result) {
+ unlockOrientation();
+ dismissWaiting();
+ }
}
-
- public static void dismissWaiting() {
- if(waiting != null && waiting.isShowing()) {
- waiting.dismiss();
+ class GetAlbumArtUrlTask extends AsyncTask {
+
+ @Override
+ protected void onPreExecute(){
+ image.setImageBitmap(null);
+ progress = (ProgressBar) findViewById(R.id.progress);
+ if(progress != null){
+ progress.setVisibility(View.VISIBLE);
+ }
+ }
+ @Override
+ protected String doInBackground(Song... params) {
+
+ String url;
+ Song song = params[0];
+
+ String albumResPref = prefs.getString("pandora_albumArtRes", "0");
+ int preference = Integer.parseInt(albumResPref);
+
+ AlbumArtDownloader aad = new AlbumArtDownloader(song);
+ url = aad.getAlbumUrl(preference);
+
+ if(url == null || url.length() == 0) {
+ Log.i(LOG_TAG, "Couldn't find lastFm album artwork, reverting to pandora...");
+ url = song.getAlbumCoverUrl();
+ }
+ Log.i(LOG_TAG,"album url = " + url);
+
+ return url;
+ }
+ @Override
+ protected void onPostExecute(String url){
+ if(url != null){
+
+ ImageView image = (ImageView)findViewById(R.id.player_image);
+ imageDownloader.download(url, image);
+ }
}
}
+
+ public void lockOrientation() {
+ this.setRequestedOrientation(getResources().getConfiguration().orientation);
+ Log.i(LOG_TAG, "Locking orientation: " + getResources().getConfiguration().orientation );
+ }
+ public void unlockOrientation() {
+ this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ Log.i(LOG_TAG, "Unlocking orientation: " + getResources().getConfiguration().orientation);
+ }
@Override
protected void onPause() {
super.onPause();
- // Another activity is taking focus (this activity is about to be "paused").
- }
-
- @Override
- protected void onStop() {
- super.onStop();
+
+ unregisterReceiver(receiver);
+ dismissWaiting();
}
}
\ No newline at end of file
diff --git a/src/com/aregner/android/pandoid/PandoidRecentlyPlayedList.java b/src/com/aregner/android/pandoid/PandoidRecentlyPlayedList.java
new file mode 100644
index 0000000..60e4586
--- /dev/null
+++ b/src/com/aregner/android/pandoid/PandoidRecentlyPlayedList.java
@@ -0,0 +1,212 @@
+package com.aregner.android.pandoid;
+
+import java.util.List;
+
+import com.aregner.pandora.Song;
+
+import android.app.Activity;
+import android.app.ListActivity;
+//import android.app.ProgressDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+
+
+
+public class PandoidRecentlyPlayedList extends ListActivity{
+ public static final String LOG_TAG = "PandoidRecentlyPlayedList";
+ private static PandoraRadioService pandora;
+ RecentListAdapter adapter;
+ List results;
+
+ private static PlaySongTask playSongTask;
+// private static ProgressDialog waiting;
+ private static boolean loading = false;
+
+ IntentFilter filter = new IntentFilter();
+ private BroadcastReceiver receiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(LOG_TAG, "Song Change Broadcast Received");
+ adapter.notifyDataSetChanged();
+ }
+ };
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ pandora = PandoraRadioService.getInstance(true);
+ results = pandora.getRecentlyPlayed();
+ adapter = new RecentListAdapter(results, this);
+
+ final ListView lv = getListView();
+ setListAdapter(adapter);
+ registerForContextMenu(lv);
+ lv.setTextFilterEnabled(true);
+
+ lv.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ view.showContextMenu();
+ }
+ });
+
+ if(loading){
+ playSongTask = (PlaySongTask) getLastNonConfigurationInstance();
+ playSongTask.attach(this);
+ }
+
+ }
+ @Override
+ public Object onRetainNonConfigurationInstance(){
+ if(playSongTask != null)
+ playSongTask.detach();
+
+ return playSongTask;
+ }
+ @Override
+ public void onSaveInstanceState(Bundle outState){
+ outState.putBoolean("loading", loading);
+ }
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState){
+ loading = savedInstanceState.getBoolean("loading");
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ filter.addAction(PandoraRadioService.SONG_CHANGE);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ registerReceiver(receiver, filter);
+ }
+ protected void onPause(){
+ super.onPause();
+ unregisterReceiver(receiver);
+ }
+ protected void onStop(){
+ super.onStop();
+ }
+ protected void onDestroy(){
+ super.onDestroy();
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ menu.setHeaderTitle("Options");
+ menu.add("Play");
+ // menu.add("Download");
+ }
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+ playSongTask = (PlaySongTask) new PlaySongTask(this).execute(results.get(info.position));
+
+ return true;
+ }
+
+ private class RecentListAdapter extends BaseAdapter {
+
+ private List results;
+ private Context context;
+
+ public RecentListAdapter(List results, Context context) {
+ this.results = results;
+ this.context = context;
+ }
+
+ public int getCount() {
+ return results.size();
+ }
+
+ public Song getItem(int position) {
+ return results.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ String artist, title;
+ Song result = results.get(position);
+
+ View itemLayout = LayoutInflater.from(context).inflate(R.layout.search_item, parent, false);
+
+ TextView tvName = (TextView) itemLayout.findViewById(R.id.item_name);
+
+ artist = result.getArtist();
+ title = result.getTitle();
+
+ if(title != null){
+ title += " by ";
+ }
+ else {
+ title = "";
+ }
+
+ tvName.setText(title + artist);
+
+
+ return itemLayout;
+ }
+ }
+
+ static class PlaySongTask extends AsyncTask {
+
+ private Activity activity;
+
+ public PlaySongTask(Activity activity){
+ this.activity = activity;
+ }
+ @Override
+ protected void onPreExecute() {
+ loading = true;
+ // waiting = ProgressDialog.show(activity, "", activity.getString(R.string.searching));
+ }
+ @Override
+ protected Void doInBackground(Song... query) {
+ pandora.play(query[0]);
+ return null;
+ }
+ @Override
+ protected void onPostExecute(Void result) {
+ if(activity != null){
+ // ` waiting.dismiss();
+ }
+ loading = false;
+ }
+ public void detach(){
+ this.activity = null;
+ }
+ public void attach(Activity activity){
+ this.activity = activity;
+
+ // waiting = ProgressDialog.show(activity, "", activity.getString(R.string.searching));
+ }
+ }
+
+}
+
diff --git a/src/com/aregner/android/pandoid/PandoidSearchActivity.java b/src/com/aregner/android/pandoid/PandoidSearchActivity.java
new file mode 100644
index 0000000..851718d
--- /dev/null
+++ b/src/com/aregner/android/pandoid/PandoidSearchActivity.java
@@ -0,0 +1,136 @@
+package com.aregner.android.pandoid;
+
+import com.aregner.pandora.Station;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.Spinner;
+
+public class PandoidSearchActivity extends Activity {
+
+ private static final int SEARCH_RESULT = 0x01;
+ private static PandoraRadioService pandora;
+ private static boolean searching = false;
+ private static SearchTask searchTask;
+ private static String searchTextSave;
+
+ private static ProgressDialog waiting;
+ private EditText searchQuery;
+
+
+ public void onCreate(Bundle savedInstanceState){
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.station_creator);
+ this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ pandora = PandoraRadioService.getInstance(false);
+ searchQuery = (EditText)findViewById(R.id.searchQuery);
+
+ if(searching){
+ searchTask = (SearchTask) getLastNonConfigurationInstance();
+ searchTask.attach(this);
+ }
+ }
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+
+ if(requestCode == SEARCH_RESULT && resultCode == RESULT_OK){
+ String musicId = intent.getStringExtra("musicId");
+ Station station = pandora.createStation(musicId);
+ Intent data = new Intent().putExtra("stationId", station.getId());
+ setResult(Activity.RESULT_OK, data);
+
+ finish();
+ }
+ }
+ @Override
+ public Object onRetainNonConfigurationInstance(){
+ if(searchTask != null)
+ searchTask.detach();
+
+ return searchTask;
+ }
+ @Override
+ public void onSaveInstanceState(Bundle outState){
+ outState.putBoolean("searching", searching);
+ searchTextSave = searchQuery.getText().toString();
+ outState.putString("searchSting", searchTextSave);
+ }
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState){
+ searching = savedInstanceState.getBoolean("waiting");
+ searchTextSave = savedInstanceState.getString("searchString");
+
+ if(searchTextSave != null){
+ searchQuery.setText(searchTextSave);
+ }
+ }
+ public void onSearchQuery(View button){
+ String query = searchQuery.getText().toString();
+
+ searchTask = (SearchTask) new SearchTask(this).execute(query);
+ }
+ protected void onStop(){
+ super.onStop();
+ if(waiting != null){
+ waiting.dismiss();
+ }
+ }
+ protected void onPause(){
+ super.onPause();
+ if(waiting != null){
+ waiting.dismiss();
+ }
+ }
+ static class SearchTask extends AsyncTask {
+
+ private Activity activity;
+
+ public SearchTask(Activity activity){
+ this.activity = activity;
+ }
+ @Override
+ protected void onPreExecute() {
+ searching = true;
+ waiting = ProgressDialog.show(activity, "", activity.getString(R.string.searching));
+ }
+ @Override
+ protected Void doInBackground(String... query) {
+ Spinner spin = (Spinner)(activity.findViewById(R.id.queryType));
+ String text = spin.getSelectedItem().toString();
+ try{
+ pandora.search(query[0], text);
+ if(activity != null){
+ activity.startActivityForResult(new Intent(activity, PandoidSearchResultList.class), SEARCH_RESULT);
+ }
+ searching = false;
+ }
+ catch(NullPointerException e){
+ activity.setResult(PandoidPlayer.GET_STATIONS_FAILED);
+ activity.finish();
+ }
+ return null;
+ }
+ @Override
+ protected void onPostExecute(Void result) {
+ if(activity != null){
+ waiting.dismiss();
+ }
+ searching = false;
+ }
+ public void detach(){
+ this.activity = null;
+ }
+ public void attach(Activity activity){
+ this.activity = activity;
+
+ waiting = ProgressDialog.show(activity, "", activity.getString(R.string.searching));
+ }
+ }
+}
diff --git a/src/com/aregner/android/pandoid/PandoidSearchResultList.java b/src/com/aregner/android/pandoid/PandoidSearchResultList.java
new file mode 100644
index 0000000..20d2a8c
--- /dev/null
+++ b/src/com/aregner/android/pandoid/PandoidSearchResultList.java
@@ -0,0 +1,101 @@
+package com.aregner.android.pandoid;
+
+import java.util.List;
+
+import com.aregner.pandora.SearchResult;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+
+public class PandoidSearchResultList extends ListActivity {
+ private PandoraRadioService pandora;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ pandora = PandoraRadioService.getInstance(true);
+ final List results = pandora.getSearchResults();
+ ListView lv = getListView();
+ setListAdapter(new ResultListAdapter(results, this));
+ lv.setTextFilterEnabled(true);
+ lv.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ String musicId = results.get(position).getMusicId();
+ setResult(RESULT_OK, (new Intent()).putExtra("musicId", musicId));
+ finish();
+ }
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ PandoidPlayer.dismissWaiting();
+ }
+
+ private class ResultListAdapter extends BaseAdapter {
+
+ private List results;
+ private Context context;
+
+ public ResultListAdapter(List results, Context context) {
+ this.results = results;
+ this.context = context;
+ }
+
+ public int getCount() {
+ return results.size();
+ }
+
+ public SearchResult getItem(int position) {
+ return results.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ String artist, title;
+ SearchResult result = results.get(position);
+
+ View itemLayout = LayoutInflater.from(context).inflate(R.layout.search_item, parent, false);
+
+ TextView tvName = (TextView) itemLayout.findViewById(R.id.item_name);
+
+ artist = result.getArtist();
+ title = result.getTitle();
+
+ if(title != null){
+ title += " by ";
+ }
+ else {
+ title = "";
+ }
+
+ tvName.setText(title + artist);
+
+ //ImageView ivImage = (ImageView) itemLayout.findViewById(R.id.stations_icon);
+ //ImageDownloader imageDownloader = new ImageDownloader();
+ //imageDownloader.download(station.getAlbumCoverUrl(), ivImage);
+
+ return itemLayout;
+ }
+
+ }
+
+}
diff --git a/src/com/aregner/android/pandoid/PandoidSettings.java b/src/com/aregner/android/pandoid/PandoidSettings.java
index ff807bf..8d96357 100644
--- a/src/com/aregner/android/pandoid/PandoidSettings.java
+++ b/src/com/aregner/android/pandoid/PandoidSettings.java
@@ -17,6 +17,7 @@
*/
package com.aregner.android.pandoid;
+import android.media.AudioManager;
import android.os.Bundle;
import android.preference.PreferenceActivity;
@@ -25,6 +26,7 @@ public class PandoidSettings extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
diff --git a/src/com/aregner/android/pandoid/PandoidStationSelect.java b/src/com/aregner/android/pandoid/PandoidStationSelect.java
index 62e7560..b9e4e17 100644
--- a/src/com/aregner/android/pandoid/PandoidStationSelect.java
+++ b/src/com/aregner/android/pandoid/PandoidStationSelect.java
@@ -25,43 +25,60 @@
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
+import android.media.AudioManager;
import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.BaseAdapter;
-import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
public class PandoidStationSelect extends ListActivity {
private PandoraRadioService pandora;
+ ArrayList stations;
+ StationListAdapter adapter;
+ private static final int GET_STATIONS_FAILED = 3;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
pandora = PandoraRadioService.getInstance(true);
- ArrayList stations = pandora.getStations();
-
- ListView lv = getListView();
- setListAdapter(new StationListAdapter(stations, this));
- lv.setTextFilterEnabled(true);
- lv.setOnItemClickListener(new OnItemClickListener() {
- public void onItemClick(AdapterView> parent, View view, int position, long id) {
- //Station station = PandoraRadioService.getInstance().getStations().get(position);
- setResult(RESULT_OK, (new Intent()).putExtra("stationId", id));
- finish();
- //finishActivity(PandoidPlayer.REQUIRE_SELECT_STATION);
- }
- });
+
+ try{
+ stations = pandora.getStations();
+ ListView lv = getListView();
+ adapter = new StationListAdapter(stations, this);
+ setListAdapter(adapter);
+ lv.setTextFilterEnabled(true);
+ lv.setOnItemClickListener(new OnItemClickListener() {
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ //Station station = PandoraRadioService.getInstance().getStations().get(position);
+ setResult(RESULT_OK, (new Intent()).putExtra("stationId", id));
+ finish();
+ //finishActivity(PandoidPlayer.REQUIRE_SELECT_STATION);
+ }
+ });
+ registerForContextMenu(lv);
+ }
+ catch(NullPointerException e){
+ setResult(GET_STATIONS_FAILED);
+ finish();
+ }
}
+
@Override
protected void onResume() {
super.onResume();
@@ -71,9 +88,38 @@ protected void onResume() {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.player_menu, menu);
+ inflater.inflate(R.menu.station_select_menu, menu);
return true;
}
+
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case R.id.create_station:
+ startActivity(new Intent(PandoidStationSelect.this, PandoidSearchActivity.class).addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT));
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ menu.setHeaderTitle("Delete Station");
+ menu.add("Delete");
+ }
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
+ pandora.deleteStation(stations.get(info.position));
+ stations.remove(info.position);
+ adapter.notifyDataSetChanged();
+ return true;
+
+ }
+
private class StationListAdapter extends BaseAdapter {
diff --git a/src/com/aregner/android/pandoid/PandoraDB.java b/src/com/aregner/android/pandoid/PandoraDB.java
index 3682aa6..ce58a1c 100644
--- a/src/com/aregner/android/pandoid/PandoraDB.java
+++ b/src/com/aregner/android/pandoid/PandoraDB.java
@@ -21,6 +21,7 @@
import java.util.HashMap;
import java.util.Iterator;
+import com.aregner.pandora.Song;
import com.aregner.pandora.Station;
import android.content.ContentValues;
@@ -28,11 +29,13 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
public class PandoraDB extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1;
+ public static final String RECENT_TABLE_NAME = "recentlyPlayed";
public static final String STATION_TABLE_NAME = "stations";
public static final String STATION_TABLE_CREATE =
"CREATE TABLE " + STATION_TABLE_NAME + " (" +
@@ -41,6 +44,14 @@ public class PandoraDB extends SQLiteOpenHelper {
"isCreator INTEGER, " +
"isQuickMix INTEGER, " +
"stationName TEXT);";
+ public static final String RECENT_TABLE_CREATE =
+ "CREATE TABLE " + RECENT_TABLE_NAME + " (" +
+ "musicId TEXT PRIMARY KEY, " +
+ "title TEXT, " +
+ "artist TEXT, " +
+ "album TEXT, " +
+ "albumUrl TEXT, " +
+ "audioUrl TEXT);";
public PandoraDB(Context context) {
super(context, "pandoradb", null, DATABASE_VERSION);
@@ -49,15 +60,57 @@ public PandoraDB(Context context) {
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(STATION_TABLE_CREATE);
+ db.execSQL(RECENT_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-
+ db.execSQL("DROP TABLE IF EXISTS " + RECENT_TABLE_CREATE);
+ db.execSQL("DROP TABLE IF EXISTS " + STATION_TABLE_CREATE);
}
/** */
- public void syncStations(ArrayList stations) {
+ public synchronized void syncRecentSongs(ArrayList recentlyPlayed) {
+ Log.i("PandoraDB", "syncstations called");
+ SQLiteDatabase write = getWritableDatabase();
+ Iterator songIter = recentlyPlayed.iterator();
+
+ while(songIter.hasNext()) {
+ Song song = songIter.next();
+
+ ContentValues values = new ContentValues(6);
+ values.put("musicId", song.getId());
+ values.put("title", song.getTitle());
+ values.put("artist", song.getArtist());
+ values.put("album", song.getAlbum());
+ values.put("albumUrl", song.getAlbumCoverUrl());
+ values.put("audioUrl", song.getOrigAudioUrl());
+
+ write.insertWithOnConflict(PandoraDB.RECENT_TABLE_NAME, null, values , SQLiteDatabase.CONFLICT_IGNORE);
+ }
+ } /**
+ public HashMap[] getRecentSongs(){
+ Cursor records = getReadableDatabase().query(RECENT_TABLE_NAME, null, null, null, null, null, null);
+ HashMap[] songs = new HashMap[records.getCount()];
+
+ for(int s=0; s();
+
+ songs[s].put("musicId", records.getString(0));
+ songs[s].put("songTitle", records.getString(1));
+ songs[s].put("artistSummary", records.getString(2));
+ songs[s].put("albumTitle", records.getString(3));
+ songs[s].put("artRadio", records.getString(4));
+ songs[s].put("audioURL", records.getString(5));
+
+ }
+
+ return songs;
+ } */
+ public synchronized void syncStations(ArrayList stations) {
+ Log.i("PandoraDB", "syncstations called");
SQLiteDatabase write = getWritableDatabase();
Iterator stationIter = stations.iterator();
diff --git a/src/com/aregner/android/pandoid/PandoraRadioService.java b/src/com/aregner/android/pandoid/PandoraRadioService.java
index c162351..5db49ba 100644
--- a/src/com/aregner/android/pandoid/PandoraRadioService.java
+++ b/src/com/aregner/android/pandoid/PandoraRadioService.java
@@ -21,55 +21,57 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import com.aregner.pandora.PandoraRadio;
+import com.aregner.pandora.SearchResult;
import com.aregner.pandora.Song;
import com.aregner.pandora.Station;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.app.ProgressDialog;
import android.app.Service;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.database.sqlite.SQLiteDatabase;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnPreparedListener;
import android.os.AsyncTask;
import android.os.IBinder;
import android.preference.PreferenceManager;
-import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
public class PandoraRadioService extends Service {
+ public static final String SONG_CHANGE = "com.aregner.android.pandoid.PanoraRadioService.SONG_CHANGE";
+
+ private static final String LOG_TAG = "PandoraRadioService";
private static final int NOTIFICATION_SONG_PLAYING = 1;
- // tools this service uses
- private PandoraRadio pandora;
- private MediaPlayer media;
+ private static PandoraRadioService instance;
+ private static Object lock = new Object();
+ // tools this service uses
private NotificationManager notificationManager;
private TelephonyManager telephonyManager;
+ private AudioFocusListener focusListener;
+ private AudioManager audioManager;
private SharedPreferences prefs;
+ private PandoraRadio pandora;
+ private MediaPlayer media;
+ private PandoraDB db;
// tracking/organizing what we are doing
private Station currentStation;
private Song[] currentPlaylist;
private Song[] nextPlaylist;
private int currentSongIndex;
- private HashMap,Object> listeners = new HashMap,Object>();
-
- protected PandoraDB db;
-
- // static usefullness
- private static PandoraRadioService instance;
- private static Object lock = new Object();
+ private List searchResults;
+ private ArrayList recentlyPlayed;
public static void createPandoraRadioService(Context context) {
synchronized(lock) {
@@ -108,10 +110,21 @@ public void onCreate() {
pandora = new PandoraRadio();
media = new MediaPlayer();
+ recentlyPlayed = new ArrayList();
+ focusListener = new AudioFocusListener();
+
+ audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
-
+
+ media.setOnCompletionListener(new OnCompletionListener(){
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ next();
+ }
+ });
+/**
// Register the listener with the telephony manager
telephonyManager.listen(new PhoneStateListener() {
boolean pausedForRing = false;
@@ -140,7 +153,8 @@ public void onCallStateChanged(int state, String incomingNumber) {
}
}
}, PhoneStateListener.LISTEN_CALL_STATE);
- }
+ */
+ }
}
@Override
@@ -149,13 +163,9 @@ public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
- public void setListener(Class> klass, Object listener) {
- listeners.put(klass, listener);
- }
-
- public void setNotification() {
+ private void setNotification() {
Notification notification = new Notification(R.drawable.icon, "Pandoroid Radio", System.currentTimeMillis());
- Intent notificationIntent = new Intent(this, PandoidPlayer.class);
+ Intent notificationIntent = new Intent(this, PandoidPlayer.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent contentIntent = PendingIntent.getActivity(this, NOTIFICATION_SONG_PLAYING, notificationIntent, 0);
notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE;
@@ -164,15 +174,6 @@ public void setNotification() {
startForeground(NOTIFICATION_SONG_PLAYING, notification);
}
- /** */
- private class PrepareNextPlaylistTask extends AsyncTask {
- @Override
- protected Void doInBackground(Void... arg0) {
- nextPlaylist = currentStation.getPlaylist( prefs.getString("pandora_audioFormat", PandoraRadio.DEFAULT_AUDIO_FORMAT) );
- return null;
- }
- }
-
/** methods for clients */
public void signIn(String username, String password) {
pandora.connect(username, password);
@@ -212,24 +213,27 @@ public ArrayList getStations(boolean forceDownload) {
return stations;
}
}
- @SuppressWarnings("unchecked")
public ArrayList getStations() {
ArrayList stations;
stations = pandora.getStations();
- (new AsyncTask, Void, Void>() {
- @Override
- protected Void doInBackground(ArrayList... params) {
- db = new PandoraDB(getBaseContext());
- db.syncStations(params[0]);
- db.close();
- return null;
- }
- }).execute(stations);
-
+ db = new PandoraDB(getBaseContext());
+ db.syncStations(stations);
+ db.close();
+
return stations;
}
+ public Station createStation(String musicId) {
+ return pandora.createStation(musicId, pandora.TYPE_MUSIC_ID);
+ }
+ public void deleteStation(Station station) {
+ String stationId = station.getStationId();
+ pandora.deleteStation(stationId);
+ }
+ public ArrayList getRecentlyPlayed(){
+ return recentlyPlayed;
+ }
public void setCurrentStationId(long sid) {
if(sid < 0) return;
currentStation = pandora.getStationById(sid);
@@ -240,22 +244,32 @@ public Station getCurrentStation() {
public Song getCurrentSong() {
return currentPlaylist[currentSongIndex];
}
+ public ArrayList getRecentSongs(){
+ return recentlyPlayed;
+ }
public boolean isPlayable() {
return currentStation != null && pandora.isAlive();
}
public boolean isPlaying() {
return media.isPlaying();
}
+ public boolean isReadytoUpdateUI() {
+ boolean ready = false;
+
+ if (instance != null && currentPlaylist != null) {
+ ready = true;
+ }
+ return ready;
+ }
public void prepare() {
currentPlaylist = currentStation.getPlaylist( prefs.getString("pandora_audioFormat", PandoraRadio.DEFAULT_AUDIO_FORMAT) );
prepare(0);
}
public void prepare(int i) {
currentSongIndex = i;
+ songChangeEvent();
media.reset();
- media.setOnCompletionListener((OnCompletionListener)listeners.get(OnCompletionListener.class));
- media.setOnPreparedListener((OnPreparedListener)listeners.get(OnPreparedListener.class));
try {
media.setDataSource( currentPlaylist[i].getAudioUrl() );
} catch (IllegalArgumentException e1) {
@@ -267,7 +281,7 @@ public void prepare(int i) {
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
- }
+ }
try {
media.prepare();
} catch (IllegalStateException e) {
@@ -278,21 +292,53 @@ public void prepare(int i) {
e.printStackTrace();
}
}
+ public void play(Song song) {
+ if(currentPlaylist == null){
+ currentPlaylist = new Song[1];
+ currentSongIndex = 0;
+ }
+ currentPlaylist[currentSongIndex] = song;
+
+ prepare(currentSongIndex);
+ play(currentSongIndex);
+ }
public Song play() {
return play(0);
}
public Song play(int i) {
+ requestAudioFocus();
media.start();
+ PandoidPlayer.togglePlayButton();
setNotification();
+ addRecentlyPlayed(currentPlaylist[i]);
+
return currentPlaylist[i];
+
+ }
+ private void requestAudioFocus(){
+ audioManager.requestAudioFocus(focusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
+ }
+ public void releaseAudioFocus(){
+ if(audioManager != null && focusListener != null){
+ audioManager.abandonAudioFocus(focusListener);
+ }
+ }
+ private void addRecentlyPlayed(Song song) {
+ if(!recentlyPlayed.contains(song)){
+ recentlyPlayed.add(0, song);
+ }
}
public void pause() {
if(media.isPlaying()) {
+ releaseAudioFocus();
media.pause();
+ PandoidPlayer.togglePlayButton();
stopForeground(true);
}
else {
+ requestAudioFocus();
media.start();
+ PandoidPlayer.togglePlayButton();
setNotification();
}
}
@@ -331,5 +377,57 @@ public void rate(String rating) {
boolean ratingBool = rating.equals(PandoidPlayer.RATING_LOVE) ? true : false;
pandora.rate(currentStation, currentPlaylist[currentSongIndex], ratingBool);
+
+ if(ratingBool == false){
+ pandora.tired(currentStation, currentPlaylist[currentSongIndex]);
+ }
+ }
+
+ public void search(String query, String type){
+ int queryType;
+ if(type.equals("Artist")){
+ queryType = pandora.ARTIST_QUERY;
+ }
+ else{
+ queryType = pandora.TRACK_QUERY;
+ }
+ searchResults = pandora.search(query, queryType);
+ }
+ public List getSearchResults(){
+ return searchResults;
+ }
+
+ private void songChangeEvent() {
+ Intent i = new Intent();
+ i.setAction(SONG_CHANGE);
+ sendBroadcast(i);
+ }
+ private class AudioFocusListener implements OnAudioFocusChangeListener {
+
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ if(focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ media.start();
+ setNotification();
+ }
+ else if(focusChange == AudioManager.AUDIOFOCUS_LOSS){
+ if(media != null && media.isPlaying()){
+ media.pause();
+ stopForeground(true);
+ }
+ }
+ else if(focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT){
+ media.pause();
+ }
+ else if(focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK){
+ }
+ }
+ }
+ private class PrepareNextPlaylistTask extends AsyncTask {
+ @Override
+ protected Void doInBackground(Void... arg0) {
+ nextPlaylist = currentStation.getPlaylist( prefs.getString("pandora_audioFormat", PandoraRadio.DEFAULT_AUDIO_FORMAT) );
+ return null;
+ }
}
}
diff --git a/src/com/aregner/pandora/._XmlRpc.java8628963733050321481.tmp b/src/com/aregner/pandora/._XmlRpc.java8628963733050321481.tmp
new file mode 100644
index 0000000..05e2a0f
--- /dev/null
+++ b/src/com/aregner/pandora/._XmlRpc.java8628963733050321481.tmp
@@ -0,0 +1,184 @@
+/* Pandoroid Radio - open source pandora.com client for android
+ * Copyright (C) 2011 Andrew Regner
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package com.aregner.pandora;
+
+import java.io.BufferedInputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URI;
+import java.util.AbstractCollection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.entity.StringEntity;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlrpc.android.Tag;
+import org.xmlrpc.android.XMLRPCException;
+import org.xmlrpc.android.XMLRPCFault;
+
+public class XmlRpc extends org.xmlrpc.android.XMLRPCClient {
+
+ public XmlRpc(String url) {
+ super(url);
+ }
+
+ /* This method is extracted from the parent class with slight modifications
+ * for sending a request with a predetermined body content. */
+ @SuppressWarnings("unchecked")
+ public Object callWithBody(String url, String body) throws XMLRPCException {
+
+ postMethod.setURI(URI.create(url));
+
+ try {
+ // set POST body
+ HttpEntity entity = new StringEntity(body);
+ postMethod.setEntity(entity);
+
+ //Log.d(Tag.LOG, "ros HTTP POST");
+ // execute HTTP POST request
+ HttpResponse response = client.execute(postMethod);
+ //Log.d(Tag.LOG, "ros HTTP POSTed");
+
+ // check status code
+ int statusCode = response.getStatusLine().getStatusCode();
+ //Log.d(Tag.LOG, "ros status code:" + statusCode);
+ if (statusCode != HttpStatus.SC_OK) {
+ throw new XMLRPCException("HTTP status code: " + statusCode + " != " + HttpStatus.SC_OK);
+ }
+
+ // parse response stuff
+ //
+ // setup pull parser
+ XmlPullParser pullParser = XmlPullParserFactory.newInstance().newPullParser();
+ entity = response.getEntity();
+ Reader reader = new InputStreamReader(new BufferedInputStream(entity.getContent()));
+ // for testing purposes only
+ // reader = new StringReader("\n\n\n");
+ pullParser.setInput(reader);
+
+ // lets start pulling...
+ pullParser.nextTag();
+ pullParser.require(XmlPullParser.START_TAG, null, Tag.METHOD_RESPONSE);
+
+ pullParser.nextTag(); // either Tag.PARAMS () or Tag.FAULT ()
+ String tag = pullParser.getName();
+ if (tag.equals(Tag.PARAMS)) {
+ // normal response
+ pullParser.nextTag(); // Tag.PARAM ()
+ pullParser.require(XmlPullParser.START_TAG, null, Tag.PARAM);
+ pullParser.nextTag(); // Tag.VALUE ()
+ // no parser.require() here since its called in XMLRPCSerializer.deserialize() below
+
+ // deserialize result
+ Object obj = iXMLRPCSerializer.deserialize(pullParser);
+ entity.consumeContent();
+ return obj;
+ }
+ else if (tag.equals(Tag.FAULT)) {
+ // fault response
+ pullParser.nextTag(); // Tag.VALUE ()
+ // no parser.require() here since its called in XMLRPCSerializer.deserialize() below
+
+ // deserialize fault result
+ Map map = (Map) iXMLRPCSerializer.deserialize(pullParser);
+ String faultString = (String) map.get(Tag.FAULT_STRING);
+ int faultCode = (Integer) map.get(Tag.FAULT_CODE);
+ entity.consumeContent();
+ throw new XMLRPCFault(faultString, faultCode);
+ } else {
+ entity.consumeContent();
+ throw new XMLRPCException("Bad tag <" + tag + "> in XMLRPC response - neither nor ");
+ }
+ } catch (XMLRPCException e) {
+ e.printStackTrace();
+ // catch & propagate XMLRPCException/XMLRPCFault
+ throw e;
+ } catch (Exception e) {
+ e.printStackTrace();
+ // wrap any other Exception(s) around XMLRPCException
+ throw new XMLRPCException(e);
+ }
+ }
+
+ public void addHeader(String header, String value) {
+ postMethod.addHeader(header, value);
+ }
+
+ public static String value(String v) {
+ return "" + v.replace("&", "&").replace("<", "<").replace(">", ">") + "";
+ }
+ public static String value(boolean v) {
+ return v ? "1" : "0";
+ }
+ public static String value(int v) {
+ return "" + String.valueOf(v) + "";
+ }
+ public static String value(Number v) {
+ return value(v.intValue());
+ }
+ public static String value(String[] list) {
+ StringBuilder result = new StringBuilder("");
+ for(int i=0; i").toString();
+ }
+ public static String value(int[] list) {
+ StringBuilder result = new StringBuilder("");
+ for(int i=0; i").toString();
+ }
+ public static String value(AbstractCollection> list) {
+ StringBuilder result = new StringBuilder("");
+ Iterator> listIter = list.iterator();
+ while(listIter.hasNext()) {
+ result.append(valueGuess(listIter.next()));
+ }
+ return result.append("").toString();
+ }
+
+ public static String valueGuess(Object v) {
+ if(v instanceof Number)
+ return value((Number)v);
+ else if(v instanceof Boolean)
+ return value((Boolean)v);
+ else if(v instanceof String)
+ return value((String)v);
+ else if(v instanceof AbstractCollection>)
+ return value((AbstractCollection>)v);
+ else
+ return value(v.toString());
+ }
+
+ public static String makeCall(String method, Vector