Java tutorial
/* Pandoroid Radio - open source pandora.com client for android * Copyright (C) 2011 Andrew Regner <andrew@aregner.com> * * 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.pandoroid; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import org.apache.http.HttpStatus; import org.apache.http.client.HttpResponseException; import com.pandoroid.pandora.PandoraRadio; import com.pandoroid.pandora.RPCException; import com.pandoroid.pandora.Song; import com.pandoroid.pandora.Station; import com.pandoroid.pandora.SubscriberTypeException; import com.pandoroid.playback.MediaPlaybackController; import com.pandoroid.playback.OnNewSongListener; import com.pandoroid.playback.OnPlaybackContinuedListener; import com.pandoroid.playback.OnPlaybackHaltedListener; import com.pandoroid.R; import android.app.AlertDialog; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnPreparedListener; import android.net.ConnectivityManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; import android.os.IBinder; import android.preference.PreferenceManager; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; /** * Description: Someone really needs to give this class some loving, document * it up, organize it, and make it thread safe. */ public class PandoraRadioService extends Service { private static final int NOTIFICATION_SONG_PLAYING = 1; // tools this service uses private PandoraRadio m_pandora_remote; public MediaPlaybackController m_song_playback; public ImageDownloader image_downloader; private TelephonyManager telephonyManager; private ConnectivityManager connectivity_manager; private MusicIntentReceiver m_music_intent_receiver; private SharedPreferences m_prefs; // tracking/organizing what we are doing private Station m_current_station; private String m_audio_quality; private boolean m_paused; //We'll use this for now as the database implementation is garbage. private ArrayList<Station> m_stations; private HashMap<Class<?>, Object> listeners = new HashMap<Class<?>, Object>(); protected PandoraDB db; // static usefullness private static Object lock = new Object(); private static Object pandora_lock = new Object(); //Taken straight from the Android service reference /** * Class for clients to access. Because we know this service always * runs in the same process as its clients, we don't need to deal with * IPC. */ public class PandoraRadioBinder extends Binder { PandoraRadioService getService() { return PandoraRadioService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } // This is the object that receives interactions from clients. private final IBinder mBinder = new PandoraRadioBinder(); //End service reference @Override public void onCreate() { m_paused = false; m_pandora_remote = new PandoraRadio(); image_downloader = new ImageDownloader(); m_stations = new ArrayList<Station>(); connectivity_manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); m_prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); // Register the listener with the telephony manager telephonyManager.listen(new PhoneStateListener() { boolean pausedForRing = false; @Override public void onCallStateChanged(int state, String incomingNumber) { switch (state) { case TelephonyManager.CALL_STATE_IDLE: if (pausedForRing && m_song_playback != null) { if (m_prefs.getBoolean("behave_resumeOnHangup", true)) { if (m_song_playback != null && !m_paused) { m_song_playback.play(); } } } pausedForRing = false; break; case TelephonyManager.CALL_STATE_OFFHOOK: case TelephonyManager.CALL_STATE_RINGING: if (m_song_playback != null) { m_song_playback.pause(); } pausedForRing = true; break; } } }, PhoneStateListener.LISTEN_CALL_STATE); m_music_intent_receiver = new MusicIntentReceiver(); this.registerReceiver(m_music_intent_receiver, new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)); } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } public void onDestroy() { if (m_song_playback != null) { m_song_playback.stop(); } this.unregisterReceiver(m_music_intent_receiver); stopForeground(true); return; } public Song getCurrentSong() throws Exception { return m_song_playback.getSong(); } public Station getCurrentStation() { return m_current_station; } public ArrayList<Station> getStations() { return m_stations; } public boolean isPartnerAuthorized() { return m_pandora_remote.isPartnerAuthorized(); } public boolean isUserAuthorized() { return m_pandora_remote.isUserAuthorized(); } public void runPartnerLogin(boolean pandora_one_subscriber_flag) throws RPCException, IOException, HttpResponseException, Exception { Log.i("Pandoroid", "Running a partner login for a " + (pandora_one_subscriber_flag ? "Pandora One" : "standard Pandora") + " subscriber."); m_pandora_remote.runPartnerLogin(pandora_one_subscriber_flag); } public void runUserLogin(String user, String password) throws HttpResponseException, RPCException, IOException, Exception { boolean needs_partner_login = false; boolean is_pandora_one_user = m_pandora_remote.isPandoraOneCredentials(); boolean failure_but_not_epic_failure = true; while (failure_but_not_epic_failure) { try { if (needs_partner_login) { m_prefs.edit().putBoolean("pandora_one_flag", is_pandora_one_user).apply(); runPartnerLogin(is_pandora_one_user); needs_partner_login = false; } if (is_pandora_one_user) { m_audio_quality = PandoraRadio.MP3_192; } else { m_audio_quality = PandoraRadio.MP3_128; } Log.i("Pandoroid", "Running a user login."); m_pandora_remote.connect(user, password); failure_but_not_epic_failure = false; //Or any type of fail for that matter. } catch (SubscriberTypeException e) { needs_partner_login = true; is_pandora_one_user = e.is_pandora_one; Log.i("Pandoroid", "Wrong subscriber type. User is a " + (is_pandora_one_user ? "Pandora One" : "standard Pandora") + " subscriber."); } catch (RPCException e) { if (e.code == RPCException.INVALID_AUTH_TOKEN) { needs_partner_login = true; Log.e("Pandoroid", e.getMessage()); } else { throw e; } } } } public void setListener(Class<?> klass, Object listener) { listeners.put(klass, listener); } public void setNotification() { if (!m_paused) { try { Song tmp_song; tmp_song = m_song_playback.getSong(); Notification notification = new Notification(R.drawable.icon, "Pandoroid Radio", System.currentTimeMillis()); Intent notificationIntent = new Intent(this, PandoroidPlayer.class); PendingIntent contentIntent = PendingIntent.getActivity(this, NOTIFICATION_SONG_PLAYING, notificationIntent, 0); notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE; notification.setLatestEventInfo(getApplicationContext(), tmp_song.getTitle(), tmp_song.getArtist() + " on " + tmp_song.getAlbum(), contentIntent); startForeground(NOTIFICATION_SONG_PLAYING, notification); } catch (Exception e) { } } } public void signOut() { if (m_song_playback != null) { stopForeground(true); m_song_playback.stop(); } if (m_pandora_remote != null) { m_pandora_remote.disconnect(); } if (m_current_station != null) { m_current_station = null; } } public boolean isAlive() { return m_pandora_remote.isAlive(); } public void updateStations() throws HttpResponseException, RPCException, IOException, Exception { m_stations = m_pandora_remote.getStations(); } public boolean setCurrentStation(String station_id) { for (int i = 0; i < m_stations.size(); ++i) { Station tmp_station = m_stations.get(i); if (tmp_station.compareTo(station_id) == 0) { m_current_station = tmp_station; stopForeground(true); setPlaybackController(); m_prefs.edit().putString("lastStationId", station_id).apply(); return true; } } return false; } public void playPause() { if (m_song_playback != null) { if (!m_paused) { pause(); } else { play(); } } } private void play() { m_paused = false; m_song_playback.play(); setNotification(); } private void pause() { m_song_playback.pause(); m_paused = true; stopForeground(true); } public void rate(String rating) { if (rating == PandoroidPlayer.RATING_NONE) { // cannot set rating to none return; } (new RateTask()).execute(rating); } public void resetPlaybackListeners() { if (m_song_playback != null) { try { m_song_playback.setOnNewSongListener((OnNewSongListener) listeners.get(OnNewSongListener.class)); m_song_playback.setOnPlaybackContinuedListener( (OnPlaybackContinuedListener) listeners.get(OnPlaybackContinuedListener.class)); m_song_playback.setOnPlaybackHaltedListener( (OnPlaybackHaltedListener) listeners.get(OnPlaybackHaltedListener.class)); m_song_playback.setOnErrorListener(new PlaybackOnErrorListener()); } catch (Exception e) { Log.e("Pandoroid", e.getMessage(), e); } } } private void setPlaybackController() { try { if (m_song_playback == null) { m_song_playback = new MediaPlaybackController(m_current_station.getStationIdToken(), PandoraRadio.AAC_32, m_audio_quality, m_pandora_remote, connectivity_manager); } else { m_song_playback.reset(m_current_station.getStationIdToken(), m_pandora_remote); } resetPlaybackListeners(); } catch (Exception e) { Log.e("Pandoroid", e.getMessage(), e); m_song_playback = null; } } public void skip() { m_song_playback.skip(); } public void startPlayback() { if (m_song_playback != null) { Thread t = new Thread(m_song_playback); t.start(); } } public void stopPlayback() { if (m_song_playback != null) { m_song_playback.stop(); } stopForeground(true); } public abstract static class OnInvalidAuthListener { public abstract void onInvalidAuth(); } public class PlaybackOnErrorListener extends com.pandoroid.playback.OnErrorListener { public void onError(String error_message, Throwable e, boolean remote_error_flag, int rpc_error_code) { if (remote_error_flag) { if (rpc_error_code == RPCException.INVALID_AUTH_TOKEN) { m_pandora_remote.disconnect(); OnInvalidAuthListener listener = (OnInvalidAuthListener) listeners .get(OnInvalidAuthListener.class); if (listener != null) { listener.onInvalidAuth(); } } } } } public class MusicIntentReceiver extends android.content.BroadcastReceiver { public void onReceive(Context ctx, Intent intent) { if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) { if (m_song_playback != null) { pause(); } } } } public class RateTask extends AsyncTask<String, Void, Void> { public void onPreExecute() { try { this.m_song = m_song_playback.getSong(); } catch (Exception e) { Log.e("Pandoroid", "No song to rate."); } } public Void doInBackground(String... ratings) { if (m_song != null) { String rating = ratings[0]; boolean rating_bool = rating.equals(PandoroidPlayer.RATING_LOVE) ? true : false; try { m_pandora_remote.rate(this.m_song, rating_bool); Log.i("Pandoroid", "A " + (rating_bool ? "thumbs up" : "thumbs down") + " rating for the song " + this.m_song.getTitle() + " was successfully sent."); //We'll have to do more later, but this works for now. // } catch (HttpResponseException e) { // } catch (RPCException e) { // } catch (IOException e) { } catch (Exception e) { Log.e("Pandoroid", "Exception while sending a song rating.", e); } } return null; } private Song m_song; } /** * Description: An abstract asynchronous task for doing a generic login. * @param <Params> -Parameters specific for the doInBackground() execution. */ public abstract static class ServerAsyncTask<Params> extends AsyncTask<Params, Void, Integer> { protected static final int ERROR_UNSUPPORTED_API = 0; protected static final int ERROR_NETWORK = 1; protected static final int ERROR_UNKNOWN = 2; protected static final int ERROR_REMOTE_SERVER = 3; /** * Description: The required AsyncTask.doInBackground() function. */ protected abstract Integer doInBackground(Params... args); protected abstract void quit(); protected abstract void reportAction(); /** * Description: A function that specifies the action to be taken * when a user clicks the retry button in an alert dialog. */ protected abstract void retryAction(); protected abstract void showAlert(AlertDialog new_alert); /** * Description: Builds an alert dialog necessary for all login tasks. * @param error -The error code. * @return An alert dialog builder that can be converted into an alert * dialog. */ protected AlertDialog.Builder buildErrorDialog(int error, final Context context) { AlertDialog.Builder alert_builder = new AlertDialog.Builder(context); alert_builder.setCancelable(false); alert_builder.setPositiveButton("Quit", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { quit(); } }); switch (error) { case ERROR_NETWORK: case ERROR_UNKNOWN: case ERROR_REMOTE_SERVER: alert_builder.setNeutralButton("Retry", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { retryAction(); } }); } switch (error) { case ERROR_UNSUPPORTED_API: alert_builder.setNeutralButton("Report", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { reportAction(); } }); alert_builder.setMessage("Please update the app. " + "The current Pandora API is unsupported."); break; case ERROR_NETWORK: alert_builder.setMessage("A network error has occurred."); break; case ERROR_UNKNOWN: alert_builder.setMessage("An unknown error has occurred."); break; case ERROR_REMOTE_SERVER: alert_builder.setMessage("Pandora's servers are having troubles. " + "Try again later."); break; } return alert_builder; } /** * Description: A test to show off different exceptions. * @throws RPCException * @throws HttpResponseException * @throws IOException * @throws Exception */ public void exceptionTest() throws RPCException, HttpResponseException, IOException, Exception { switch (1) { case 0: throw new RPCException(RPCException.API_VERSION_NOT_SUPPORTED, "Invalid API test"); case 1: throw new HttpResponseException(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal server error test"); case 2: throw new IOException("IO exception test"); case 3: throw new Exception("Generic exception test"); } } /** * Description: A handler that must be called when an RPCException * has occurred. * @param e * @return */ protected int rpcExceptionHandler(RPCException e) { int success_flag = ERROR_UNKNOWN; if (RPCException.URL_PARAM_MISSING_METHOD <= e.code && e.code <= RPCException.API_VERSION_NOT_SUPPORTED) { success_flag = ERROR_UNSUPPORTED_API; } else if (e.code == RPCException.INTERNAL || e.code == RPCException.MAINTENANCE_MODE) { success_flag = ERROR_REMOTE_SERVER; } else { success_flag = ERROR_UNKNOWN; } return success_flag; } /** * Description: A handler that must be called when an HttpResponseException * has occurred. * @param e * @return */ protected int httpResponseExceptionHandler(HttpResponseException e) { int success_flag = ERROR_UNKNOWN; if (e.getStatusCode() == HttpStatus.SC_INTERNAL_SERVER_ERROR) { success_flag = ERROR_REMOTE_SERVER; } else { success_flag = ERROR_NETWORK; } return success_flag; } /** * Description: A handler that must be called when an IOException * has been encountered. * @param e * @return */ protected int ioExceptionHandler(IOException e) { return ERROR_NETWORK; } /** * Description: A handler that must be called when a generic Exception has * been encountered. * @param e * @return */ protected int generalExceptionHandler(Exception e) { return ERROR_UNKNOWN; } } }