diff options
Diffstat (limited to 'src/de/danoeh/antennapod')
20 files changed, 1190 insertions, 1083 deletions
diff --git a/src/de/danoeh/antennapod/activity/AddFeedActivity.java b/src/de/danoeh/antennapod/activity/AddFeedActivity.java index 39434fa87..545b53494 100644 --- a/src/de/danoeh/antennapod/activity/AddFeedActivity.java +++ b/src/de/danoeh/antennapod/activity/AddFeedActivity.java @@ -2,6 +2,8 @@ package de.danoeh.antennapod.activity; import java.util.Date; +import org.apache.commons.lang3.StringUtils; + import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; @@ -44,6 +46,9 @@ public class AddFeedActivity extends SherlockActivity { @Override protected void onCreate(Bundle savedInstanceState) { + if (AppConfig.DEBUG) + Log.d(TAG, "Was started with Intent " + getIntent().getAction() + + " and Data " + getIntent().getDataString()); setTheme(UserPreferences.getTheme()); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -54,6 +59,10 @@ public class AddFeedActivity extends SherlockActivity { progDialog = new ProgressDialog(this); etxtFeedurl = (EditText) findViewById(R.id.etxtFeedurl); + if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) { + etxtFeedurl.setText(getIntent().getDataString()); + } + butBrowseMiroGuide = (Button) findViewById(R.id.butBrowseMiroguide); butOpmlImport = (Button) findViewById(R.id.butOpmlImport); butConfirm = (Button) findViewById(R.id.butConfirm); @@ -101,7 +110,7 @@ public class AddFeedActivity extends SherlockActivity { if (intent.getAction() != null && intent.getAction().equals(Intent.ACTION_SEND)) { if (AppConfig.DEBUG) - Log.d(TAG, "Was started with ACTION_SEND intent"); + Log.d(TAG, "Resuming with ACTION_SEND intent"); String text = intent.getStringExtra(Intent.EXTRA_TEXT); if (text != null) { etxtFeedurl.setText(text); @@ -152,7 +161,7 @@ public class AddFeedActivity extends SherlockActivity { } @Override - public void onConnectionFailure(int reason) { + public void onConnectionFailure(DownloadError reason) { handleDownloadError(reason); } }); @@ -168,11 +177,11 @@ public class AddFeedActivity extends SherlockActivity { progDialog.setMessage(getString(R.string.loading_label)); } - private void handleDownloadError(int reason) { + private void handleDownloadError(DownloadError reason) { final AlertDialog errorDialog = new AlertDialog.Builder(this).create(); errorDialog.setTitle(R.string.error_label); errorDialog.setMessage(getString(R.string.error_msg_prefix) + " " - + DownloadError.getErrorString(this, reason)); + + reason.getErrorString(this)); errorDialog.setButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() { @Override diff --git a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index 50ff38e22..ce58babc0 100644 --- a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -89,9 +89,8 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity { if (status.isSuccessful()) { parseFeed(); } else { - String errorMsg = DownloadError.getErrorString( - OnlineFeedViewActivity.this, - status.getReason()); + String errorMsg = status.getReason().getErrorString( + OnlineFeedViewActivity.this); if (errorMsg != null && status.getReasonDetailed() != null) { errorMsg += " (" @@ -191,9 +190,9 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity { } }); } else { - final String errorMsg = DownloadError.getErrorString( - OnlineFeedViewActivity.this, - DownloadError.ERROR_PARSER_EXCEPTION) + final String errorMsg = + DownloadError.ERROR_PARSER_EXCEPTION.getErrorString( + OnlineFeedViewActivity.this) + " (" + reasonDetailed + ")"; runOnUiThread(new Runnable() { diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java index 14d9c29cc..be5fc2c26 100644 --- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -12,6 +12,7 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; import android.preference.CheckBoxPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceChangeListener; import android.preference.Preference.OnPreferenceClickListener; @@ -147,32 +148,47 @@ public class PreferenceActivity extends SherlockPreferenceActivity { .setOnPreferenceChangeListener( new OnPreferenceChangeListener() { + @Override - public boolean onPreferenceChange( - Preference preference, Object newValue) { - if (newValue instanceof String) { - setEpisodeCacheSizeText(Integer - .valueOf((String) newValue)); - } + public boolean onPreferenceChange(Preference preference, Object o) { + checkItemVisibility(); return true; } }); - findPreference(UserPreferences.PREF_ENABLE_AUTODL) - .setOnPreferenceClickListener(new OnPreferenceClickListener() { - - @Override - public boolean onPreferenceClick(Preference preference) { - checkItemVisibility(); - return true; - } - }); - + buildUpdateIntervalPreference(); buildAutodownloadSelectedNetworsPreference(); setSelectedNetworksEnabled(UserPreferences .isEnableAutodownloadWifiFilter()); } + private void buildUpdateIntervalPreference() { + ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_UPDATE_INTERVAL); + String[] values = getResources().getStringArray( + R.array.update_intervall_values); + String[] entries = new String[values.length]; + for (int x = 0; x < values.length; x++) { + Integer v = Integer.parseInt(values[x]); + switch (v) { + case 0: + entries[x] = getString(R.string.pref_update_interval_hours_manual); + break; + case 1: + entries[x] = v + + " " + + getString(R.string.pref_update_interval_hours_singular); + break; + default: + entries[x] = v + " " + + getString(R.string.pref_update_interval_hours_plural); + break; + + } + } + pref.setEntries(entries); + + } + private void setSelectedNetworksEnabled(boolean b) { if (selectedNetworks != null) { for (Preference p : selectedNetworks) { diff --git a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java index 5f6a0ea77..c067ac5d2 100644 --- a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java +++ b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java @@ -71,8 +71,7 @@ public class DownloadLogAdapter extends BaseAdapter { holder.successful.setTextColor(convertView.getResources().getColor( R.color.download_failed_red)); holder.successful.setText(R.string.download_failed); - String reasonText = DownloadError.getErrorString(context, - status.getReason()); + String reasonText = status.getReason().getErrorString(context); if (status.getReasonDetailed() != null) { reasonText += ": " + status.getReasonDetailed(); } diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java index 4637c7725..99bef4bd8 100644 --- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java +++ b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java @@ -20,7 +20,7 @@ import android.net.Uri; public class MiroGuideConnector { private HttpClient httpClient; - private static final String HOST_URL = "https://www.miroguide.com/api/"; + private static final String HOST_URL = "http://www.miroguide.com/api/"; private static final String PATH_GET_CHANNELS = "get_channels"; private static final String PATH_LIST_CATEGORIES = "list_categories"; private static final String PATH_GET_CHANNEL = "get_channel"; diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java index edcc4157e..a173dfdfb 100644 --- a/src/de/danoeh/antennapod/service/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/PlaybackService.java @@ -48,6 +48,7 @@ import de.danoeh.antennapod.util.QueueAccess; import de.danoeh.antennapod.util.flattr.FlattrUtils; import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.playback.Playable.PlayableException; +import de.danoeh.antennapod.util.playback.PlaybackController; /** * Controls the MediaPlayer that plays a FeedMedia-file @@ -435,41 +436,47 @@ public class PlaybackService extends Service { return Service.START_NOT_STICKY; } - /** - * Handles media button events - */ - private void handleKeycode(int keycode) { - if (AppConfig.DEBUG) - Log.d(TAG, "Handling keycode: " + keycode); - switch (keycode) { - case KeyEvent.KEYCODE_HEADSETHOOK: - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - if (status == PlayerStatus.PLAYING) { - pause(true, true); - } else if (status == PlayerStatus.PAUSED) { - play(); - } else if (status == PlayerStatus.PREPARING) { - setStartWhenPrepared(!startWhenPrepared); - } else if (status == PlayerStatus.INITIALIZED) { - startWhenPrepared = true; - prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - if (status == PlayerStatus.PAUSED) { - play(); - } else if (status == PlayerStatus.INITIALIZED) { - startWhenPrepared = true; - prepare(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - if (status == PlayerStatus.PLAYING) { - pause(true, true); - } - break; - } - } + /** Handles media button events */ + private void handleKeycode(int keycode) { + if (AppConfig.DEBUG) + Log.d(TAG, "Handling keycode: " + keycode); + switch (keycode) { + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + if (status == PlayerStatus.PLAYING) { + pause(true, true); + } else if (status == PlayerStatus.PAUSED) { + play(); + } else if (status == PlayerStatus.PREPARING) { + setStartWhenPrepared(!startWhenPrepared); + } else if (status == PlayerStatus.INITIALIZED) { + startWhenPrepared = true; + prepare(); + } + break; + case KeyEvent.KEYCODE_MEDIA_PLAY: + if (status == PlayerStatus.PAUSED) { + play(); + } else if (status == PlayerStatus.INITIALIZED) { + startWhenPrepared = true; + prepare(); + } + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + if (status == PlayerStatus.PLAYING) { + pause(true, true); + } + break; + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: { + seekDelta(PlaybackController.DEFAULT_SEEK_DELTA); + break; + } + case KeyEvent.KEYCODE_MEDIA_REWIND: { + seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA); + break; + } + } + } /** * Called by a mediaplayer Activity as soon as it has prepared its @@ -544,12 +551,12 @@ public class PlaybackService extends Service { status = PlayerStatus.STOPPED; } - public void notifyVideoSurfaceAbandoned() { - resetVideoSurface(); + public void notifyVideoSurfaceAbandoned() { + resetVideoSurface(); if (media != null) { initMediaplayer(true); } - } + } /** * Called after service has extracted the media it is supposed to play. @@ -635,125 +642,125 @@ public class PlaybackService extends Service { } } - private void setupPositionSaver() { - if (positionSaverFuture == null - || (positionSaverFuture.isCancelled() || positionSaverFuture - .isDone())) { - - positionSaver = new PositionSaver(); - positionSaverFuture = schedExecutor.scheduleAtFixedRate( - positionSaver, PositionSaver.WAITING_INTERVALL, - PositionSaver.WAITING_INTERVALL, TimeUnit.MILLISECONDS); - } - } - - private void cancelPositionSaver() { - if (positionSaverFuture != null) { - boolean result = positionSaverFuture.cancel(true); - if (AppConfig.DEBUG) - Log.d(TAG, "PositionSaver cancelled. Result: " + result); - } - } - - private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(MediaPlayer mp) { - if (AppConfig.DEBUG) - Log.d(TAG, "Resource prepared"); - mp.seekTo(media.getPosition()); - if (media.getDuration() == 0) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting duration of media"); - media.setDuration(mp.getDuration()); - } - setStatus(PlayerStatus.PREPARED); - if (chapterLoader != null) { - chapterLoader.interrupt(); - } - chapterLoader = new Thread() { - @Override - public void run() { - if (AppConfig.DEBUG) - Log.d(TAG, "Chapter loader started"); - if (media != null && media.getChapters() == null) { - media.loadChapterMarks(); - if (!isInterrupted() && media.getChapters() != null) { - sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, - 0); - } - } - if (AppConfig.DEBUG) - Log.d(TAG, "Chapter loader stopped"); - } - }; - chapterLoader.start(); - - if (startWhenPrepared) { - play(); - } - } - }; - - private MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { - - @Override - public void onSeekComplete(MediaPlayer mp) { - if (status == PlayerStatus.SEEKING) { - setStatus(statusBeforeSeek); - } - - } - }; - - private MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() { - - @Override - public boolean onInfo(MediaPlayer mp, int what, int extra) { - switch (what) { - case MediaPlayer.MEDIA_INFO_BUFFERING_START: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); - return true; - case MediaPlayer.MEDIA_INFO_BUFFERING_END: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); - return true; - default: - return false; - } - } - }; - - private MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() { - private static final String TAG = "PlaybackService.onErrorListener"; - - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - Log.w(TAG, "An error has occured: " + what); - if (mp.isPlaying()) { - pause(true, true); - } - sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); - setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); - stopSelf(); - return true; - } - }; - - private MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() { - - @Override - public void onCompletion(MediaPlayer mp) { - endPlayback(true); - } - }; - - private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { - - @Override - public void onBufferingUpdate(MediaPlayer mp, int percent) { - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); - - } - }; + private void setupPositionSaver() { + if (positionSaverFuture == null + || (positionSaverFuture.isCancelled() || positionSaverFuture + .isDone())) { + + positionSaver = new PositionSaver(); + positionSaverFuture = schedExecutor.scheduleAtFixedRate( + positionSaver, PositionSaver.WAITING_INTERVALL, + PositionSaver.WAITING_INTERVALL, TimeUnit.MILLISECONDS); + } + } + + private void cancelPositionSaver() { + if (positionSaverFuture != null) { + boolean result = positionSaverFuture.cancel(true); + if (AppConfig.DEBUG) + Log.d(TAG, "PositionSaver cancelled. Result: " + result); + } + } + + private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mp) { + if (AppConfig.DEBUG) + Log.d(TAG, "Resource prepared"); + mp.seekTo(media.getPosition()); + if (media.getDuration() == 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Setting duration of media"); + media.setDuration(mp.getDuration()); + } + setStatus(PlayerStatus.PREPARED); + if (chapterLoader != null) { + chapterLoader.interrupt(); + } + chapterLoader = new Thread() { + @Override + public void run() { + if (AppConfig.DEBUG) + Log.d(TAG, "Chapter loader started"); + if (media != null && media.getChapters() == null) { + media.loadChapterMarks(); + if (!isInterrupted() && media.getChapters() != null) { + sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, + 0); + } + } + if (AppConfig.DEBUG) + Log.d(TAG, "Chapter loader stopped"); + } + }; + chapterLoader.start(); + + if (startWhenPrepared) { + play(); + } + } + }; + + private MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { + + @Override + public void onSeekComplete(MediaPlayer mp) { + if (status == PlayerStatus.SEEKING) { + setStatus(statusBeforeSeek); + } + + } + }; + + private MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() { + + @Override + public boolean onInfo(MediaPlayer mp, int what, int extra) { + switch (what) { + case MediaPlayer.MEDIA_INFO_BUFFERING_START: + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); + return true; + case MediaPlayer.MEDIA_INFO_BUFFERING_END: + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); + return true; + default: + return false; + } + } + }; + + private MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() { + private static final String TAG = "PlaybackService.onErrorListener"; + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.w(TAG, "An error has occured: " + what); + if (mp.isPlaying()) { + pause(true, true); + } + sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what); + setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); + stopSelf(); + return true; + } + }; + + private MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() { + + @Override + public void onCompletion(MediaPlayer mp) { + endPlayback(true); + } + }; + + private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { + + @Override + public void onBufferingUpdate(MediaPlayer mp, int percent) { + sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); + + } + }; private void endPlayback(boolean playNextEpisode) { if (AppConfig.DEBUG) @@ -833,757 +840,738 @@ public class PlaybackService extends Service { } } - public void setSleepTimer(long waitingTime) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) - + " milliseconds"); - if (sleepTimerFuture != null) { - sleepTimerFuture.cancel(true); - } - sleepTimer = new SleepTimer(waitingTime); - sleepTimerFuture = schedExecutor.submit(sleepTimer); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - public void disableSleepTimer() { - if (sleepTimerFuture != null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Disabling sleep timer"); - sleepTimerFuture.cancel(true); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - } - - /** - * Saves the current position and pauses playback. Note that, if audiofocus - * is abandoned, the lockscreen controls will also disapear. - * - * @param abandonFocus is true if the service should release audio focus - * @param reinit is true if service should reinit after pausing if the media - * file is being streamed - */ - public void pause(boolean abandonFocus, boolean reinit) { - if (player.isPlaying()) { - if (AppConfig.DEBUG) - Log.d(TAG, "Pausing playback."); - player.pause(); - if (abandonFocus) { - audioManager.abandonAudioFocus(audioFocusChangeListener); - pausedBecauseOfTransientAudiofocusLoss = false; - disableSleepTimer(); - } - cancelPositionSaver(); - saveCurrentPosition(); - setStatus(PlayerStatus.PAUSED); - stopWidgetUpdater(); - stopForeground(true); - if (shouldStream && reinit) { - reinit(); - } - } - } - - /** - * Pauses playback and destroys service. Recommended for video playback. - */ - public void stop() { - if (AppConfig.DEBUG) - Log.d(TAG, "Stopping playback"); - if (status == PlayerStatus.PREPARED || status == PlayerStatus.PAUSED - || status == PlayerStatus.STOPPED - || status == PlayerStatus.PLAYING) { - player.stop(); - } - setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); - stopSelf(); - } - - /** - * Prepared media player for playback if the service is in the INITALIZED - * state. - */ - public void prepare() { - if (status == PlayerStatus.INITIALIZED) { - if (AppConfig.DEBUG) - Log.d(TAG, "Preparing media player"); - setStatus(PlayerStatus.PREPARING); - player.prepareAsync(); - } - } - - /** - * Resets the media player and moves into INITIALIZED state. - */ - public void reinit() { - player.reset(); - player = createMediaPlayer(player); - initMediaplayer(false); - } - - @SuppressLint("NewApi") - public void play() { - if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED - || status == PlayerStatus.STOPPED) { - int focusGained = audioManager.requestAudioFocus( - audioFocusChangeListener, AudioManager.STREAM_MUSIC, - AudioManager.AUDIOFOCUS_GAIN); - - if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - if (AppConfig.DEBUG) - Log.d(TAG, "Audiofocus successfully requested"); - if (AppConfig.DEBUG) - Log.d(TAG, "Resuming/Starting playback"); - writePlaybackPreferences(); - - player.start(); - if (status != PlayerStatus.PAUSED) { - player.seekTo((int) media.getPosition()); - } - setStatus(PlayerStatus.PLAYING); - setupPositionSaver(); - setupWidgetUpdater(); - setupNotification(); - pausedBecauseOfTransientAudiofocusLoss = false; - if (android.os.Build.VERSION.SDK_INT >= 14) { - audioManager - .registerRemoteControlClient(remoteControlClient); - } - audioManager - .registerMediaButtonEventReceiver(mediaButtonReceiver); - media.onPlaybackStart(); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Failed to request Audiofocus"); - } - } - } - - private void writePlaybackPreferences() { - if (AppConfig.DEBUG) - Log.d(TAG, "Writing playback preferences"); - - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - if (media != null) { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - media.getPlayableType()); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, - shouldStream); - editor.putBoolean( - PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, - playingVideo); - if (media instanceof FeedMedia) { - FeedMedia fMedia = (FeedMedia) media; - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - fMedia.getItem().getFeed().getId()); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - fMedia.getId()); - } else { - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - media.writeToPreferences(editor); - } else { - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - editor.putLong( - PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, - PlaybackPreferences.NO_MEDIA_PLAYING); - } - - editor.commit(); - } - - private void setStatus(PlayerStatus newStatus) { - if (AppConfig.DEBUG) - Log.d(TAG, "Setting status to " + newStatus); - status = newStatus; - sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); - updateWidget(); - refreshRemoteControlClientState(); - bluetoothNotifyChange(); - } - - /** - * Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. - */ - private void postStatusUpdateIntent() { - setStatus(status); - } - - private void sendNotificationBroadcast(int type, int code) { - Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); - intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); - intent.putExtra(EXTRA_NOTIFICATION_CODE, code); - sendBroadcast(intent); - } - - /** - * Used by setupNotification to load notification data in another thread. - */ - private AsyncTask<Void, Void, Void> notificationSetupTask; - - /** - * Prepares notification and starts the service in the foreground. - */ - @SuppressLint("NewApi") - private void setupNotification() { - final PendingIntent pIntent = PendingIntent.getActivity(this, 0, - PlaybackService.getPlayerActivityIntent(this), - PendingIntent.FLAG_UPDATE_CURRENT); - - if (notificationSetupTask != null) { - notificationSetupTask.cancel(true); - } - notificationSetupTask = new AsyncTask<Void, Void, Void>() { - Bitmap icon = null; - - @Override - protected Void doInBackground(Void... params) { - if (AppConfig.DEBUG) - Log.d(TAG, "Starting background work"); - if (android.os.Build.VERSION.SDK_INT >= 11) { - if (media != null && media != null) { - int iconSize = getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - icon = BitmapDecoder - .decodeBitmapFromWorkerTaskResource(iconSize, - media); - } - - } - if (icon == null) { - icon = BitmapFactory.decodeResource(getResources(), - R.drawable.ic_stat_antenna); - } - - return null; - } - - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - if (!isCancelled() && status == PlayerStatus.PLAYING - && media != null) { - String contentText = media.getFeedTitle(); - String contentTitle = media.getEpisodeTitle(); - Notification notification = null; - if (android.os.Build.VERSION.SDK_INT >= 16) { - Intent pauseButtonIntent = new Intent( - PlaybackService.this, PlaybackService.class); - pauseButtonIntent.putExtra( - MediaButtonReceiver.EXTRA_KEYCODE, - KeyEvent.KEYCODE_MEDIA_PAUSE); - PendingIntent pauseButtonPendingIntent = PendingIntent - .getService(PlaybackService.this, 0, - pauseButtonIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - Notification.Builder notificationBuilder = new Notification.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText) - .setOngoing(true) - .setContentIntent(pIntent) - .setLargeIcon(icon) - .setSmallIcon(R.drawable.ic_stat_antenna) - .addAction(android.R.drawable.ic_media_pause, - getString(R.string.pause_label), - pauseButtonPendingIntent); - notification = notificationBuilder.build(); - } else { - NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( - PlaybackService.this) - .setContentTitle(contentTitle) - .setContentText(contentText).setOngoing(true) - .setContentIntent(pIntent).setLargeIcon(icon) - .setSmallIcon(R.drawable.ic_stat_antenna); - notification = notificationBuilder.getNotification(); - } - startForeground(NOTIFICATION_ID, notification); - if (AppConfig.DEBUG) - Log.d(TAG, "Notification set up"); - } - } - - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - notificationSetupTask - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - notificationSetupTask.execute(); - } - - } - - /** - * Seek a specific position from the current position - * - * @param delta offset from current position (positive or negative) - */ - public void seekDelta(int delta) { - int position = getCurrentPositionSafe(); - if (position != INVALID_TIME) { - seek(player.getCurrentPosition() + delta); - } - } - - public void seek(int i) { - saveCurrentPosition(); - if (status == PlayerStatus.INITIALIZED - || status == PlayerStatus.INITIALIZING - || status == PlayerStatus.PREPARING) { - media.setPosition(i); - setStartWhenPrepared(true); - prepare(); - } else { - if (AppConfig.DEBUG) - Log.d(TAG, "Seeking position " + i); - if (shouldStream) { - if (status != PlayerStatus.SEEKING) { - statusBeforeSeek = status; - } - setStatus(PlayerStatus.SEEKING); - } - player.seekTo(i); - } - } - - public void seekToChapter(Chapter chapter) { - seek((int) chapter.getStart()); - } - - /** - * Saves the current position of the media file to the DB - */ - private synchronized void saveCurrentPosition() { - int position = getCurrentPositionSafe(); - if (position != INVALID_TIME) { - if (AppConfig.DEBUG) - Log.d(TAG, "Saving current position to " + position); - media.saveCurrentPosition(PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()), - position); - } - } - - private void stopWidgetUpdater() { - if (widgetUpdaterFuture != null) { - if (AppConfig.DEBUG) - Log.d(TAG, "Stopping widgetUpdateWorker"); - widgetUpdaterFuture.cancel(true); - } - sendBroadcast(new Intent(PlayerWidget.STOP_WIDGET_UPDATE)); - } - - @SuppressLint("NewApi") - private void setupWidgetUpdater() { - if (widgetUpdaterFuture == null - || (widgetUpdaterFuture.isCancelled() || widgetUpdaterFuture - .isDone())) { - widgetUpdater = new WidgetUpdateWorker(); - widgetUpdaterFuture = schedExecutor.scheduleAtFixedRate( - widgetUpdater, WidgetUpdateWorker.NOTIFICATION_INTERVALL, - WidgetUpdateWorker.NOTIFICATION_INTERVALL, - TimeUnit.MILLISECONDS); - } - } - - private void updateWidget() { - if (AppConfig.DEBUG) - Log.d(TAG, "Sending widget update request"); - PlaybackService.this.sendBroadcast(new Intent( - PlayerWidget.FORCE_WIDGET_UPDATE)); - } - - public boolean sleepTimerActive() { - return sleepTimer != null && sleepTimer.isWaiting(); - } - - public long getSleepTimerTimeLeft() { - if (sleepTimerActive()) { - return sleepTimer.getWaitingTime(); - } else { - return 0; - } - } - - @SuppressLint("NewApi") - private RemoteControlClient setupRemoteControlClient() { - Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); - mediaButtonIntent.setComponent(mediaButtonReceiver); - PendingIntent mediaPendingIntent = PendingIntent.getBroadcast( - getApplicationContext(), 0, mediaButtonIntent, 0); - remoteControlClient = new RemoteControlClient(mediaPendingIntent); - int controlFlags; - if (android.os.Build.VERSION.SDK_INT < 16) { - controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE - | RemoteControlClient.FLAG_KEY_MEDIA_NEXT; - } else { - controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; - } - remoteControlClient.setTransportControlFlags(controlFlags); - return remoteControlClient; - } - - /** - * Refresh player status and metadata. - */ - @SuppressLint("NewApi") - private void refreshRemoteControlClientState() { - if (android.os.Build.VERSION.SDK_INT >= 14) { - if (remoteControlClient != null) { - switch (status) { - case PLAYING: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); - break; - case PAUSED: - case INITIALIZED: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); - break; - case STOPPED: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); - break; - case ERROR: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR); - break; - default: - remoteControlClient - .setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING); - } - if (media != null) { - MetadataEditor editor = remoteControlClient - .editMetadata(false); - editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, - media.getEpisodeTitle()); - - editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, - media.getFeedTitle()); - - editor.apply(); - } - if (AppConfig.DEBUG) - Log.d(TAG, "RemoteControlClient state was refreshed"); - } - } - } - - private void bluetoothNotifyChange() { - boolean isPlaying = false; - - if (status == PlayerStatus.PLAYING) { - isPlaying = true; - } - - Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); - i.putExtra("id", 1); - i.putExtra("artist", ""); - i.putExtra("album", media.getFeedTitle()); - i.putExtra("track", media.getEpisodeTitle()); - i.putExtra("playing", isPlaying); + public void setSleepTimer(long waitingTime) { + if (AppConfig.DEBUG) + Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + + " milliseconds"); + if (sleepTimerFuture != null) { + sleepTimerFuture.cancel(true); + } + sleepTimer = new SleepTimer(waitingTime); + sleepTimerFuture = schedExecutor.submit(sleepTimer); + sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); + } + + public void disableSleepTimer() { + if (sleepTimerFuture != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Disabling sleep timer"); + sleepTimerFuture.cancel(true); + sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); + } + } + + /** + * Saves the current position and pauses playback. Note that, if audiofocus + * is abandoned, the lockscreen controls will also disapear. + * + * @param abandonFocus + * is true if the service should release audio focus + * @param reinit + * is true if service should reinit after pausing if the media + * file is being streamed + */ + public void pause(boolean abandonFocus, boolean reinit) { + if (player.isPlaying()) { + if (AppConfig.DEBUG) + Log.d(TAG, "Pausing playback."); + player.pause(); + if (abandonFocus) { + audioManager.abandonAudioFocus(audioFocusChangeListener); + pausedBecauseOfTransientAudiofocusLoss = false; + disableSleepTimer(); + } + cancelPositionSaver(); + saveCurrentPosition(); + setStatus(PlayerStatus.PAUSED); + stopWidgetUpdater(); + stopForeground(true); + if (shouldStream && reinit) { + reinit(); + } + } + } + + /** Pauses playback and destroys service. Recommended for video playback. */ + public void stop() { + if (AppConfig.DEBUG) + Log.d(TAG, "Stopping playback"); + if (status == PlayerStatus.PREPARED || status == PlayerStatus.PAUSED + || status == PlayerStatus.STOPPED + || status == PlayerStatus.PLAYING) { + player.stop(); + } + setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING); + stopSelf(); + } + + /** + * Prepared media player for playback if the service is in the INITALIZED + * state. + */ + public void prepare() { + if (status == PlayerStatus.INITIALIZED) { + if (AppConfig.DEBUG) + Log.d(TAG, "Preparing media player"); + setStatus(PlayerStatus.PREPARING); + player.prepareAsync(); + } + } + + /** Resets the media player and moves into INITIALIZED state. */ + public void reinit() { + player.reset(); + player = createMediaPlayer(player); + initMediaplayer(false); + } + + @SuppressLint("NewApi") + public void play() { + if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED + || status == PlayerStatus.STOPPED) { + int focusGained = audioManager.requestAudioFocus( + audioFocusChangeListener, AudioManager.STREAM_MUSIC, + AudioManager.AUDIOFOCUS_GAIN); + + if (focusGained == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + if (AppConfig.DEBUG) + Log.d(TAG, "Audiofocus successfully requested"); + if (AppConfig.DEBUG) + Log.d(TAG, "Resuming/Starting playback"); + writePlaybackPreferences(); + + player.start(); + if (status != PlayerStatus.PAUSED) { + player.seekTo((int) media.getPosition()); + } + setStatus(PlayerStatus.PLAYING); + setupPositionSaver(); + setupWidgetUpdater(); + setupNotification(); + pausedBecauseOfTransientAudiofocusLoss = false; + if (android.os.Build.VERSION.SDK_INT >= 14) { + audioManager + .registerRemoteControlClient(remoteControlClient); + } + audioManager + .registerMediaButtonEventReceiver(mediaButtonReceiver); + media.onPlaybackStart(); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Failed to request Audiofocus"); + } + } + } + + private void writePlaybackPreferences() { + if (AppConfig.DEBUG) + Log.d(TAG, "Writing playback preferences"); + + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + if (media != null) { + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + media.getPlayableType()); + editor.putBoolean( + PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM, + shouldStream); + editor.putBoolean( + PlaybackPreferences.PREF_CURRENT_EPISODE_IS_VIDEO, + playingVideo); + if (media instanceof FeedMedia) { + FeedMedia fMedia = (FeedMedia) media; + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + fMedia.getItem().getFeed().getId()); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + fMedia.getId()); + } else { + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + } + media.writeToPreferences(editor); + } else { + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + editor.putLong( + PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, + PlaybackPreferences.NO_MEDIA_PLAYING); + } + + editor.commit(); + } + + private void setStatus(PlayerStatus newStatus) { + if (AppConfig.DEBUG) + Log.d(TAG, "Setting status to " + newStatus); + status = newStatus; + sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); + updateWidget(); + refreshRemoteControlClientState(); + bluetoothNotifyChange(); + } + + /** Send ACTION_PLAYER_STATUS_CHANGED without changing the status attribute. */ + private void postStatusUpdateIntent() { + setStatus(status); + } + + private void sendNotificationBroadcast(int type, int code) { + Intent intent = new Intent(ACTION_PLAYER_NOTIFICATION); + intent.putExtra(EXTRA_NOTIFICATION_TYPE, type); + intent.putExtra(EXTRA_NOTIFICATION_CODE, code); + sendBroadcast(intent); + } + + /** Used by setupNotification to load notification data in another thread. */ + private AsyncTask<Void, Void, Void> notificationSetupTask; + + /** Prepares notification and starts the service in the foreground. */ + @SuppressLint("NewApi") + private void setupNotification() { + final PendingIntent pIntent = PendingIntent.getActivity(this, 0, + PlaybackService.getPlayerActivityIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT); + + if (notificationSetupTask != null) { + notificationSetupTask.cancel(true); + } + notificationSetupTask = new AsyncTask<Void, Void, Void>() { + Bitmap icon = null; + + @Override + protected Void doInBackground(Void... params) { + if (AppConfig.DEBUG) + Log.d(TAG, "Starting background work"); + if (android.os.Build.VERSION.SDK_INT >= 11) { + if (media != null && media != null) { + int iconSize = getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + icon = BitmapDecoder + .decodeBitmapFromWorkerTaskResource(iconSize, + media); + } + + } + if (icon == null) { + icon = BitmapFactory.decodeResource(getResources(), + R.drawable.ic_stat_antenna); + } + + return null; + } + + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + if (!isCancelled() && status == PlayerStatus.PLAYING + && media != null) { + String contentText = media.getFeedTitle(); + String contentTitle = media.getEpisodeTitle(); + Notification notification = null; + if (android.os.Build.VERSION.SDK_INT >= 16) { + Intent pauseButtonIntent = new Intent( + PlaybackService.this, PlaybackService.class); + pauseButtonIntent.putExtra( + MediaButtonReceiver.EXTRA_KEYCODE, + KeyEvent.KEYCODE_MEDIA_PAUSE); + PendingIntent pauseButtonPendingIntent = PendingIntent + .getService(PlaybackService.this, 0, + pauseButtonIntent, + PendingIntent.FLAG_UPDATE_CURRENT); + Notification.Builder notificationBuilder = new Notification.Builder( + PlaybackService.this) + .setContentTitle(contentTitle) + .setContentText(contentText) + .setOngoing(true) + .setContentIntent(pIntent) + .setLargeIcon(icon) + .setSmallIcon(R.drawable.ic_stat_antenna) + .addAction(android.R.drawable.ic_media_pause, + getString(R.string.pause_label), + pauseButtonPendingIntent); + notification = notificationBuilder.build(); + } else { + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder( + PlaybackService.this) + .setContentTitle(contentTitle) + .setContentText(contentText).setOngoing(true) + .setContentIntent(pIntent).setLargeIcon(icon) + .setSmallIcon(R.drawable.ic_stat_antenna); + notification = notificationBuilder.getNotification(); + } + startForeground(NOTIFICATION_ID, notification); + if (AppConfig.DEBUG) + Log.d(TAG, "Notification set up"); + } + } + + }; + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + notificationSetupTask + .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + notificationSetupTask.execute(); + } + + } + + /** + * Seek a specific position from the current position + * + * @param delta + * offset from current position (positive or negative) + * */ + public void seekDelta(int delta) { + int position = getCurrentPositionSafe(); + if (position != INVALID_TIME) { + seek(player.getCurrentPosition() + delta); + } + } + + public void seek(int i) { + saveCurrentPosition(); + if (status == PlayerStatus.INITIALIZED + || status == PlayerStatus.INITIALIZING + || status == PlayerStatus.PREPARING) { + media.setPosition(i); + setStartWhenPrepared(true); + prepare(); + } else { + if (AppConfig.DEBUG) + Log.d(TAG, "Seeking position " + i); + if (shouldStream) { + if (status != PlayerStatus.SEEKING) { + statusBeforeSeek = status; + } + setStatus(PlayerStatus.SEEKING); + } + player.seekTo(i); + } + } + + public void seekToChapter(Chapter chapter) { + seek((int) chapter.getStart()); + } + + /** Saves the current position of the media file to the DB */ + private synchronized void saveCurrentPosition() { + int position = getCurrentPositionSafe(); + if (position != INVALID_TIME) { + if (AppConfig.DEBUG) + Log.d(TAG, "Saving current position to " + position); + media.saveCurrentPosition(PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()), + position); + } + } + + private void stopWidgetUpdater() { + if (widgetUpdaterFuture != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Stopping widgetUpdateWorker"); + widgetUpdaterFuture.cancel(true); + } + sendBroadcast(new Intent(PlayerWidget.STOP_WIDGET_UPDATE)); + } + + @SuppressLint("NewApi") + private void setupWidgetUpdater() { + if (widgetUpdaterFuture == null + || (widgetUpdaterFuture.isCancelled() || widgetUpdaterFuture + .isDone())) { + widgetUpdater = new WidgetUpdateWorker(); + widgetUpdaterFuture = schedExecutor.scheduleAtFixedRate( + widgetUpdater, WidgetUpdateWorker.NOTIFICATION_INTERVALL, + WidgetUpdateWorker.NOTIFICATION_INTERVALL, + TimeUnit.MILLISECONDS); + } + } + + private void updateWidget() { + if (AppConfig.DEBUG) + Log.d(TAG, "Sending widget update request"); + PlaybackService.this.sendBroadcast(new Intent( + PlayerWidget.FORCE_WIDGET_UPDATE)); + } + + public boolean sleepTimerActive() { + return sleepTimer != null && sleepTimer.isWaiting(); + } + + public long getSleepTimerTimeLeft() { + if (sleepTimerActive()) { + return sleepTimer.getWaitingTime(); + } else { + return 0; + } + } + + @SuppressLint("NewApi") + private RemoteControlClient setupRemoteControlClient() { + Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(mediaButtonReceiver); + PendingIntent mediaPendingIntent = PendingIntent.getBroadcast( + getApplicationContext(), 0, mediaButtonIntent, 0); + remoteControlClient = new RemoteControlClient(mediaPendingIntent); + int controlFlags; + if (android.os.Build.VERSION.SDK_INT < 16) { + controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE + | RemoteControlClient.FLAG_KEY_MEDIA_NEXT; + } else { + controlFlags = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; + } + remoteControlClient.setTransportControlFlags(controlFlags); + return remoteControlClient; + } + + /** Refresh player status and metadata. */ + @SuppressLint("NewApi") + private void refreshRemoteControlClientState() { + if (android.os.Build.VERSION.SDK_INT >= 14) { + if (remoteControlClient != null) { + switch (status) { + case PLAYING: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING); + break; + case PAUSED: + case INITIALIZED: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED); + break; + case STOPPED: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); + break; + case ERROR: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_ERROR); + break; + default: + remoteControlClient + .setPlaybackState(RemoteControlClient.PLAYSTATE_BUFFERING); + } + if (media != null) { + MetadataEditor editor = remoteControlClient + .editMetadata(false); + editor.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, + media.getEpisodeTitle()); + + editor.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, + media.getFeedTitle()); + + editor.apply(); + } + if (AppConfig.DEBUG) + Log.d(TAG, "RemoteControlClient state was refreshed"); + } + } + } + + private void bluetoothNotifyChange() { + boolean isPlaying = false; + + if (status == PlayerStatus.PLAYING) { + isPlaying = true; + } + + Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); + i.putExtra("id", 1); + i.putExtra("artist", ""); + i.putExtra("album", media.getFeedTitle()); + i.putExtra("track", media.getEpisodeTitle()); + i.putExtra("playing", isPlaying); if (queue != null) { i.putExtra("ListSize", queue.size()); } i.putExtra("duration", media.getDuration()); - i.putExtra("position", media.getPosition()); - sendBroadcast(i); - } - - /** - * Pauses playback when the headset is disconnected and the preference is - * set - */ - private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { - private static final String TAG = "headsetDisconnected"; - private static final int UNPLUGGED = 0; - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) { - int state = intent.getIntExtra("state", -1); - if (state != -1) { - if (AppConfig.DEBUG) - Log.d(TAG, "Headset plug event. State is " + state); - if (state == UNPLUGGED && status == PlayerStatus.PLAYING) { - if (AppConfig.DEBUG) - Log.d(TAG, "Headset was unplugged during playback."); - pauseIfPauseOnDisconnect(); - } - } else { - Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); - } - } - } - }; - - private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - // sound is about to change, eg. bluetooth -> speaker - if (AppConfig.DEBUG) - Log.d(TAG, "Pausing playback because audio is becoming noisy"); - pauseIfPauseOnDisconnect(); - } - // android.media.AUDIO_BECOMING_NOISY - }; - - /** - * Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. - */ - private void pauseIfPauseOnDisconnect() { - if (UserPreferences.isPauseOnHeadsetDisconnect() - && status == PlayerStatus.PLAYING) { - pause(true, true); - } - } - - private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { - schedExecutor.shutdownNow(); - stop(); - media = null; - } - } - - }; - - private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) { - - if (AppConfig.DEBUG) - Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); - if (media != null) { - setStatus(PlayerStatus.STOPPED); - player.reset(); - endPlayback(false); - } - } - } - }; - - /** - * Periodically saves the position of the media file - */ - class PositionSaver implements Runnable { - public static final int WAITING_INTERVALL = 5000; - - @Override - public void run() { - if (player != null && player.isPlaying()) { - try { - saveCurrentPosition(); - } catch (IllegalStateException e) { - Log.w(TAG, - "saveCurrentPosition was called in illegal state"); - } - } - } - } - - /** - * Notifies the player widget in the specified intervall - */ - class WidgetUpdateWorker implements Runnable { - private static final int NOTIFICATION_INTERVALL = 1000; - - @Override - public void run() { - if (PlaybackService.isRunning) { - updateWidget(); - } - } - } - - /** - * Sleeps for a given time and then pauses playback. - */ - class SleepTimer implements Runnable { - private static final String TAG = "SleepTimer"; - private static final long UPDATE_INTERVALL = 1000L; - private volatile long waitingTime; - private boolean isWaiting; - - public SleepTimer(long waitingTime) { - super(); - this.waitingTime = waitingTime; - } - - @Override - public void run() { - isWaiting = true; - if (AppConfig.DEBUG) - Log.d(TAG, "Starting"); - while (waitingTime > 0) { - try { - Thread.sleep(UPDATE_INTERVALL); - waitingTime -= UPDATE_INTERVALL; - - if (waitingTime <= 0) { - if (AppConfig.DEBUG) - Log.d(TAG, "Waiting completed"); - if (status == PlayerStatus.PLAYING) { - if (AppConfig.DEBUG) - Log.d(TAG, "Pausing playback"); - pause(true, true); - } - postExecute(); - } - } catch (InterruptedException e) { - Log.d(TAG, "Thread was interrupted while waiting"); - break; - } - } - postExecute(); - } - - protected void postExecute() { - isWaiting = false; - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - public long getWaitingTime() { - return waitingTime; - } - - public boolean isWaiting() { - return isWaiting; - } - - } - - public static boolean isPlayingVideo() { - return playingVideo; - } - - public boolean isShouldStream() { - return shouldStream; - } - - public PlayerStatus getStatus() { - return status; - } - - public Playable getMedia() { - return media; - } - - public MediaPlayer getPlayer() { - return player; - } - - public boolean isStartWhenPrepared() { - return startWhenPrepared; - } - - public void setStartWhenPrepared(boolean startWhenPrepared) { - this.startWhenPrepared = startWhenPrepared; - postStatusUpdateIntent(); - } - - /** - * call getDuration() on mediaplayer or return INVALID_TIME if player is in - * an invalid state. This method should be used instead of calling - * getDuration() directly to avoid an error. - */ - public int getDurationSafe() { - if (status != null && player != null) { - switch (status) { - case PREPARED: - case PLAYING: - case PAUSED: - case SEEKING: - try { - return player.getDuration(); - } catch (IllegalStateException e) { - e.printStackTrace(); - return INVALID_TIME; - } - default: - return INVALID_TIME; - } - } else { - return INVALID_TIME; - } - } - - /** - * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player - * is in an invalid state. This method should be used instead of calling - * getCurrentPosition() directly to avoid an error. - */ - public int getCurrentPositionSafe() { - if (status != null && player != null) { - switch (status) { - case PREPARED: - case PLAYING: - case PAUSED: - case SEEKING: - return player.getCurrentPosition(); - default: - return INVALID_TIME; - } - } else { - return INVALID_TIME; - } - } - - private void setCurrentlyPlayingMedia(long id) { - SharedPreferences.Editor editor = PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()).edit(); - editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); - editor.commit(); - } - - private static class InitTask extends AsyncTask<Playable, Void, Playable> { - private Playable playable; - public PlayableException exception; - - @Override - protected Playable doInBackground(Playable... params) { - if (params[0] == null) { - throw new IllegalArgumentException("Playable must not be null"); - } - playable = params[0]; - - try { - playable.loadMetadata(); - } catch (PlayableException e) { - e.printStackTrace(); - exception = e; - return null; - } - return playable; - } - - @SuppressLint("NewApi") - public void executeAsync(Playable playable) { - FlattrUtils.hasToken(); - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - executeOnExecutor(THREAD_POOL_EXECUTOR, playable); - } else { - execute(playable); - } - } - - } + i.putExtra("position", media.getPosition()); + sendBroadcast(i); + } + + /** + * Pauses playback when the headset is disconnected and the preference is + * set + */ + private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() { + private static final String TAG = "headsetDisconnected"; + private static final int UNPLUGGED = 0; + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) { + int state = intent.getIntExtra("state", -1); + if (state != -1) { + if (AppConfig.DEBUG) + Log.d(TAG, "Headset plug event. State is " + state); + if (state == UNPLUGGED && status == PlayerStatus.PLAYING) { + if (AppConfig.DEBUG) + Log.d(TAG, "Headset was unplugged during playback."); + pauseIfPauseOnDisconnect(); + } + } else { + Log.e(TAG, "Received invalid ACTION_HEADSET_PLUG intent"); + } + } + } + }; + + private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + // sound is about to change, eg. bluetooth -> speaker + if (AppConfig.DEBUG) + Log.d(TAG, "Pausing playback because audio is becoming noisy"); + pauseIfPauseOnDisconnect(); + } + // android.media.AUDIO_BECOMING_NOISY + }; + + /** Pauses playback if PREF_PAUSE_ON_HEADSET_DISCONNECT was set to true. */ + private void pauseIfPauseOnDisconnect() { + if (UserPreferences.isPauseOnHeadsetDisconnect() + && status == PlayerStatus.PLAYING) { + pause(true, true); + } + } + + private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { + schedExecutor.shutdownNow(); + stop(); + media = null; + } + } + + }; + + private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) { + + if (AppConfig.DEBUG) + Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent"); + if (media != null) { + setStatus(PlayerStatus.STOPPED); + player.reset(); + endPlayback(false); + } + } + } + }; + + /** Periodically saves the position of the media file */ + class PositionSaver implements Runnable { + public static final int WAITING_INTERVALL = 5000; + + @Override + public void run() { + if (player != null && player.isPlaying()) { + try { + saveCurrentPosition(); + } catch (IllegalStateException e) { + Log.w(TAG, + "saveCurrentPosition was called in illegal state"); + } + } + } + } + + /** Notifies the player widget in the specified intervall */ + class WidgetUpdateWorker implements Runnable { + private static final int NOTIFICATION_INTERVALL = 1000; + + @Override + public void run() { + if (PlaybackService.isRunning) { + updateWidget(); + } + } + } + + /** Sleeps for a given time and then pauses playback. */ + class SleepTimer implements Runnable { + private static final String TAG = "SleepTimer"; + private static final long UPDATE_INTERVALL = 1000L; + private volatile long waitingTime; + private boolean isWaiting; + + public SleepTimer(long waitingTime) { + super(); + this.waitingTime = waitingTime; + } + + @Override + public void run() { + isWaiting = true; + if (AppConfig.DEBUG) + Log.d(TAG, "Starting"); + while (waitingTime > 0) { + try { + Thread.sleep(UPDATE_INTERVALL); + waitingTime -= UPDATE_INTERVALL; + + if (waitingTime <= 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Waiting completed"); + if (status == PlayerStatus.PLAYING) { + if (AppConfig.DEBUG) + Log.d(TAG, "Pausing playback"); + pause(true, true); + } + postExecute(); + } + } catch (InterruptedException e) { + Log.d(TAG, "Thread was interrupted while waiting"); + break; + } + } + postExecute(); + } + + protected void postExecute() { + isWaiting = false; + sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); + } + + public long getWaitingTime() { + return waitingTime; + } + + public boolean isWaiting() { + return isWaiting; + } + + } + + public static boolean isPlayingVideo() { + return playingVideo; + } + + public boolean isShouldStream() { + return shouldStream; + } + + public PlayerStatus getStatus() { + return status; + } + + public Playable getMedia() { + return media; + } + + public MediaPlayer getPlayer() { + return player; + } + + public boolean isStartWhenPrepared() { + return startWhenPrepared; + } + + public void setStartWhenPrepared(boolean startWhenPrepared) { + this.startWhenPrepared = startWhenPrepared; + postStatusUpdateIntent(); + } + + /** + * call getDuration() on mediaplayer or return INVALID_TIME if player is in + * an invalid state. This method should be used instead of calling + * getDuration() directly to avoid an error. + */ + public int getDurationSafe() { + if (status != null && player != null) { + switch (status) { + case PREPARED: + case PLAYING: + case PAUSED: + case SEEKING: + try { + return player.getDuration(); + } catch (IllegalStateException e) { + e.printStackTrace(); + return INVALID_TIME; + } + default: + return INVALID_TIME; + } + } else { + return INVALID_TIME; + } + } + + /** + * call getCurrentPosition() on mediaplayer or return INVALID_TIME if player + * is in an invalid state. This method should be used instead of calling + * getCurrentPosition() directly to avoid an error. + */ + public int getCurrentPositionSafe() { + if (status != null && player != null) { + switch (status) { + case PREPARED: + case PLAYING: + case PAUSED: + case SEEKING: + return player.getCurrentPosition(); + default: + return INVALID_TIME; + } + } else { + return INVALID_TIME; + } + } + + private void setCurrentlyPlayingMedia(long id) { + SharedPreferences.Editor editor = PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()).edit(); + editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_MEDIA, id); + editor.commit(); + } + + private static class InitTask extends AsyncTask<Playable, Void, Playable> { + private Playable playable; + public PlayableException exception; + + @Override + protected Playable doInBackground(Playable... params) { + if (params[0] == null) { + throw new IllegalArgumentException("Playable must not be null"); + } + playable = params[0]; + + try { + playable.loadMetadata(); + } catch (PlayableException e) { + e.printStackTrace(); + exception = e; + return null; + } + return playable; + } + + @SuppressLint("NewApi") + public void executeAsync(Playable playable) { + FlattrUtils.hasToken(); + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + executeOnExecutor(THREAD_POOL_EXECUTOR, playable); + } else { + execute(playable); + } + } + + } private void loadQueue() { dbLoaderExecutor.submit(new QueueLoaderTask()); diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java index b19b0482e..2056efab2 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadService.java +++ b/src/de/danoeh/antennapod/service/download/DownloadService.java @@ -608,7 +608,7 @@ public class DownloadService extends Service { private DownloadRequest request; - private int reason; + private DownloadError reason; private boolean successful; public FeedSyncThread(DownloadRequest request) { @@ -626,7 +626,7 @@ public class DownloadService extends Service { feed.setFile_url(request.getDestination()); feed.setDownloaded(true); - reason = 0; + reason = null; String reasonDetailed = null; successful = true; FeedHandler feedHandler = new FeedHandler(); diff --git a/src/de/danoeh/antennapod/service/download/DownloadStatus.java b/src/de/danoeh/antennapod/service/download/DownloadStatus.java index 76091ec67..62e54cbb4 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadStatus.java +++ b/src/de/danoeh/antennapod/service/download/DownloadStatus.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.service.download; import java.util.Date; import de.danoeh.antennapod.feed.FeedFile; +import de.danoeh.antennapod.util.DownloadError; /** Contains status attributes for one download */ public class DownloadStatus { @@ -21,7 +22,7 @@ public class DownloadStatus { * URL if the download has no other title. */ protected String title; - protected int reason; + protected DownloadError reason; /** * A message which can be presented to the user to give more information. * Should be null if Download was successful. @@ -43,7 +44,7 @@ public class DownloadStatus { /** Constructor for restoring Download status entries from DB. */ public DownloadStatus(long id, String title, long feedfileId, - int feedfileType, boolean successful, int reason, + int feedfileType, boolean successful, DownloadError reason, Date completionDate, String reasonDetailed) { this.id = id; this.title = title; @@ -56,7 +57,7 @@ public class DownloadStatus { this.feedfileType = feedfileType; } - public DownloadStatus(DownloadRequest request, int reason, + public DownloadStatus(DownloadRequest request, DownloadError reason, boolean successful, boolean cancelled, String reasonDetailed) { if (request == null) { throw new IllegalArgumentException("request must not be null"); @@ -72,7 +73,7 @@ public class DownloadStatus { } /** Constructor for creating new completed downloads. */ - public DownloadStatus(FeedFile feedfile, String title, int reason, + public DownloadStatus(FeedFile feedfile, String title, DownloadError reason, boolean successful, String reasonDetailed) { if (feedfile == null) { throw new IllegalArgumentException("feedfile must not be null"); @@ -90,7 +91,7 @@ public class DownloadStatus { /** Constructor for creating new completed downloads. */ public DownloadStatus(long feedfileId, int feedfileType, String title, - int reason, boolean successful, String reasonDetailed) { + DownloadError reason, boolean successful, String reasonDetailed) { this.title = title; this.done = true; this.feedfileId = feedfileId; @@ -111,48 +112,70 @@ public class DownloadStatus { + ", cancelled=" + cancelled + "]"; } - public long getId() { - return id; - } - - public String getTitle() { - return title; - } - - public int getReason() { - return reason; - } - - public String getReasonDetailed() { - return reasonDetailed; - } - - public boolean isSuccessful() { - return successful; - } - - public Date getCompletionDate() { - return completionDate; - } - - public long getFeedfileId() { - return feedfileId; - } - - public int getFeedfileType() { - return feedfileType; - } - - public boolean isDone() { - return done; - } - - public boolean isCancelled() { - return cancelled; - } - - public void setId(long id) { - this.id = id; - } - + public long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public DownloadError getReason() { + return reason; + } + + public String getReasonDetailed() { + return reasonDetailed; + } + + public boolean isSuccessful() { + return successful; + } + + public Date getCompletionDate() { + return completionDate; + } + + public long getFeedfileId() { + return feedfileId; + } + + public int getFeedfileType() { + return feedfileType; + } + + public boolean isDone() { + return done; + } + + public boolean isCancelled() { + return cancelled; + } + + public void setSuccessful() { + this.successful = true; + this.reason = DownloadError.SUCCESS; + this.done = true; + } + + public void setFailed(DownloadError reason, String reasonDetailed) { + this.successful = false; + this.reason = reason; + this.reasonDetailed = reasonDetailed; + } + + public void setCancelled() { + this.successful = false; + this.reason = DownloadError.ERROR_DOWNLOAD_CANCELLED; + this.done = true; + this.cancelled = true; + } + + public void setCompletionDate(Date completionDate) { + this.completionDate = completionDate; + } + + public void setId(long id) { + this.id = id; + } }
\ No newline at end of file diff --git a/src/de/danoeh/antennapod/service/download/Downloader.java b/src/de/danoeh/antennapod/service/download/Downloader.java index 67507d40f..84731fe9f 100644 --- a/src/de/danoeh/antennapod/service/download/Downloader.java +++ b/src/de/danoeh/antennapod/service/download/Downloader.java @@ -20,6 +20,7 @@ public abstract class Downloader implements Callable<Downloader> { this.request = request; this.request.setStatusMsg(R.string.download_pending); this.cancelled = false; + this.result = new DownloadStatus(request, null, false, false, null); } protected abstract void download(); diff --git a/src/de/danoeh/antennapod/service/download/HttpDownloader.java b/src/de/danoeh/antennapod/service/download/HttpDownloader.java index b533ca676..c9671ceb3 100644 --- a/src/de/danoeh/antennapod/service/download/HttpDownloader.java +++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java @@ -153,23 +153,21 @@ public class HttpDownloader extends Downloader { private void onSuccess() { if (AppConfig.DEBUG) Log.d(TAG, "Download was successful"); - result = new DownloadStatus(request, 0, true, false, null); + result.setSuccessful(); } - private void onFail(int reason, String reasonDetailed) { + private void onFail(DownloadError reason, String reasonDetailed) { if (AppConfig.DEBUG) { Log.d(TAG, "Download failed"); } - result = new DownloadStatus(request, reason, false, false, - reasonDetailed); + result.setFailed(reason, reasonDetailed); cleanup(); } private void onCancelled() { if (AppConfig.DEBUG) Log.d(TAG, "Download was cancelled"); - result = new DownloadStatus(request, - DownloadError.ERROR_DOWNLOAD_CANCELLED, false, true, null); + result.setCancelled(); cleanup(); } diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java index ababcdf78..4f08c2b00 100644 --- a/src/de/danoeh/antennapod/storage/DBReader.java +++ b/src/de/danoeh/antennapod/storage/DBReader.java @@ -19,6 +19,7 @@ import de.danoeh.antennapod.feed.ID3Chapter; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.feed.VorbisCommentChapter; import de.danoeh.antennapod.service.download.*; +import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator; @@ -428,7 +429,7 @@ public final class DBReader { logCursor .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)); downloadLog.add(new DownloadStatus(id, title, feedfileId, - feedfileType, successful, reason, completionDate, + feedfileType, successful, DownloadError.fromCode(reason), completionDate, reasonDetailed)); } while (logCursor.moveToNext()); diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 5171b6932..5718e03c0 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -519,7 +519,7 @@ public class PodDBAdapter { ContentValues values = new ContentValues(); values.put(KEY_FEEDFILE, status.getFeedfileId()); values.put(KEY_FEEDFILETYPE, status.getFeedfileType()); - values.put(KEY_REASON, status.getReason()); + values.put(KEY_REASON, status.getReason().getCode()); values.put(KEY_SUCCESSFUL, status.isSuccessful()); values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime()); values.put(KEY_REASON_DETAILED, status.getReasonDetailed()); diff --git a/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java b/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java index 4d0b42132..5a2c6005e 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java +++ b/src/de/danoeh/antennapod/syndication/namespace/NSRSS20.java @@ -78,6 +78,15 @@ public class NSRSS20 extends Namespace { @Override public void handleElementEnd(String localName, HandlerState state) { if (localName.equals(ITEM)) { + if (state.getCurrentItem() != null) { + // the title tag is optional in RSS 2.0. The description is used + // as a + // title if the item has no title-tag. + if (state.getCurrentItem().getTitle() == null) { + state.getCurrentItem().setTitle( + state.getCurrentItem().getDescription()); + } + } state.setCurrentItem(null); } else if (state.getTagstack().size() >= 2 && state.getContentBuf() != null) { @@ -98,7 +107,8 @@ public class NSRSS20 extends Namespace { state.getCurrentItem().setTitle(content); } else if (second.equals(CHANNEL)) { state.getFeed().setTitle(content); - } else if (second.equals(IMAGE) && third != null && third.equals(CHANNEL)) { + } else if (second.equals(IMAGE) && third != null + && third.equals(CHANNEL)) { state.getFeed().getImage().setTitle(content); } } else if (top.equals(LINK)) { @@ -110,7 +120,8 @@ public class NSRSS20 extends Namespace { } else if (top.equals(PUBDATE) && second.equals(ITEM)) { state.getCurrentItem().setPubDate( SyndDateUtils.parseRFC822Date(content)); - } else if (top.equals(URL) && second.equals(IMAGE) && third != null && third.equals(CHANNEL)) { + } else if (top.equals(URL) && second.equals(IMAGE) && third != null + && third.equals(CHANNEL)) { state.getFeed().getImage().setDownload_url(content); } else if (localName.equals(DESCR)) { if (second.equals(CHANNEL)) { diff --git a/src/de/danoeh/antennapod/util/ConnectionTester.java b/src/de/danoeh/antennapod/util/ConnectionTester.java index 2fd22d356..5d940d9e1 100644 --- a/src/de/danoeh/antennapod/util/ConnectionTester.java +++ b/src/de/danoeh/antennapod/util/ConnectionTester.java @@ -14,7 +14,7 @@ public class ConnectionTester implements Runnable { private static final String TAG = "ConnectionTester"; private String strUrl; private Callback callback; - private int reason; + private DownloadError reason; private Handler handler; @@ -68,10 +68,10 @@ public class ConnectionTester implements Runnable { public static abstract class Callback { public abstract void onConnectionSuccessful(); - public abstract void onConnectionFailure(int reason); + public abstract void onConnectionFailure(DownloadError reason); } - public int getReason() { + public DownloadError getReason() { return reason; } diff --git a/src/de/danoeh/antennapod/util/DownloadError.java b/src/de/danoeh/antennapod/util/DownloadError.java index 4723a521c..c37a14584 100644 --- a/src/de/danoeh/antennapod/util/DownloadError.java +++ b/src/de/danoeh/antennapod/util/DownloadError.java @@ -4,54 +4,46 @@ import android.content.Context; import de.danoeh.antennapod.R; /** Utility class for Download Errors. */ -public class DownloadError { - public static final int ERROR_PARSER_EXCEPTION = 1; - public static final int ERROR_UNSUPPORTED_TYPE = 2; - public static final int ERROR_CONNECTION_ERROR = 3; - public static final int ERROR_MALFORMED_URL = 4; - public static final int ERROR_IO_ERROR = 5; - public static final int ERROR_FILE_EXISTS = 6; - public static final int ERROR_DOWNLOAD_CANCELLED = 7; - public static final int ERROR_DEVICE_NOT_FOUND = 8; - public static final int ERROR_HTTP_DATA_ERROR = 9; - public static final int ERROR_NOT_ENOUGH_SPACE = 10; - public static final int ERROR_UNKNOWN_HOST = 11; - public static final int ERROR_REQUEST_ERROR = 12; - - /** Get a human-readable string for a specific error code. */ - public static String getErrorString(Context context, int code) { - int resId; - switch(code) { - case ERROR_NOT_ENOUGH_SPACE: - resId = R.string.download_error_insufficient_space; - break; - case ERROR_DEVICE_NOT_FOUND: - resId = R.string.download_error_device_not_found; - break; - case ERROR_IO_ERROR: - resId = R.string.download_error_io_error; - break; - case ERROR_HTTP_DATA_ERROR: - resId = R.string.download_error_http_data_error; - break; - case ERROR_PARSER_EXCEPTION: - resId = R.string.download_error_parser_exception; - break; - case ERROR_UNSUPPORTED_TYPE: - resId = R.string.download_error_unsupported_type; - break; - case ERROR_CONNECTION_ERROR: - resId = R.string.download_error_connection_error; - break; - case ERROR_UNKNOWN_HOST: - resId = R.string.download_error_unknown_host; - break; - case ERROR_REQUEST_ERROR: - resId = R.string.download_error_request_error; - break; - default: - resId = R.string.download_error_error_unknown; +public enum DownloadError { + SUCCESS(0, R.string.download_successful), + ERROR_PARSER_EXCEPTION(1, R.string.download_error_parser_exception), + ERROR_UNSUPPORTED_TYPE(2, R.string.download_error_unsupported_type), + ERROR_CONNECTION_ERROR(3, R.string.download_error_connection_error), + ERROR_MALFORMED_URL(4, R.string.download_error_error_unknown), + ERROR_IO_ERROR(5, R.string.download_error_io_error), + ERROR_FILE_EXISTS(6, R.string.download_error_error_unknown), + ERROR_DOWNLOAD_CANCELLED(7, R.string.download_error_error_unknown), + ERROR_DEVICE_NOT_FOUND(8, R.string.download_error_device_not_found), + ERROR_HTTP_DATA_ERROR(9, R.string.download_error_http_data_error), + ERROR_NOT_ENOUGH_SPACE(10, R.string.download_error_insufficient_space), + ERROR_UNKNOWN_HOST(11, R.string.download_error_unknown_host), + ERROR_REQUEST_ERROR(12, R.string.download_error_request_error); + + private final int code; + private final int resId; + + private DownloadError(int code, int resId) { + this.code = code; + this.resId = resId; + } + + /** Return DownloadError from its associated code. */ + public static DownloadError fromCode(int code) { + for (DownloadError reason : values()) { + if (reason.getCode() == code) { + return reason; + } } + throw new IllegalArgumentException("unknown code: " + code); + } + + /** Get machine-readable code. */ + public int getCode() { + return code; + } + + /** Get a human-readable string. */ + public String getErrorString(Context context) { return context.getString(resId); } diff --git a/src/de/danoeh/antennapod/util/UndoBarController.java b/src/de/danoeh/antennapod/util/UndoBarController.java index e726717a1..a0240e7ce 100644 --- a/src/de/danoeh/antennapod/util/UndoBarController.java +++ b/src/de/danoeh/antennapod/util/UndoBarController.java @@ -16,15 +16,17 @@ package de.danoeh.antennapod.util; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.text.TextUtils; import android.view.View; -import android.view.ViewPropertyAnimator; import android.widget.TextView; +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.AnimatorListenerAdapter; +import com.nineoldandroids.view.ViewHelper; +import com.nineoldandroids.view.ViewPropertyAnimator; +import static com.nineoldandroids.view.ViewPropertyAnimator.animate; import de.danoeh.antennapod.R; @@ -46,7 +48,7 @@ public class UndoBarController { public UndoBarController(View undoBarView, UndoListener undoListener) { mBarView = undoBarView; - mBarAnimator = mBarView.animate(); + mBarAnimator = animate(mBarView); mUndoListener = undoListener; mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message); @@ -73,7 +75,7 @@ public class UndoBarController { mBarView.setVisibility(View.VISIBLE); if (immediate) { - mBarView.setAlpha(1); + ViewHelper.setAlpha(mBarView, 1); } else { mBarAnimator.cancel(); mBarAnimator @@ -89,7 +91,7 @@ public class UndoBarController { mHideHandler.removeCallbacks(mHideRunnable); if (immediate) { mBarView.setVisibility(View.GONE); - mBarView.setAlpha(0); + ViewHelper.setAlpha(mBarView, 0); mUndoMessage = null; mUndoToken = null; diff --git a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java index e1cafe85d..f897f886c 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java @@ -2,18 +2,24 @@ package de.danoeh.antennapod.util.id3reader; import java.io.IOException; import java.io.InputStream; +import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; +import android.util.Log; + +import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.ID3Chapter; import de.danoeh.antennapod.util.id3reader.model.FrameHeader; import de.danoeh.antennapod.util.id3reader.model.TagHeader; public class ChapterReader extends ID3Reader { + private static final String TAG = "ID3ChapterReader"; private static final String FRAME_ID_CHAPTER = "CHAP"; private static final String FRAME_ID_TITLE = "TIT2"; + private static final String FRAME_ID_LINK = "WXXX"; private List<Chapter> chapters; private ID3Chapter currentChapter; @@ -33,27 +39,45 @@ public class ChapterReader extends ID3Reader { if (currentChapter != null) { if (!hasId3Chapter(currentChapter)) { chapters.add(currentChapter); - System.out.println("Found chapter: " + currentChapter); + if (AppConfig.DEBUG) Log.d(TAG, "Found chapter: " + currentChapter); currentChapter = null; } } - String elementId = readISOString(input, Integer.MAX_VALUE); + StringBuffer elementId = new StringBuffer(); + readISOString(elementId, input, Integer.MAX_VALUE); char[] startTimeSource = readBytes(input, 4); long startTime = ((int) startTimeSource[0] << 24) | ((int) startTimeSource[1] << 16) | ((int) startTimeSource[2] << 8) | startTimeSource[3]; - currentChapter = new ID3Chapter(elementId, startTime); + currentChapter = new ID3Chapter(elementId.toString(), startTime); skipBytes(input, 12); return ID3Reader.ACTION_DONT_SKIP; } else if (header.getId().equals(FRAME_ID_TITLE)) { if (currentChapter != null && currentChapter.getTitle() == null) { + StringBuffer title = new StringBuffer(); + readString(title, input, header.getSize()); currentChapter - .setTitle(readString(input, header.getSize())); - System.out.println("Found title: " + currentChapter.getTitle()); + .setTitle(title.toString()); + if (AppConfig.DEBUG) Log.d(TAG, "Found title: " + currentChapter.getTitle()); return ID3Reader.ACTION_DONT_SKIP; } - } + } else if (header.getId().equals(FRAME_ID_LINK)) { + if (currentChapter != null) { + // skip description + int descriptionLength = readString(null, input, header.getSize()); + StringBuffer link = new StringBuffer(); + readISOString(link, input, header.getSize() - descriptionLength); + String decodedLink = URLDecoder.decode(link.toString(), "UTF-8"); + + currentChapter.setLink(decodedLink); + + if (AppConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink()); + return ID3Reader.ACTION_DONT_SKIP; + } + } else if (header.getId().equals("APIC")) { + Log.d(TAG, header.toString()); + } return super.onStartFrameHeader(header, input); } diff --git a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java index dff6d77e8..92f817363 100644 --- a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java +++ b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java @@ -24,7 +24,11 @@ public class ID3Reader { protected int readerPosition; - private static final byte ENCODING_UNICODE = 1; + private static final byte ENCODING_UTF16_WITH_BOM = 1; + private static final byte ENCODING_UTF16_WITHOUT_BOM = 2; + private static final byte ENCODING_UTF8 = 3; + + private TagHeader tagHeader; public ID3Reader() { } @@ -34,7 +38,7 @@ public class ID3Reader { int rc; readerPosition = 0; char[] tagHeaderSource = readBytes(input, HEADER_LENGTH); - TagHeader tagHeader = createTagHeader(tagHeaderSource); + tagHeader = createTagHeader(tagHeaderSource); if (tagHeader == null) { onNoTagHeaderFound(); } else { @@ -124,12 +128,12 @@ public class ID3Reader { + HEADER_LENGTH); } if (hasTag) { - String id = null; - id = new String(source, 0, ID3_LENGTH); + String id = new String(source, 0, ID3_LENGTH); char version = (char) ((source[3] << 8) | source[4]); byte flags = (byte) source[5]; int size = (source[6] << 24) | (source[7] << 16) | (source[8] << 8) | source[9]; + size = unsynchsafe(size); return new TagHeader(id, size, version, flags); } else { return null; @@ -142,48 +146,89 @@ public class ID3Reader { throw new ID3ReaderException("Length of header must be " + HEADER_LENGTH); } - String id = null; - id = new String(source, 0, FRAME_ID_LENGTH); - int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) - | (((int) source[6]) << 8) | source[7]; + String id = new String(source, 0, FRAME_ID_LENGTH); + + int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) + | (((int) source[6]) << 8) | source[7]; + if (tagHeader != null && tagHeader.getVersion() >= 0x0400) { + size = unsynchsafe(size); + } char flags = (char) ((source[8] << 8) | source[9]); return new FrameHeader(id, size, flags); } - protected String readString(InputStream input, int max) throws IOException, + private int unsynchsafe(int in) { + int out = 0; + int mask = 0x7F000000; + + while (mask != 0) { + out >>= 1; + out |= in & mask; + mask >>= 8; + } + + return out; + } + + protected int readString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { if (max > 0) { char[] encoding = readBytes(input, 1); max--; - if (encoding[0] == ENCODING_UNICODE) { - return readUnicodeString(input, max); - } else { - return readISOString(input, max); + if (encoding[0] == ENCODING_UTF16_WITH_BOM || encoding[0] == ENCODING_UTF16_WITHOUT_BOM) { + return readUnicodeString(buffer, input, max, Charset.forName("UTF-16")) + 1; // take encoding byte into account + } else if (encoding[0] == ENCODING_UTF8) { + return readUnicodeString(buffer, input, max, Charset.forName("UTF-8")) + 1; // take encoding byte into account + } else { + return readISOString(buffer, input, max) + 1; // take encoding byte into account } } else { - return ""; + if (buffer != null) { + buffer.append(""); + } + return 0; } } - protected String readISOString(InputStream input, int max) + protected int readISOString(StringBuffer buffer, InputStream input, int max) throws IOException, ID3ReaderException { int bytesRead = 0; - StringBuilder builder = new StringBuilder(); char c; while (++bytesRead <= max && (c = (char) input.read()) > 0) { - builder.append(c); + if (buffer != null) { + buffer.append(c); + } } - return builder.toString(); + return bytesRead; } - private String readUnicodeString(InputStream input, int max) + private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max, Charset charset) throws IOException, ID3ReaderException { byte[] buffer = new byte[max]; - IOUtils.readFully(input, buffer); - Charset charset = Charset.forName("UTF-16"); - return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString(); + int c, cZero = -1; + int i = 0; + for (; i < max; i++) { + c = input.read(); + if (c == -1) { + break; + } else if (c == 0) { + if (cZero == 0) { + // termination character found + break; + } else { + cZero = 0; + } + } else { + buffer[i] = (byte) c; + cZero = -1; + } + } + if (strBuffer != null) { + strBuffer.append(charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString()); + } + return i; } public int onStartTagHeader(TagHeader header) { diff --git a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java index 2c0d8e5ba..df73393a5 100644 --- a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java +++ b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java @@ -11,8 +11,7 @@ public class FrameHeader extends Header { @Override public String toString() { - return "FrameHeader [flags=" + Integer.toString(flags) + ", id=" + id + ", size=" + size - + "]"; - } + return String.format("FrameHeader [flags=%s, id=%s, size=%s]", Integer.toBinaryString(flags), id, Integer.toBinaryString(size)); + } } diff --git a/src/de/danoeh/antennapod/util/playback/PlaybackController.java b/src/de/danoeh/antennapod/util/playback/PlaybackController.java index 847e08b4a..5a5b43a6e 100644 --- a/src/de/danoeh/antennapod/util/playback/PlaybackController.java +++ b/src/de/danoeh/antennapod/util/playback/PlaybackController.java @@ -44,8 +44,8 @@ import de.danoeh.antennapod.util.playback.Playable.PlayableUtils; public abstract class PlaybackController { private static final String TAG = "PlaybackController"; - static final int DEFAULT_SEEK_DELTA = 30000; - public static final int INVALID_TIME = -1; + public static final int DEFAULT_SEEK_DELTA = 30000; + public static final int INVALID_TIME = -1; private Activity activity; |