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 org.apache.http.HttpStatus; import org.apache.http.client.HttpResponseException; import com.actionbarsherlock.app.SherlockActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.SubMenu; import com.pandoroid.pandora.RPCException; import com.pandoroid.pandora.Song; import com.pandoroid.playback.MediaPlaybackController; import com.pandoroid.playback.OnNewSongListener; import com.pandoroid.playback.OnPlaybackContinuedListener; import com.pandoroid.playback.OnPlaybackHaltedListener; import com.pandoroid.PandoraRadioService.ServerAsyncTask; import com.pandoroid.R; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; public class PandoroidPlayer extends SherlockActivity { // public static final int REQUIRE_SELECT_STATION = 0x10; // public static final int REQUIRE_LOGIN_CREDS = 0x20; public static final String RATING_BAN = "ban"; public static final String RATING_LOVE = "love"; public static final String RATING_NONE = null; // private static final String STATE_IMAGE = "imageDownloader"; private static AlertDialog m_alert; private static boolean m_alert_active_flag = false; private static int m_song_halted_reason = MediaPlaybackController.HALT_STATE_CLEAR; private boolean m_is_bound; private SharedPreferences m_prefs; private PartnerLoginTask m_partner_login_task; private static boolean m_partner_login_finished_flag = true; private PandoraRadioService m_service; private RetrieveStationsTask m_retrieve_stations_task; private static boolean m_retrieve_stations_finished_flag = true; private UserLoginTask m_user_login_task; private static boolean m_user_login_finished_flag = true; private static ProgressDialog m_waiting; /* * Activity start and end specific stuff */ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(R.style.Theme_Sherlock); setContentView(R.layout.player); m_prefs = PreferenceManager.getDefaultSharedPreferences(this); doBindService(); } @Override protected void onStart() { super.onStart(); } @Override protected void onResume() { super.onResume(); if (m_is_bound) { startup(); } else { m_waiting = ProgressDialog.show(PandoroidPlayer.this, "", getString(R.string.loading)); } } protected void onPause() { super.onPause(); } protected void onStop() { super.onStop(); if (m_alert_active_flag) { m_alert.dismiss(); } dismissWaiting(); } protected void onDestroy() { super.onDestroy(); doUnbindService(); } /* End Activity */ /* * Service connection specific stuff. */ private ServiceConnection m_connection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. Because we have bound to a explicit // service that we know is running in our own process, we can // cast its IBinder to a concrete class and directly access it. m_service = ((PandoraRadioService.PandoraRadioBinder) service).getService(); m_is_bound = true; startup(); m_service.setListener(OnNewSongListener.class, new OnNewSongListener() { public void onNewSong(Song song) { updateForNewSong(song); } }); m_service.setListener(OnPlaybackContinuedListener.class, new OnPlaybackContinuedListener() { public void onPlaybackContinued() { dismissSongHaltedProgress(); } }); m_service.setListener(OnPlaybackHaltedListener.class, new OnPlaybackHaltedListener() { public void onPlaybackHalted(int reason) { showSongHaltedProgress(reason); } }); m_service.setListener(PandoraRadioService.OnInvalidAuthListener.class, new PandoraRadioService.OnInvalidAuthListener() { @Override public void onInvalidAuth() { partnerLogin(); } }); m_service.resetPlaybackListeners(); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. // Because it is running in our same process, we should never // see this happen. m_service = null; m_is_bound = false; } }; void doBindService() { // This is the master service start startService(new Intent(this, PandoraRadioService.class)); // Establish a connection with the service. We use an explicit // class name because we want a specific service implementation that // we know will be running in our own process (and thus won't be // supporting component replacement by other applications). bindService(new Intent(this, PandoraRadioService.class), m_connection, Context.BIND_AUTO_CREATE); } void doUnbindService() { if (m_is_bound) { // Detach our existing connection. unbindService(m_connection); m_is_bound = false; } } /* End Service */ public void controlButtonPressed(View button) { switch (button.getId()) { case R.id.player_ban: m_service.rate(RATING_BAN); Toast.makeText(getApplicationContext(), getString(R.string.baned_song), Toast.LENGTH_SHORT).show(); if (m_prefs.getBoolean("behave_nextOnBan", true)) { m_service.skip(); } break; case R.id.player_love: m_service.rate(RATING_LOVE); Toast.makeText(getApplicationContext(), getString(R.string.loved_song), Toast.LENGTH_SHORT).show(); break; case R.id.player_pause: m_service.playPause(); break; case R.id.player_next: m_service.skip(); break; } } /** * Description: Removes alert dialogs. */ private void dismissAlert() { if (m_alert_active_flag) { m_alert.dismiss(); m_alert_active_flag = false; m_alert = null; } } private void dismissSongHaltedProgress() { m_song_halted_reason = MediaPlaybackController.HALT_STATE_CLEAR; View progress = findViewById(R.id.progressUpdate); progress.setVisibility(View.INVISIBLE); } /** * Description: Removes waiting prompts. */ private static void dismissWaiting() { if (m_waiting != null && m_waiting.isShowing()) { m_waiting.dismiss(); m_waiting = null; } } public boolean onCreateOptionsMenu(Menu menu) { SubMenu sub = menu.addSubMenu("Options"); sub.add(0, R.id.menu_stations, Menu.NONE, R.string.menu_stations); sub.add(0, R.id.menu_settings, Menu.NONE, R.string.menu_settings); sub.add(0, R.id.menu_logout, Menu.NONE, R.string.menu_logout); sub.add(0, R.id.menu_about, Menu.NONE, R.string.menu_about); MenuItem subMenu = sub.getItem(); subMenu.setIcon(R.drawable.ic_sysbar_menu); subMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); return super.onCreateOptionsMenu(menu); } public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_stations: spawnStationActivity(); return true; case R.id.menu_logout: m_service.signOut(); SharedPreferences.Editor prefs_edit = m_prefs.edit(); prefs_edit.remove("pandora_username"); prefs_edit.remove("pandora_password"); prefs_edit.remove("lastStationId"); prefs_edit.apply(); dismissSongHaltedProgress(); userLogin(); return true; case R.id.menu_settings: startActivity(new Intent(getApplicationContext(), PandoroidSettings.class)); return true; case R.id.menu_about: startActivity(new Intent(getApplicationContext(), AboutDialog.class)); return true; default: return super.onOptionsItemSelected(item); } } /** * Description: Executes a PartnerLoginTask. */ private void partnerLogin() { if (m_partner_login_finished_flag == true) { m_partner_login_finished_flag = false; m_partner_login_task = new PartnerLoginTask(); boolean pandora_one_flag = m_prefs.getBoolean("pandora_one_flag", true); m_partner_login_task.execute(pandora_one_flag); } else { showWaiting("Authorizing the app..."); } } /** * Description: Closes down the app as much as is possible with android. */ private void quit() { doUnbindService(); stopService(new Intent(PandoroidPlayer.this, PandoraRadioService.class)); m_alert_active_flag = false; finish(); } /** * Description: Executes the RetrieveStationsTask. */ private void setStation() { if (m_retrieve_stations_finished_flag == true) { m_retrieve_stations_finished_flag = false; m_retrieve_stations_task = new RetrieveStationsTask(); m_retrieve_stations_task.execute(); } else { showWaiting("Acquiring a station..."); } } /** * Description: Shows an alert with the specified alert. * @param new_alert -The AlertDialog to show. */ private void showAlert(AlertDialog new_alert) { dismissAlert(); m_alert = new_alert; m_alert.show(); m_alert_active_flag = true; } private void showSongHaltedProgress(int reason) { m_song_halted_reason = reason; View progress = findViewById(R.id.progressUpdate); TextView view_text = (TextView) findViewById(R.id.progressText); int reason_str; switch (reason) { case MediaPlaybackController.HALT_STATE_NO_NETWORK: reason_str = R.string.no_network; break; case MediaPlaybackController.HALT_STATE_NO_INTERNET: reason_str = R.string.no_internet; break; case MediaPlaybackController.HALT_STATE_BUFFERING: reason_str = R.string.buffering; break; case MediaPlaybackController.HALT_STATE_PREPARING: reason_str = R.string.preparing; break; case MediaPlaybackController.HALT_STATE_NO_SONGS: reason_str = R.string.no_songs; break; default: reason_str = R.string.buffering; } view_text.setText(reason_str); progress.setVisibility(View.VISIBLE); } /** * Description: Shows a waiting/ProgressDialog with the specified message. * @param message -The String to show in the ProgressDialog. */ private void showWaiting(String message) { dismissWaiting(); m_waiting = ProgressDialog.show(this, "", message); m_waiting.show(); } /** * Description: Refreshes the view with the specified song. If song is null * it will reset to the default configuration. * @param song -The song to set the view to show. */ private void songRefresh(Song song) { TextView top = (TextView) findViewById(R.id.player_topText); ImageView image = (ImageView) findViewById(R.id.player_image); if (song != null) { getSupportActionBar().setTitle(String.format("" + song.getTitle())); m_service.image_downloader.download(song.getAlbumCoverUrl(), image); top.setText(String.format("%s\n%s", song.getArtist(), song.getAlbum())); } else { image.setImageResource(R.drawable.transparent); top.setText(R.string.loading); getSupportActionBar().setTitle(R.string.app_name); } } /** * Description: Starts a login activity. */ private void spawnLoginActivity() { m_prefs.edit().remove("pandora_password").apply(); startActivity(new Intent(this, PandoroidLogin.class)); } /** * Description: Starts a new station selection activity. */ private void spawnStationActivity() { startActivity(new Intent(this, PandoroidStationSelect.class)); } /** * Description: This gets executed whenever the activity has to be * restarted/resumed. */ private void startup() { if (m_alert_active_flag) { m_alert.show(); } else if (!m_service.isPartnerAuthorized()) { partnerLogin(); } else if (!m_service.isUserAuthorized()) { userLogin(); } else if (m_service.getCurrentStation() == null) { setStation(); } else { if (m_song_halted_reason != MediaPlaybackController.HALT_STATE_CLEAR) { showSongHaltedProgress(m_song_halted_reason); } try { songRefresh(m_service.getCurrentSong()); } catch (Exception e) { songRefresh(null); } dismissWaiting(); } } /** * Description: Updates a new song. This is mainly for OnNewSongListener * purposes. * @param song -The new Song. */ private void updateForNewSong(Song song) { m_service.setNotification(); songRefresh(song); } /** * Description: Executes the UserLoginTask. */ private void userLogin() { if (m_user_login_finished_flag == true) { String username = m_prefs.getString("pandora_username", null); String password = m_prefs.getString("pandora_password", null); if (username == null || password == null) { spawnLoginActivity(); } else { m_user_login_finished_flag = false; m_user_login_task = new UserLoginTask(); m_user_login_task.execute(username, password); } } else { showWaiting(getString(R.string.signing_in)); } } /** * Description: An abstract class of ServerAsyncTask that's specific to this * activity. * @param <Params> -Parameters specific to the doInBackground() execution. */ private abstract class PandoroidPlayerServerTask<Params> extends ServerAsyncTask<Params> { protected AlertDialog.Builder buildErrorDialog(int error) { return super.buildErrorDialog(error, PandoroidPlayer.this); } protected void quit() { PandoroidPlayer.this.quit(); } protected void reportAction() { String issue_url = "https://github.com/dylanPowers/pandoroid/issues"; Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(issue_url)); startActivity(i); } protected void showAlert(AlertDialog alert) { PandoroidPlayer.this.showAlert(alert); } } /** * Description: A login async task specific to authorizing the app itself * for further communication with Pandora's servers. */ private class PartnerLoginTask extends PandoroidPlayerServerTask<Boolean> { protected void onPreExecute() { showWaiting("Authorizing the app..."); } protected Integer doInBackground(Boolean... subscriber_type) { Integer success_flag = -1; try { m_service.runPartnerLogin(subscriber_type[0].booleanValue()); // exceptionTest(); } catch (RPCException e) { Log.e("Pandoroid", "Error running partner login.", e); if (e.code == RPCException.INVALID_PARTNER_CREDENTIALS) { success_flag = ERROR_UNSUPPORTED_API; } else { success_flag = rpcExceptionHandler(e); } } catch (HttpResponseException e) { Log.e("Pandoroid", "Error running partner login.", e); success_flag = httpResponseExceptionHandler(e); } catch (IOException e) { Log.e("Pandoroid", "Error running partner login.", e); success_flag = ioExceptionHandler(e); } catch (Exception e) { Log.e("Pandoroid", "Error running partner login.", e); success_flag = generalExceptionHandler(e); } return success_flag; } protected void onPostExecute(Integer success_int) { int success = success_int.intValue(); if (success >= 0) { AlertDialog.Builder alert_builder = buildErrorDialog(success); showAlert(alert_builder.create()); } else { userLogin(); } m_partner_login_finished_flag = true; } protected void retryAction() { m_alert_active_flag = false; partnerLogin(); } } /** * Description: A retrieving stations asynchronous task. * @author Dylan Powers <dylan.kyle.powers@gmail.com> * */ private class RetrieveStationsTask extends PandoroidPlayerServerTask<Void> { protected void onPreExecute() { showWaiting("Acquiring a station..."); } protected Integer doInBackground(Void... massive_void) { Integer success_flag = -1; try { m_service.updateStations(); } catch (RPCException e) { Log.e("Pandoroid", "Error fetching stations.", e); success_flag = rpcExceptionHandler(e); } catch (HttpResponseException e) { Log.e("Pandoroid", "Error fetching stations.", e); success_flag = httpResponseExceptionHandler(e); } catch (IOException e) { Log.e("Pandoroid", "Error fetching stations.", e); success_flag = ioExceptionHandler(e); } catch (Exception e) { Log.e("Pandoroid", "Error fetching stations.", e); success_flag = generalExceptionHandler(e); } return success_flag; } protected void onPostExecute(Integer success_int) { if (success_int.intValue() < 0) { dismissWaiting(); String last_station_id = m_prefs.getString("lastStationId", null); if (last_station_id != null && m_service.setCurrentStation(last_station_id)) { m_service.startPlayback(); } else { spawnStationActivity(); } } else { AlertDialog.Builder alert_builder = buildErrorDialog(success_int); showAlert(alert_builder.create()); } m_retrieve_stations_finished_flag = true; } protected void retryAction() { m_alert_active_flag = false; setStation(); } } /** * Description: A login async task specific to logging in a user. */ private class UserLoginTask extends PandoroidPlayerServerTask<String> { private static final int ERROR_BAD_CREDENTIALS = 10; protected void onPreExecute() { showWaiting(getString(R.string.signing_in)); } protected Integer doInBackground(String... strings) { Integer success_flag = -1; try { m_service.runUserLogin(strings[0], strings[1]); // exceptionTest(); } catch (RPCException e) { Log.e("Pandoroid", "RPC error running user login. " + e.getMessage()); if (e.code == RPCException.INVALID_USER_CREDENTIALS) { success_flag = ERROR_BAD_CREDENTIALS; } else { success_flag = rpcExceptionHandler(e); } } catch (HttpResponseException e) { Log.e("Pandoroid", "Error running user login.", e); success_flag = httpResponseExceptionHandler(e); } catch (IOException e) { Log.e("Pandoroid", "Error running user login.", e); success_flag = ioExceptionHandler(e); } catch (Exception e) { Log.e("Pandoroid", "Error running user login.", e); success_flag = generalExceptionHandler(e); } return success_flag; } protected void onPostExecute(Integer success_int) { int success = success_int.intValue(); if (success == ERROR_BAD_CREDENTIALS) { int len = Toast.LENGTH_LONG; CharSequence text = "Wrong username and/or password!"; Toast toasty = Toast.makeText(PandoroidPlayer.this, text, len); toasty.show(); spawnLoginActivity(); } else if (success >= 0) { AlertDialog.Builder alert_builder = buildErrorDialog(success); showAlert(alert_builder.create()); } else { setStation(); } m_user_login_finished_flag = true; } protected void retryAction() { m_alert_active_flag = false; userLogin(); } } }