summaryrefslogtreecommitdiff
path: root/src/de/danoeh/antennapod/service
diff options
context:
space:
mode:
Diffstat (limited to 'src/de/danoeh/antennapod/service')
-rw-r--r--src/de/danoeh/antennapod/service/PlaybackService.java1233
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadRequest.java177
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadService.java1779
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadStatus.java181
-rw-r--r--src/de/danoeh/antennapod/service/download/Downloader.java51
-rw-r--r--src/de/danoeh/antennapod/service/download/HttpDownloader.java51
6 files changed, 1925 insertions, 1547 deletions
diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java
index 294892ec9..a173dfdfb 100644
--- a/src/de/danoeh/antennapod/service/PlaybackService.java
+++ b/src/de/danoeh/antennapod/service/PlaybackService.java
@@ -2,13 +2,8 @@ package de.danoeh.antennapod.service;
import java.io.IOException;
import java.util.Date;
-import java.util.concurrent.Future;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import java.util.List;
+import java.util.concurrent.*;
import android.annotation.SuppressLint;
import android.app.Notification;
@@ -40,367 +35,406 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AudioplayerActivity;
import de.danoeh.antennapod.activity.VideoplayerActivity;
-import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.FeedComponent;
-import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
-import de.danoeh.antennapod.feed.FeedMedia;
-import de.danoeh.antennapod.feed.MediaType;
+import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.receiver.PlayerWidget;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.BitmapDecoder;
+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 */
+/**
+ * Controls the MediaPlayer that plays a FeedMedia-file
+ */
public class PlaybackService extends Service {
- /** Logging tag */
- private static final String TAG = "PlaybackService";
-
- /** Parcelable of type Playable. */
- public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra";
- /** True if media should be streamed. */
- public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.service.shouldStream";
- /**
- * True if playback should be started immediately after media has been
- * prepared.
- */
- public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.service.startWhenPrepared";
-
- public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.service.prepareImmediately";
-
- public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.service.playerStatusChanged";
- private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged";
-
- public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.service.playerNotification";
- public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.service.notificationCode";
- public static final String EXTRA_NOTIFICATION_TYPE = "extra.de.danoeh.antennapod.service.notificationType";
-
- /**
- * If the PlaybackService receives this action, it will stop playback and
- * try to shutdown.
- */
- public static final String ACTION_SHUTDOWN_PLAYBACK_SERVICE = "action.de.danoeh.antennapod.service.actionShutdownPlaybackService";
-
- /**
- * If the PlaybackService receives this action, it will end playback of the
- * current episode and load the next episode if there is one available.
- * */
- public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.service.skipCurrentEpisode";
-
- /** Used in NOTIFICATION_TYPE_RELOAD. */
- public static final int EXTRA_CODE_AUDIO = 1;
- public static final int EXTRA_CODE_VIDEO = 2;
-
- public static final int NOTIFICATION_TYPE_ERROR = 0;
- public static final int NOTIFICATION_TYPE_INFO = 1;
- public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2;
- public static final int NOTIFICATION_TYPE_RELOAD = 3;
- /** The state of the sleeptimer changed. */
- public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4;
- public static final int NOTIFICATION_TYPE_BUFFER_START = 5;
- public static final int NOTIFICATION_TYPE_BUFFER_END = 6;
- /** No more episodes are going to be played. */
- public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7;
-
- /**
- * Returned by getPositionSafe() or getDurationSafe() if the playbackService
- * is in an invalid state.
- */
- public static final int INVALID_TIME = -1;
-
- /** Is true if service is running. */
- public static boolean isRunning = false;
-
- private static final int NOTIFICATION_ID = 1;
-
- private AudioManager audioManager;
- private ComponentName mediaButtonReceiver;
-
- private MediaPlayer player;
- private RemoteControlClient remoteControlClient;
-
- private Playable media;
-
- /** True if media should be streamed (Extracted from Intent Extra) . */
- private boolean shouldStream;
-
- /** True if service should prepare playback after it has been initialized */
- private boolean prepareImmediately;
- private boolean startWhenPrepared;
- private FeedManager manager;
- private PlayerStatus status;
-
- private PositionSaver positionSaver;
- private ScheduledFuture positionSaverFuture;
-
- private WidgetUpdateWorker widgetUpdater;
- private ScheduledFuture widgetUpdaterFuture;
-
- private SleepTimer sleepTimer;
- private Future sleepTimerFuture;
-
- private static final int SCHED_EX_POOL_SIZE = 3;
- private ScheduledThreadPoolExecutor schedExecutor;
-
- private volatile PlayerStatus statusBeforeSeek;
-
- private static boolean playingVideo;
-
- /** True if mediaplayer was paused because it lost audio focus temporarily */
- private boolean pausedBecauseOfTransientAudiofocusLoss;
-
- private Thread chapterLoader;
-
- private final IBinder mBinder = new LocalBinder();
-
- public class LocalBinder extends Binder {
- public PlaybackService getService() {
- return PlaybackService.this;
- }
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received onUnbind event");
- return super.onUnbind(intent);
- }
-
- /**
- * Returns an intent which starts an audio- or videoplayer, depending on the
- * type of media that is being played. If the playbackservice is not
- * running, the type of the last played media will be looked up.
- * */
- public static Intent getPlayerActivityIntent(Context context) {
- if (isRunning) {
- if (playingVideo) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- } else {
- if (PlaybackPreferences.getCurrentEpisodeIsVideo()) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- }
- }
-
- /**
- * Same as getPlayerActivityIntent(context), but here the type of activity
- * depends on the FeedMedia that is provided as an argument.
- */
- public static Intent getPlayerActivityIntent(Context context, Playable media) {
- MediaType mt = media.getMediaType();
- if (mt == MediaType.VIDEO) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onCreate() {
- super.onCreate();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Service created.");
- isRunning = true;
- pausedBecauseOfTransientAudiofocusLoss = false;
- status = PlayerStatus.STOPPED;
- audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- manager = FeedManager.getInstance();
- schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
- new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- }, new RejectedExecutionHandler() {
-
- @Override
- public void rejectedExecution(Runnable r,
- ThreadPoolExecutor executor) {
- Log.w(TAG, "SchedEx rejected submission of new task");
- }
- });
- player = createMediaPlayer();
-
- mediaButtonReceiver = new ComponentName(getPackageName(),
- MediaButtonReceiver.class.getName());
- audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver);
- if (android.os.Build.VERSION.SDK_INT >= 14) {
- audioManager
- .registerRemoteControlClient(setupRemoteControlClient());
- }
- registerReceiver(headsetDisconnected, new IntentFilter(
- Intent.ACTION_HEADSET_PLUG));
- registerReceiver(shutdownReceiver, new IntentFilter(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- registerReceiver(audioBecomingNoisy, new IntentFilter(
- AudioManager.ACTION_AUDIO_BECOMING_NOISY));
- registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
- ACTION_SKIP_CURRENT_EPISODE));
-
- }
-
- private MediaPlayer createMediaPlayer() {
- return createMediaPlayer(new MediaPlayer());
- }
-
- private MediaPlayer createMediaPlayer(MediaPlayer mp) {
- if (mp != null) {
- mp.setOnPreparedListener(preparedListener);
- mp.setOnCompletionListener(completionListener);
- mp.setOnSeekCompleteListener(onSeekCompleteListener);
- mp.setOnErrorListener(onErrorListener);
- mp.setOnBufferingUpdateListener(onBufferingUpdateListener);
- mp.setOnInfoListener(onInfoListener);
- }
- return mp;
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Service is about to be destroyed");
- isRunning = false;
- if (chapterLoader != null) {
- chapterLoader.interrupt();
- }
- disableSleepTimer();
- unregisterReceiver(headsetDisconnected);
- unregisterReceiver(shutdownReceiver);
- unregisterReceiver(audioBecomingNoisy);
- unregisterReceiver(skipCurrentEpisodeReceiver);
- if (android.os.Build.VERSION.SDK_INT >= 14) {
- audioManager.unregisterRemoteControlClient(remoteControlClient);
- }
- audioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver);
- audioManager.abandonAudioFocus(audioFocusChangeListener);
- player.release();
- stopWidgetUpdater();
- updateWidget();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received onBind event");
- return mBinder;
- }
-
- private final OnAudioFocusChangeListener audioFocusChangeListener = new OnAudioFocusChangeListener() {
-
- @Override
- public void onAudioFocusChange(int focusChange) {
- switch (focusChange) {
- case AudioManager.AUDIOFOCUS_LOSS:
- if (AppConfig.DEBUG)
- Log.d(TAG, "Lost audio focus");
- pause(true, false);
- stopSelf();
- break;
- case AudioManager.AUDIOFOCUS_GAIN:
- if (AppConfig.DEBUG)
- Log.d(TAG, "Gained audio focus");
- if (pausedBecauseOfTransientAudiofocusLoss) {
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_RAISE, 0);
- play();
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- if (status == PlayerStatus.PLAYING) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Ducking...");
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_LOWER, 0);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- if (status == PlayerStatus.PLAYING) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Pausing...");
- pause(false, false);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- }
- }
- };
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- super.onStartCommand(intent, flags, startId);
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "OnStartCommand called");
- int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
- if (keycode != -1) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received media button event");
- handleKeycode(keycode);
- } else {
-
- Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
- boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
- true);
- if (playable == null) {
- Log.e(TAG, "Playable extra wasn't sent to the service");
- if (media == null) {
- stopSelf();
- }
- // Intent values appear to be valid
- // check if already playing and playbackType is the same
- } else if (media == null
- || !playable.getIdentifier().equals(media.getIdentifier())
- || playbackType != shouldStream) {
- pause(true, false);
- player.reset();
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
- if (media == null
- || !playable.getIdentifier().equals(
- media.getIdentifier())) {
- media = playable;
- }
-
- if (media != null) {
- shouldStream = playbackType;
- startWhenPrepared = intent.getBooleanExtra(
- EXTRA_START_WHEN_PREPARED, false);
- prepareImmediately = intent.getBooleanExtra(
- EXTRA_PREPARE_IMMEDIATELY, false);
- initMediaplayer();
-
- } else {
- Log.e(TAG, "Media is null");
- stopSelf();
- }
-
- } else if (media != null) {
- if (status == PlayerStatus.PAUSED) {
- play();
- }
-
- } else {
- Log.w(TAG, "Something went wrong. Shutting down...");
- stopSelf();
- }
- }
- return Service.START_NOT_STICKY;
- }
+ /**
+ * Logging tag
+ */
+ private static final String TAG = "PlaybackService";
+
+ /**
+ * Parcelable of type Playable.
+ */
+ public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra";
+ /**
+ * True if media should be streamed.
+ */
+ public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.service.shouldStream";
+ /**
+ * True if playback should be started immediately after media has been
+ * prepared.
+ */
+ public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.service.startWhenPrepared";
+
+ public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.service.prepareImmediately";
+
+ public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.service.playerStatusChanged";
+ private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged";
+
+ public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.service.playerNotification";
+ public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.service.notificationCode";
+ public static final String EXTRA_NOTIFICATION_TYPE = "extra.de.danoeh.antennapod.service.notificationType";
+
+ /**
+ * If the PlaybackService receives this action, it will stop playback and
+ * try to shutdown.
+ */
+ public static final String ACTION_SHUTDOWN_PLAYBACK_SERVICE = "action.de.danoeh.antennapod.service.actionShutdownPlaybackService";
+
+ /**
+ * If the PlaybackService receives this action, it will end playback of the
+ * current episode and load the next episode if there is one available.
+ */
+ public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.service.skipCurrentEpisode";
+
+ /**
+ * Used in NOTIFICATION_TYPE_RELOAD.
+ */
+ public static final int EXTRA_CODE_AUDIO = 1;
+ public static final int EXTRA_CODE_VIDEO = 2;
+
+ public static final int NOTIFICATION_TYPE_ERROR = 0;
+ public static final int NOTIFICATION_TYPE_INFO = 1;
+ public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2;
+
+ /**
+ * Receivers of this intent should update their information about the curently playing media
+ */
+ public static final int NOTIFICATION_TYPE_RELOAD = 3;
+ /**
+ * The state of the sleeptimer changed.
+ */
+ public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4;
+ public static final int NOTIFICATION_TYPE_BUFFER_START = 5;
+ public static final int NOTIFICATION_TYPE_BUFFER_END = 6;
+ /**
+ * No more episodes are going to be played.
+ */
+ public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7;
+
+ /**
+ * Returned by getPositionSafe() or getDurationSafe() if the playbackService
+ * is in an invalid state.
+ */
+ public static final int INVALID_TIME = -1;
+
+ /**
+ * Is true if service is running.
+ */
+ public static boolean isRunning = false;
+
+ private static final int NOTIFICATION_ID = 1;
+
+ private AudioManager audioManager;
+ private ComponentName mediaButtonReceiver;
+
+ private MediaPlayer player;
+ private RemoteControlClient remoteControlClient;
+
+ private Playable media;
+
+ /**
+ * True if media should be streamed (Extracted from Intent Extra) .
+ */
+ private boolean shouldStream;
+
+ private boolean startWhenPrepared;
+ private PlayerStatus status;
+
+ private PositionSaver positionSaver;
+ private ScheduledFuture positionSaverFuture;
+
+ private WidgetUpdateWorker widgetUpdater;
+ private ScheduledFuture widgetUpdaterFuture;
+
+ private SleepTimer sleepTimer;
+ private Future sleepTimerFuture;
+
+ private static final int SCHED_EX_POOL_SIZE = 3;
+ private ScheduledThreadPoolExecutor schedExecutor;
+ private ExecutorService dbLoaderExecutor;
+
+ private volatile PlayerStatus statusBeforeSeek;
+
+ private static boolean playingVideo;
+
+ /**
+ * True if mediaplayer was paused because it lost audio focus temporarily
+ */
+ private boolean pausedBecauseOfTransientAudiofocusLoss;
+
+ private Thread chapterLoader;
+
+ private final IBinder mBinder = new LocalBinder();
+
+ private volatile List<FeedItem> queue;
+
+ public class LocalBinder extends Binder {
+ public PlaybackService getService() {
+ return PlaybackService.this;
+ }
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received onUnbind event");
+ return super.onUnbind(intent);
+ }
+
+ /**
+ * Returns an intent which starts an audio- or videoplayer, depending on the
+ * type of media that is being played. If the playbackservice is not
+ * running, the type of the last played media will be looked up.
+ */
+ public static Intent getPlayerActivityIntent(Context context) {
+ if (isRunning) {
+ if (playingVideo) {
+ return new Intent(context, VideoplayerActivity.class);
+ } else {
+ return new Intent(context, AudioplayerActivity.class);
+ }
+ } else {
+ if (PlaybackPreferences.getCurrentEpisodeIsVideo()) {
+ return new Intent(context, VideoplayerActivity.class);
+ } else {
+ return new Intent(context, AudioplayerActivity.class);
+ }
+ }
+ }
+
+ /**
+ * Same as getPlayerActivityIntent(context), but here the type of activity
+ * depends on the FeedMedia that is provided as an argument.
+ */
+ public static Intent getPlayerActivityIntent(Context context, Playable media) {
+ MediaType mt = media.getMediaType();
+ if (mt == MediaType.VIDEO) {
+ return new Intent(context, VideoplayerActivity.class);
+ } else {
+ return new Intent(context, AudioplayerActivity.class);
+ }
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Service created.");
+ isRunning = true;
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ status = PlayerStatus.STOPPED;
+ audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
+ new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ }, new RejectedExecutionHandler() {
+
+ @Override
+ public void rejectedExecution(Runnable r,
+ ThreadPoolExecutor executor) {
+ Log.w(TAG, "SchedEx rejected submission of new task");
+ }
+ }
+ );
+ dbLoaderExecutor = Executors.newSingleThreadExecutor();
+ player = createMediaPlayer();
+
+ mediaButtonReceiver = new ComponentName(getPackageName(),
+ MediaButtonReceiver.class.getName());
+ audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver);
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ audioManager
+ .registerRemoteControlClient(setupRemoteControlClient());
+ }
+ registerReceiver(headsetDisconnected, new IntentFilter(
+ Intent.ACTION_HEADSET_PLUG));
+ registerReceiver(shutdownReceiver, new IntentFilter(
+ ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ registerReceiver(audioBecomingNoisy, new IntentFilter(
+ AudioManager.ACTION_AUDIO_BECOMING_NOISY));
+ registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
+ ACTION_SKIP_CURRENT_EPISODE));
+ EventDistributor.getInstance().register(eventDistributorListener);
+ loadQueue();
+ }
+
+ private MediaPlayer createMediaPlayer() {
+ return createMediaPlayer(new MediaPlayer());
+ }
+
+ private MediaPlayer createMediaPlayer(MediaPlayer mp) {
+ if (mp != null) {
+ mp.setOnPreparedListener(preparedListener);
+ mp.setOnCompletionListener(completionListener);
+ mp.setOnSeekCompleteListener(onSeekCompleteListener);
+ mp.setOnErrorListener(onErrorListener);
+ mp.setOnBufferingUpdateListener(onBufferingUpdateListener);
+ mp.setOnInfoListener(onInfoListener);
+ }
+ return mp;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Service is about to be destroyed");
+ isRunning = false;
+ if (chapterLoader != null) {
+ chapterLoader.interrupt();
+ }
+ disableSleepTimer();
+ unregisterReceiver(headsetDisconnected);
+ unregisterReceiver(shutdownReceiver);
+ unregisterReceiver(audioBecomingNoisy);
+ unregisterReceiver(skipCurrentEpisodeReceiver);
+ EventDistributor.getInstance().unregister(eventDistributorListener);
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ audioManager.unregisterRemoteControlClient(remoteControlClient);
+ }
+ audioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver);
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ player.release();
+ stopWidgetUpdater();
+ updateWidget();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received onBind event");
+ return mBinder;
+ }
+
+ private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() {
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EventDistributor.QUEUE_UPDATE & arg) != 0) {
+ loadQueue();
+ }
+ }
+ };
+
+ private final OnAudioFocusChangeListener audioFocusChangeListener = new OnAudioFocusChangeListener() {
+
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_LOSS:
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus");
+ pause(true, false);
+ stopSelf();
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN:
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Gained audio focus");
+ if (pausedBecauseOfTransientAudiofocusLoss) {
+ audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_RAISE, 0);
+ play();
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ if (status == PlayerStatus.PLAYING) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus temporarily. Ducking...");
+ audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_LOWER, 0);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ if (status == PlayerStatus.PLAYING) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus temporarily. Pausing...");
+ pause(false, false);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
+ }
+ }
+ };
+
+ /**
+ * 1. Check type of intent
+ * 1.1 Keycode -> handle keycode -> done
+ * 1.2 Playable -> Step 2
+ * 2. Handle playable
+ * 2.1 Check current status
+ * 2.1.1 Not playing -> play new playable
+ * 2.1.2 Playing, new playable is the same -> play if playback is currently paused
+ * 2.1.3 Playing, new playable different -> Stop playback of old media
+ *
+ * @param intent
+ * @param flags
+ * @param startId
+ * @return
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ super.onStartCommand(intent, flags, startId);
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "OnStartCommand called");
+ final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
+ final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
+ if (keycode == -1 && playable == null) {
+ Log.e(TAG, "PlaybackService was started with no arguments");
+ stopSelf();
+ }
+
+ if (keycode != -1) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received media button event");
+ handleKeycode(keycode);
+ } else {
+ boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
+ true);
+ if (media == null) {
+ media = playable;
+ shouldStream = playbackType;
+ startWhenPrepared = intent.getBooleanExtra(
+ EXTRA_START_WHEN_PREPARED, false);
+ initMediaplayer(intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false));
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
+ }
+ if (media != null) {
+ if (!playable.getIdentifier().equals(media.getIdentifier())) {
+ // different media or different playback type
+ pause(true, false);
+ player.reset();
+ media = playable;
+ shouldStream = playbackType;
+ startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
+ initMediaplayer(intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false));
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
+ } else {
+ // same media and same playback type
+ if (status == PlayerStatus.PAUSED) {
+ play();
+ }
+ }
+ }
+ }
+
+ return Service.START_NOT_STICKY;
+ }
/** Handles media button events */
private void handleKeycode(int keycode) {
@@ -444,163 +478,169 @@ public class PlaybackService extends Service {
}
}
- /**
- * Called by a mediaplayer Activity as soon as it has prepared its
- * mediaplayer.
- */
- public void setVideoSurface(SurfaceHolder sh) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting display");
- player.setDisplay(null);
- player.setDisplay(sh);
- if (status == PlayerStatus.STOPPED
- || status == PlayerStatus.AWAITING_VIDEO_SURFACE) {
- try {
- InitTask initTask = new InitTask() {
-
- @Override
- protected void onPostExecute(Playable result) {
- if (status == PlayerStatus.INITIALIZING) {
- if (result != null) {
- try {
- if (shouldStream) {
- player.setDataSource(media
- .getStreamUrl());
- setStatus(PlayerStatus.PREPARING);
- player.prepareAsync();
- } else {
- player.setDataSource(media
- .getLocalMediaUrl());
- setStatus(PlayerStatus.PREPARING);
- player.prepareAsync();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- } else {
- setStatus(PlayerStatus.ERROR);
- sendBroadcast(new Intent(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- }
- }
-
- @Override
- protected void onPreExecute() {
- setStatus(PlayerStatus.INITIALIZING);
- }
-
- };
- initTask.executeAsync(media);
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (SecurityException e) {
- e.printStackTrace();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- }
-
- }
-
- /** Called when the surface holder of the mediaplayer has to be changed. */
- private void resetVideoSurface() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Resetting video surface");
- cancelPositionSaver();
- player.setDisplay(null);
- player.reset();
- player.release();
- player = createMediaPlayer();
- status = PlayerStatus.STOPPED;
- if (media != null) {
- initMediaplayer();
- }
- }
+ /**
+ * Called by a mediaplayer Activity as soon as it has prepared its
+ * mediaplayer.
+ */
+ public void setVideoSurface(SurfaceHolder sh) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting display");
+ player.setDisplay(null);
+ player.setDisplay(sh);
+ if (status == PlayerStatus.STOPPED
+ || status == PlayerStatus.AWAITING_VIDEO_SURFACE) {
+ try {
+ InitTask initTask = new InitTask() {
+
+ @Override
+ protected void onPostExecute(Playable result) {
+ if (status == PlayerStatus.INITIALIZING) {
+ if (result != null) {
+ try {
+ if (shouldStream) {
+ player.setDataSource(media
+ .getStreamUrl());
+ setStatus(PlayerStatus.PREPARING);
+ player.prepareAsync();
+ } else {
+ player.setDataSource(media
+ .getLocalMediaUrl());
+ setStatus(PlayerStatus.PREPARING);
+ player.prepareAsync();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } else {
+ setStatus(PlayerStatus.ERROR);
+ sendBroadcast(new Intent(
+ ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ setStatus(PlayerStatus.INITIALIZING);
+ }
+
+ };
+ initTask.executeAsync(media);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ /**
+ * Called when the surface holder of the mediaplayer has to be changed.
+ */
+ private void resetVideoSurface() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Resetting video surface");
+ cancelPositionSaver();
+ player.setDisplay(null);
+ player.reset();
+ player.release();
+ player = createMediaPlayer();
+ status = PlayerStatus.STOPPED;
+ }
public void notifyVideoSurfaceAbandoned() {
resetVideoSurface();
+ if (media != null) {
+ initMediaplayer(true);
+ }
}
- /** Called after service has extracted the media it is supposed to play. */
- private void initMediaplayer() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting up media player");
- try {
- MediaType mediaType = media.getMediaType();
- if (mediaType == MediaType.AUDIO) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Mime type is audio");
-
- InitTask initTask = new InitTask() {
-
- @Override
- protected void onPostExecute(Playable result) {
- // check if state of service has changed. If it has
- // changed, assume that loaded metadata is not needed
- // anymore.
- if (status == PlayerStatus.INITIALIZING) {
- if (result != null) {
- playingVideo = false;
- try {
- if (shouldStream) {
- player.setDataSource(media
- .getStreamUrl());
- } else if (media.localFileAvailable()) {
- player.setDataSource(media
- .getLocalMediaUrl());
- }
-
- if (prepareImmediately) {
- setStatus(PlayerStatus.PREPARING);
- player.prepareAsync();
- } else {
- setStatus(PlayerStatus.INITIALIZED);
- }
- } catch (IOException e) {
- e.printStackTrace();
- media = null;
- setStatus(PlayerStatus.ERROR);
- sendBroadcast(new Intent(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- } else {
- Log.e(TAG, "InitTask could not load metadata");
- media = null;
- setStatus(PlayerStatus.ERROR);
- sendBroadcast(new Intent(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Status of player has changed during initialization. Stopping init process.");
- }
- }
-
- @Override
- protected void onPreExecute() {
- setStatus(PlayerStatus.INITIALIZING);
- }
-
- };
- initTask.executeAsync(media);
- } else if (mediaType == MediaType.VIDEO) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Mime type is video");
- playingVideo = true;
- setStatus(PlayerStatus.AWAITING_VIDEO_SURFACE);
- player.setScreenOnWhilePlaying(true);
- }
-
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (SecurityException e) {
- e.printStackTrace();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- }
+ /**
+ * Called after service has extracted the media it is supposed to play.
+ *
+ * @param prepareImmediately True if service should prepare playback after it has been initialized
+ */
+ private void initMediaplayer(final boolean prepareImmediately) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting up media player");
+ try {
+ MediaType mediaType = media.getMediaType();
+ if (mediaType == MediaType.AUDIO) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Mime type is audio");
+
+ InitTask initTask = new InitTask() {
+
+ @Override
+ protected void onPostExecute(Playable result) {
+ // check if state of service has changed. If it has
+ // changed, assume that loaded metadata is not needed
+ // anymore.
+ if (status == PlayerStatus.INITIALIZING) {
+ if (result != null) {
+ playingVideo = false;
+ try {
+ if (shouldStream) {
+ player.setDataSource(media
+ .getStreamUrl());
+ } else if (media.localFileAvailable()) {
+ player.setDataSource(media
+ .getLocalMediaUrl());
+ }
+
+ if (prepareImmediately) {
+ setStatus(PlayerStatus.PREPARING);
+ player.prepareAsync();
+ } else {
+ setStatus(PlayerStatus.INITIALIZED);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ media = null;
+ setStatus(PlayerStatus.ERROR);
+ sendBroadcast(new Intent(
+ ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ } else {
+ Log.e(TAG, "InitTask could not load metadata");
+ media = null;
+ setStatus(PlayerStatus.ERROR);
+ sendBroadcast(new Intent(
+ ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Status of player has changed during initialization. Stopping init process.");
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ setStatus(PlayerStatus.INITIALIZING);
+ }
+
+ };
+ initTask.executeAsync(media);
+ } else if (mediaType == MediaType.VIDEO) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Mime type is video");
+ playingVideo = true;
+ setStatus(PlayerStatus.AWAITING_VIDEO_SURFACE);
+ player.setScreenOnWhilePlaying(true);
+ }
+
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+ }
private void setupPositionSaver() {
if (positionSaverFuture == null
@@ -722,81 +762,83 @@ public class PlaybackService extends Service {
}
};
- private void endPlayback(boolean playNextEpisode) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Playback ended");
- audioManager.abandonAudioFocus(audioFocusChangeListener);
-
- // Save state
- cancelPositionSaver();
-
- boolean isInQueue = false;
- FeedItem nextItem = null;
-
- if (media instanceof FeedMedia) {
- FeedItem item = ((FeedMedia) media).getItem();
- ((FeedMedia) media).setPlaybackCompletionDate(new Date());
- manager.markItemRead(PlaybackService.this, item, true, true);
- nextItem = manager.getQueueSuccessorOfItem(item);
- isInQueue = media instanceof FeedMedia
- && manager.isInQueue(((FeedMedia) media).getItem());
- if (isInQueue) {
- manager.removeQueueItem(PlaybackService.this, item, true);
- }
- manager.addItemToPlaybackHistory(PlaybackService.this, item);
- manager.setFeedMedia(PlaybackService.this, (FeedMedia) media);
- long autoDeleteMediaId = ((FeedComponent) media).getId();
- if (shouldStream) {
- autoDeleteMediaId = -1;
- }
- }
-
- // Load next episode if previous episode was in the queue and if there
- // is an episode in the queue left.
- // Start playback immediately if continuous playback is enabled
- boolean loadNextItem = isInQueue && nextItem != null;
- playNextEpisode = playNextEpisode && loadNextItem
- && UserPreferences.isFollowQueue();
- if (loadNextItem) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Loading next item in queue");
- media = nextItem.getMedia();
- }
-
- if (playNextEpisode) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Playback of next episode will start immediately.");
- prepareImmediately = startWhenPrepared = true;
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "No more episodes available to play");
- media = null;
- prepareImmediately = startWhenPrepared = false;
- stopForeground(true);
- stopWidgetUpdater();
- }
-
- int notificationCode = 0;
- if (media != null) {
- shouldStream = !media.localFileAvailable();
- if (media.getMediaType() == MediaType.AUDIO) {
- notificationCode = EXTRA_CODE_AUDIO;
- playingVideo = false;
- } else if (media.getMediaType() == MediaType.VIDEO) {
- notificationCode = EXTRA_CODE_VIDEO;
- }
- }
- writePlaybackPreferences();
- if (media != null) {
- resetVideoSurface();
- refreshRemoteControlClientState();
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
- notificationCode);
- } else {
- sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
- stopSelf();
- }
- }
+ private void endPlayback(boolean playNextEpisode) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Playback ended");
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+
+ // Save state
+ cancelPositionSaver();
+
+ boolean isInQueue = false;
+ FeedItem nextItem = null;
+
+ if (media instanceof FeedMedia) {
+ FeedItem item = ((FeedMedia) media).getItem();
+ ((FeedMedia) media).setPlaybackCompletionDate(new Date());
+ DBWriter.markItemRead(PlaybackService.this, item, true, true);
+ nextItem = DBTasks.getQueueSuccessorOfItem(this, item.getId(), queue);
+ isInQueue = media instanceof FeedMedia
+ && QueueAccess.ItemListAccess(queue).contains(((FeedMedia) media).getItem().getId());
+ if (isInQueue) {
+ DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true);
+ }
+ DBWriter.addItemToPlaybackHistory(PlaybackService.this, item);
+ DBWriter.setFeedMedia(PlaybackService.this, (FeedMedia) media);
+ long autoDeleteMediaId = ((FeedComponent) media).getId();
+ if (shouldStream) {
+ autoDeleteMediaId = -1;
+ }
+ }
+
+ // Load next episode if previous episode was in the queue and if there
+ // is an episode in the queue left.
+ // Start playback immediately if continuous playback is enabled
+ boolean loadNextItem = isInQueue && nextItem != null;
+ playNextEpisode = playNextEpisode && loadNextItem
+ && UserPreferences.isFollowQueue();
+ if (loadNextItem) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading next item in queue");
+ media = nextItem.getMedia();
+ }
+ final boolean prepareImmediately;
+ if (playNextEpisode) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Playback of next episode will start immediately.");
+ prepareImmediately = startWhenPrepared = true;
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "No more episodes available to play");
+ media = null;
+ prepareImmediately = startWhenPrepared = false;
+ stopForeground(true);
+ stopWidgetUpdater();
+ }
+
+ int notificationCode = 0;
+ if (media != null) {
+ shouldStream = !media.localFileAvailable();
+ if (media.getMediaType() == MediaType.AUDIO) {
+ notificationCode = EXTRA_CODE_AUDIO;
+ playingVideo = false;
+ } else if (media.getMediaType() == MediaType.VIDEO) {
+ notificationCode = EXTRA_CODE_VIDEO;
+ }
+ }
+ writePlaybackPreferences();
+ if (media != null) {
+ resetVideoSurface();
+ refreshRemoteControlClientState();
+ initMediaplayer(prepareImmediately);
+
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
+ notificationCode);
+ } else {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
+ stopSelf();
+ }
+ }
public void setSleepTimer(long waitingTime) {
if (AppConfig.DEBUG)
@@ -825,7 +867,7 @@ public class PlaybackService extends Service {
*
* @param abandonFocus
* is true if the service should release audio focus
- * @param reset
+ * @param reinit
* is true if service should reinit after pausing if the media
* file is being streamed
*/
@@ -880,8 +922,7 @@ public class PlaybackService extends Service {
public void reinit() {
player.reset();
player = createMediaPlayer(player);
- prepareImmediately = false;
- initMediaplayer();
+ initMediaplayer(false);
}
@SuppressLint("NewApi")
@@ -1248,8 +1289,10 @@ public class PlaybackService extends Service {
i.putExtra("album", media.getFeedTitle());
i.putExtra("track", media.getEpisodeTitle());
i.putExtra("playing", isPlaying);
- i.putExtra("ListSize", manager.getQueueSize(false));
- i.putExtra("duration", media.getDuration());
+ if (queue != null) {
+ i.putExtra("ListSize", queue.size());
+ }
+ i.putExtra("duration", media.getDuration());
i.putExtra("position", media.getPosition());
sendBroadcast(i);
}
@@ -1529,4 +1572,16 @@ public class PlaybackService extends Service {
}
}
+
+ private void loadQueue() {
+ dbLoaderExecutor.submit(new QueueLoaderTask());
+ }
+
+ private class QueueLoaderTask implements Runnable {
+ @Override
+ public void run() {
+ List<FeedItem> queueRef = DBReader.getQueue(PlaybackService.this);
+ queue = queueRef;
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/service/download/DownloadRequest.java b/src/de/danoeh/antennapod/service/download/DownloadRequest.java
new file mode 100644
index 000000000..1f4e32e1b
--- /dev/null
+++ b/src/de/danoeh/antennapod/service/download/DownloadRequest.java
@@ -0,0 +1,177 @@
+package de.danoeh.antennapod.service.download;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class DownloadRequest implements Parcelable {
+
+ private final String destination;
+ private final String source;
+ private final String title;
+ private final long feedfileId;
+ private final int feedfileType;
+
+ protected int progressPercent;
+ protected long soFar;
+ protected long size;
+ protected int statusMsg;
+
+ public DownloadRequest(String destination, String source, String title,
+ long feedfileId, int feedfileType) {
+ if (destination == null) {
+ throw new IllegalArgumentException("Destination must not be null");
+ }
+ if (source == null) {
+ throw new IllegalArgumentException("Source must not be null");
+ }
+ if (title == null) {
+ throw new IllegalArgumentException("Title must not be null");
+ }
+
+ this.destination = destination;
+ this.source = source;
+ this.title = title;
+ this.feedfileId = feedfileId;
+ this.feedfileType = feedfileType;
+ }
+
+ private DownloadRequest(Parcel in) {
+ destination = in.readString();
+ source = in.readString();
+ title = in.readString();
+ feedfileId = in.readLong();
+ feedfileType = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(destination);
+ dest.writeString(source);
+ dest.writeString(title);
+ dest.writeLong(feedfileId);
+ dest.writeInt(feedfileType);
+ }
+
+ public static final Parcelable.Creator<DownloadRequest> CREATOR = new Parcelable.Creator<DownloadRequest>() {
+ public DownloadRequest createFromParcel(Parcel in) {
+ return new DownloadRequest(in);
+ }
+
+ public DownloadRequest[] newArray(int size) {
+ return new DownloadRequest[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((destination == null) ? 0 : destination.hashCode());
+ result = prime * result + (int) (feedfileId ^ (feedfileId >>> 32));
+ result = prime * result + feedfileType;
+ result = prime * result + progressPercent;
+ result = prime * result + (int) (size ^ (size >>> 32));
+ result = prime * result + (int) (soFar ^ (soFar >>> 32));
+ result = prime * result + ((source == null) ? 0 : source.hashCode());
+ result = prime * result + statusMsg;
+ result = prime * result + ((title == null) ? 0 : title.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DownloadRequest other = (DownloadRequest) obj;
+ if (destination == null) {
+ if (other.destination != null)
+ return false;
+ } else if (!destination.equals(other.destination))
+ return false;
+ if (feedfileId != other.feedfileId)
+ return false;
+ if (feedfileType != other.feedfileType)
+ return false;
+ if (progressPercent != other.progressPercent)
+ return false;
+ if (size != other.size)
+ return false;
+ if (soFar != other.soFar)
+ return false;
+ if (source == null) {
+ if (other.source != null)
+ return false;
+ } else if (!source.equals(other.source))
+ return false;
+ if (statusMsg != other.statusMsg)
+ return false;
+ if (title == null) {
+ if (other.title != null)
+ return false;
+ } else if (!title.equals(other.title))
+ return false;
+ return true;
+ }
+
+ public String getDestination() {
+ return destination;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public long getFeedfileId() {
+ return feedfileId;
+ }
+
+ public int getFeedfileType() {
+ return feedfileType;
+ }
+
+ public int getProgressPercent() {
+ return progressPercent;
+ }
+
+ public void setProgressPercent(int progressPercent) {
+ this.progressPercent = progressPercent;
+ }
+
+ public long getSoFar() {
+ return soFar;
+ }
+
+ public void setSoFar(long soFar) {
+ this.soFar = soFar;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ public int getStatusMsg() {
+ return statusMsg;
+ }
+
+ public void setStatusMsg(int statusMsg) {
+ this.statusMsg = statusMsg;
+ }
+}
diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java
index d6701c129..2056efab2 100644
--- a/src/de/danoeh/antennapod/service/download/DownloadService.java
+++ b/src/de/danoeh/antennapod/service/download/DownloadService.java
@@ -1,28 +1,17 @@
-/**
- * Registers a DownloadReceiver and waits for all Downloads
- * to complete, then stops
- * */
-
package de.danoeh.antennapod.service.download;
import java.io.File;
import java.io.IOException;
-import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.parsers.ParserConfigurationException;
+import de.danoeh.antennapod.storage.*;
import org.xml.sax.SAXException;
import android.annotation.SuppressLint;
@@ -41,8 +30,6 @@ import android.os.AsyncTask;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.webkit.URLUtil;
@@ -50,904 +37,888 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.DownloadActivity;
import de.danoeh.antennapod.activity.DownloadLogActivity;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedFile;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
-import de.danoeh.antennapod.storage.DownloadRequestException;
-import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.syndication.handler.FeedHandler;
import de.danoeh.antennapod.syndication.handler.UnsupportedFeedtypeException;
import de.danoeh.antennapod.util.ChapterUtils;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.InvalidFeedException;
+/**
+ * Manages the download of feedfiles in the app. Downloads can be enqueued viathe startService intent.
+ * The argument of the intent is an instance of DownloadRequest in the EXTRA_REQUEST field of
+ * the intent.
+ * After the downloads have finished, the downloaded object will be passed on to a specific handler, depending on the
+ * type of the feedfile.
+ */
public class DownloadService extends Service {
- private static final String TAG = "DownloadService";
-
- public static String ACTION_ALL_FEED_DOWNLOADS_COMPLETED = "action.de.danoeh.antennapod.storage.all_feed_downloads_completed";
-
- public static final String ACTION_ENQUEUE_DOWNLOAD = "action.de.danoeh.antennapod.service.enqueueDownload";
- public static final String ACTION_CANCEL_DOWNLOAD = "action.de.danoeh.antennapod.service.cancelDownload";
- public static final String ACTION_CANCEL_ALL_DOWNLOADS = "action.de.danoeh.antennapod.service.cancelAllDownloads";
-
- /** Extra for ACTION_CANCEL_DOWNLOAD */
- public static final String EXTRA_DOWNLOAD_URL = "downloadUrl";
-
- /**
- * Sent by the DownloadService when the content of the downloads list
- * changes.
- */
- public static final String ACTION_DOWNLOADS_CONTENT_CHANGED = "action.de.danoeh.antennapod.service.downloadsContentChanged";
-
- public static final String EXTRA_DOWNLOAD_ID = "extra.de.danoeh.antennapod.service.download_id";
-
- /** Extra for ACTION_ENQUEUE_DOWNLOAD intent. */
- public static final String EXTRA_REQUEST = "request";
-
- private CopyOnWriteArrayList<DownloadStatus> completedDownloads;
-
- private ExecutorService syncExecutor;
- private ExecutorService downloadExecutor;
- /** Number of threads of downloadExecutor. */
- private static final int NUM_PARALLEL_DOWNLOADS = 4;
-
- private DownloadRequester requester;
- private FeedManager manager;
- private NotificationCompat.Builder notificationCompatBuilder;
- private Notification.BigTextStyle notificationBuilder;
- private int NOTIFICATION_ID = 2;
- private int REPORT_ID = 3;
-
- private List<Downloader> downloads;
-
- /** Number of completed downloads which are currently being handled. */
- private volatile int downloadsBeingHandled;
-
- private volatile boolean shutdownInitiated = false;
- /** True if service is running. */
- public static boolean isRunning = false;
-
- private Handler handler;
-
- private NotificationUpdater notificationUpdater;
- private ScheduledFuture notificationUpdaterFuture;
- private static final int SCHED_EX_POOL_SIZE = 1;
- private ScheduledThreadPoolExecutor schedExecutor;
-
- private final IBinder mBinder = new LocalBinder();
-
- public class LocalBinder extends Binder {
- public DownloadService getService() {
- return DownloadService.this;
- }
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
- onDownloadQueued(intent);
- }
- return Service.START_NOT_STICKY;
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onCreate() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Service started");
- isRunning = true;
- handler = new Handler();
- completedDownloads = new CopyOnWriteArrayList<DownloadStatus>(
- new ArrayList<DownloadStatus>());
- downloads = new ArrayList<Downloader>();
- registerReceiver(downloadQueued, new IntentFilter(
- ACTION_ENQUEUE_DOWNLOAD));
-
- IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
- cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
- cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
- registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
- syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
-
- @Override
- public void uncaughtException(Thread thread, Throwable ex) {
- Log.e(TAG, "Thread exited with uncaught exception");
- ex.printStackTrace();
- downloadsBeingHandled -= 1;
- queryDownloads();
- }
- });
- return t;
- }
- });
- downloadExecutor = Executors.newFixedThreadPool(NUM_PARALLEL_DOWNLOADS,
- new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- });
- schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
- new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- }, new RejectedExecutionHandler() {
-
- @Override
- public void rejectedExecution(Runnable r,
- ThreadPoolExecutor executor) {
- Log.w(TAG, "SchedEx rejected submission of new task");
- }
- });
- setupNotificationBuilders();
- manager = FeedManager.getInstance();
- requester = DownloadRequester.getInstance();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- @Override
- public void onDestroy() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Service shutting down");
- isRunning = false;
- unregisterReceiver(cancelDownloadReceiver);
- unregisterReceiver(downloadQueued);
- }
-
- @SuppressLint("NewApi")
- private void setupNotificationBuilders() {
- PendingIntent pIntent = PendingIntent.getActivity(this, 0, new Intent(
- this, DownloadActivity.class),
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- Bitmap icon = BitmapFactory.decodeResource(getResources(),
- R.drawable.stat_notify_sync);
-
- if (android.os.Build.VERSION.SDK_INT >= 16) {
- notificationBuilder = new Notification.BigTextStyle(
- new Notification.Builder(this).setOngoing(true)
- .setContentIntent(pIntent).setLargeIcon(icon)
- .setSmallIcon(R.drawable.stat_notify_sync));
- } else {
- notificationCompatBuilder = new NotificationCompat.Builder(this)
- .setOngoing(true).setContentIntent(pIntent)
- .setLargeIcon(icon)
- .setSmallIcon(R.drawable.stat_notify_sync);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Notification set up");
- }
-
- /**
- * Updates the contents of the service's notifications. Should be called
- * before setupNotificationBuilders.
- */
- @SuppressLint("NewApi")
- private Notification updateNotifications() {
- String contentTitle = getString(R.string.download_notification_title);
- String downloadsLeft = requester.getNumberOfDownloads()
- + getString(R.string.downloads_left);
- if (android.os.Build.VERSION.SDK_INT >= 16) {
-
- if (notificationBuilder != null) {
-
- StringBuilder bigText = new StringBuilder("");
- for (int i = 0; i < downloads.size(); i++) {
- Downloader downloader = downloads.get(i);
- if (downloader.getStatus() != null) {
- FeedFile f = downloader.getStatus().getFeedFile();
- if (f.getClass() == Feed.class) {
- Feed feed = (Feed) f;
- if (feed.getTitle() != null) {
- if (i > 0) {
- bigText.append("\n");
- }
- bigText.append("\u2022 " + feed.getTitle());
- }
- } else if (f.getClass() == FeedMedia.class) {
- FeedMedia media = (FeedMedia) f;
- if (media.getItem().getTitle() != null) {
- if (i > 0) {
- bigText.append("\n");
- }
- bigText.append("\u2022 "
- + media.getItem().getTitle()
- + " ("
- + downloader.getStatus()
- .getProgressPercent() + "%)");
- }
- }
- }
- }
- notificationBuilder.setSummaryText(downloadsLeft);
- notificationBuilder.setBigContentTitle(contentTitle);
- if (bigText != null) {
- notificationBuilder.bigText(bigText.toString());
- }
- return notificationBuilder.build();
- }
- } else {
- if (notificationCompatBuilder != null) {
- notificationCompatBuilder.setContentTitle(contentTitle);
- notificationCompatBuilder.setContentText(downloadsLeft);
- return notificationCompatBuilder.getNotification();
- }
- }
- return null;
- }
-
- private Downloader getDownloader(String downloadUrl) {
- for (Downloader downloader : downloads) {
- if (downloader.getStatus().getFeedFile().getDownload_url()
- .equals(downloadUrl)) {
- return downloader;
- }
- }
- return null;
- }
-
- private BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(ACTION_CANCEL_DOWNLOAD)) {
- String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
- if (url == null) {
- throw new IllegalArgumentException(
- "ACTION_CANCEL_DOWNLOAD intent needs download url extra");
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cancelling download with url " + url);
- Downloader d = getDownloader(url);
- if (d != null) {
- d.cancel();
- removeDownload(d);
- } else {
- Log.e(TAG, "Could not cancel download with url " + url);
- }
-
- } else if (intent.getAction().equals(ACTION_CANCEL_ALL_DOWNLOADS)) {
- for (Downloader d : downloads) {
- d.cancel();
- DownloadRequester.getInstance().removeDownload(
- d.getStatus().getFeedFile());
- d.getStatus().getFeedFile().setFile_url(null);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cancelled all downloads");
- }
- downloads.clear();
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
-
- }
- queryDownloads();
- }
-
- };
-
- private void onDownloadQueued(Intent intent) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received enqueue request");
- Request request = intent.getParcelableExtra(EXTRA_REQUEST);
- if (request == null) {
- throw new IllegalArgumentException(
- "ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
- }
- if (shutdownInitiated) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cancelling shutdown; new download was queued");
- shutdownInitiated = false;
- }
-
- DownloadRequester requester = DownloadRequester.getInstance();
- FeedFile feedfile = requester.getDownload(request.source);
- if (feedfile != null) {
-
- DownloadStatus status = new DownloadStatus(feedfile,
- feedfile.getHumanReadableIdentifier());
- Downloader downloader = getDownloader(status);
- if (downloader != null) {
- downloads.add(downloader);
- downloadExecutor.submit(downloader);
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
- }
- } else {
- Log.e(TAG,
- "Could not find feedfile in download requester when trying to enqueue new download");
- }
- queryDownloads();
- }
-
- private BroadcastReceiver downloadQueued = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- onDownloadQueued(intent);
- }
-
- };
-
- private Downloader getDownloader(DownloadStatus status) {
- if (URLUtil.isHttpUrl(status.getFeedFile().getDownload_url())) {
- return new HttpDownloader(new DownloaderCallback() {
-
- @Override
- public void onDownloadCompleted(final Downloader downloader) {
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- DownloadService.this
- .onDownloadCompleted(downloader);
- }
- });
- }
- }, status);
- }
- Log.e(TAG, "Could not find appropriate downloader for "
- + status.getFeedFile().getDownload_url());
- return null;
- }
-
- @SuppressLint("NewApi")
- public void onDownloadCompleted(final Downloader downloader) {
- final AsyncTask<Void, Void, Void> handlerTask = new AsyncTask<Void, Void, Void>() {
- boolean successful;
-
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- if (!successful) {
- queryDownloads();
- }
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- removeDownload(downloader);
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received 'Download Complete' - message.");
- downloadsBeingHandled += 1;
- DownloadStatus status = downloader.getStatus();
- status.setCompletionDate(new Date());
- successful = status.isSuccessful();
-
- FeedFile download = status.getFeedFile();
- if (download != null) {
- if (successful) {
- if (download.getClass() == Feed.class) {
- handleCompletedFeedDownload(status);
- } else if (download.getClass() == FeedImage.class) {
- handleCompletedImageDownload(status);
- } else if (download.getClass() == FeedMedia.class) {
- handleCompletedFeedMediaDownload(status);
- }
- } else {
- download.setFile_url(null);
- download.setDownloaded(false);
- if (!successful && !status.isCancelled()) {
- Log.e(TAG, "Download failed");
- saveDownloadStatus(status);
- }
- sendDownloadHandledIntent();
- downloadsBeingHandled -= 1;
- }
- }
- return null;
- }
- };
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- handlerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- handlerTask.execute();
- }
- }
-
- /**
- * Remove download from the DownloadRequester list and from the
- * DownloadService list.
- */
- private void removeDownload(final Downloader d) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Removing downloader: "
- + d.getStatus().getFeedFile().getDownload_url());
- boolean rc = downloads.remove(d);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Result of downloads.remove: " + rc);
- DownloadRequester.getInstance().removeDownload(
- d.getStatus().getFeedFile());
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
- }
-
- /**
- * Adds a new DownloadStatus object to the list of completed downloads and
- * saves it in the database
- *
- * @param status
- * the download that is going to be saved
- */
- private void saveDownloadStatus(DownloadStatus status) {
- completedDownloads.add(status);
- manager.addDownloadStatus(this, status);
- }
-
- private void sendDownloadHandledIntent() {
- EventDistributor.getInstance().sendDownloadHandledBroadcast();
- }
-
- /**
- * Creates a notification at the end of the service lifecycle to notify the
- * user about the number of completed downloads. A report will only be
- * created if the number of successfully downloaded feeds is bigger than 1
- * or if there is at least one failed download which is not an image or if
- * there is at least one downloaded media file.
- */
- private void updateReport() {
- // check if report should be created
- boolean createReport = false;
- int successfulDownloads = 0;
- int failedDownloads = 0;
-
- // a download report is created if at least one download has failed
- // (excluding failed image downloads)
- for (DownloadStatus status : completedDownloads) {
- if (status.isSuccessful()) {
- successfulDownloads++;
- } else if (!status.isCancelled()) {
- if (status.getFeedFile().getClass() != FeedImage.class) {
- createReport = true;
- }
- failedDownloads++;
- }
- }
-
- if (createReport) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating report");
- // create notification object
- Notification notification = new NotificationCompat.Builder(this)
- .setTicker(
- getString(de.danoeh.antennapod.R.string.download_report_title))
- .setContentTitle(
- getString(de.danoeh.antennapod.R.string.download_report_title))
- .setContentText(
- String.format(
- getString(R.string.download_report_content),
- successfulDownloads, failedDownloads))
- .setSmallIcon(R.drawable.stat_notify_sync)
- .setLargeIcon(
- BitmapFactory.decodeResource(getResources(),
- R.drawable.stat_notify_sync))
- .setContentIntent(
- PendingIntent.getActivity(this, 0, new Intent(this,
- DownloadLogActivity.class), 0))
- .setAutoCancel(true).getNotification();
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(REPORT_ID, notification);
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "No report is created");
- }
- completedDownloads.clear();
- }
-
- /** Check if there's something else to download, otherwise stop */
- void queryDownloads() {
- int numOfDownloads = downloads.size();
- if (AppConfig.DEBUG) {
- Log.d(TAG, numOfDownloads + " downloads left");
- Log.d(TAG, "Downloads being handled: " + downloadsBeingHandled);
- Log.d(TAG, "ShutdownInitiated: " + shutdownInitiated);
- }
-
- if (numOfDownloads == 0 && downloadsBeingHandled <= 0) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Starting shutdown");
- shutdownInitiated = true;
- updateReport();
- cancelNotificationUpdater();
- stopForeground(true);
- } else {
- setupNotificationUpdater();
- startForeground(NOTIFICATION_ID, updateNotifications());
- }
- }
-
- /** Is called whenever a Feed is downloaded */
- private void handleCompletedFeedDownload(DownloadStatus status) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Handling completed Feed Download");
- syncExecutor.execute(new FeedSyncThread(status));
-
- }
-
- /** Is called whenever a Feed-Image is downloaded */
- private void handleCompletedImageDownload(DownloadStatus status) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Handling completed Image Download");
- syncExecutor.execute(new ImageHandlerThread(status));
- }
-
- /** Is called whenever a FeedMedia is downloaded. */
- private void handleCompletedFeedMediaDownload(DownloadStatus status) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Handling completed FeedMedia Download");
- syncExecutor.execute(new MediaHandlerThread(status));
- }
-
- /**
- * Takes a single Feed, parses the corresponding file and refreshes
- * information in the manager
- */
- class FeedSyncThread implements Runnable {
- private static final String TAG = "FeedSyncThread";
-
- private Feed feed;
- private DownloadStatus status;
-
- private DownloadError reason;
- private boolean successful;
-
- public FeedSyncThread(DownloadStatus status) {
- this.feed = (Feed) status.getFeedFile();
- this.status = status;
- }
-
- public void run() {
- Feed savedFeed = null;
- reason = DownloadError.SUCCESS;
- String reasonDetailed = null;
- successful = true;
- final FeedManager manager = FeedManager.getInstance();
- FeedHandler feedHandler = new FeedHandler();
- feed.setDownloaded(true);
-
- try {
- feed = feedHandler.parseFeed(feed);
- if (AppConfig.DEBUG)
- Log.d(TAG, feed.getTitle() + " parsed");
- if (checkFeedData(feed) == false) {
- throw new InvalidFeedException();
- }
- // Save information of feed in DB
- savedFeed = manager.updateFeed(DownloadService.this, feed);
- // Download Feed Image if provided and not downloaded
- if (savedFeed.getImage() != null
- && savedFeed.getImage().isDownloaded() == false) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Feed has image; Downloading....");
- savedFeed.getImage().setFeed(savedFeed);
- final Feed savedFeedRef = savedFeed;
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- try {
- requester.downloadImage(DownloadService.this,
- savedFeedRef.getImage());
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- manager.addDownloadStatus(
- DownloadService.this,
- new DownloadStatus(
- savedFeedRef.getImage(),
- savedFeedRef
- .getImage()
- .getHumanReadableIdentifier(),
- DownloadError.ERROR_REQUEST_ERROR,
- false, e.getMessage()));
- }
- }
- });
-
- }
-
- } catch (SAXException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (IOException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (ParserConfigurationException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (UnsupportedFeedtypeException e) {
- e.printStackTrace();
- successful = false;
- reason = DownloadError.ERROR_UNSUPPORTED_TYPE;
- reasonDetailed = e.getMessage();
- } catch (InvalidFeedException e) {
- e.printStackTrace();
- successful = false;
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- }
-
- // cleanup();
- if (savedFeed == null) {
- savedFeed = feed;
- }
-
- saveDownloadStatus(new DownloadStatus(savedFeed,
- savedFeed.getHumanReadableIdentifier(), reason, successful,
- reasonDetailed));
- sendDownloadHandledIntent();
- downloadsBeingHandled -= 1;
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- queryDownloads();
-
- }
- });
- }
-
- /** Checks if the feed was parsed correctly. */
- private boolean checkFeedData(Feed feed) {
- if (feed.getTitle() == null) {
- Log.e(TAG, "Feed has no title.");
- return false;
- }
- if (!hasValidFeedItems(feed)) {
- Log.e(TAG, "Feed has invalid items");
- return false;
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Feed appears to be valid.");
- return true;
-
- }
-
- private boolean hasValidFeedItems(Feed feed) {
- for (FeedItem item : feed.getItemsArray()) {
- if (item.getTitle() == null) {
- Log.e(TAG, "Item has no title");
- return false;
- }
- if (item.getPubDate() == null) {
- Log.e(TAG,
- "Item has no pubDate. Using current time as pubDate");
- if (item.getTitle() != null) {
- Log.e(TAG, "Title of invalid item: " + item.getTitle());
- }
- item.setPubDate(new Date());
- }
- }
- return true;
- }
-
- /** Delete files that aren't needed anymore */
- private void cleanup() {
- if (feed.getFile_url() != null) {
- if (new File(feed.getFile_url()).delete())
- if (AppConfig.DEBUG)
- Log.d(TAG, "Successfully deleted cache file.");
- else
- Log.e(TAG, "Failed to delete cache file.");
- feed.setFile_url(null);
- } else if (AppConfig.DEBUG) {
- Log.d(TAG, "Didn't delete cache file: File url is not set.");
- }
- }
-
- }
-
- /** Handles a completed image download. */
- class ImageHandlerThread implements Runnable {
- private FeedImage image;
- private DownloadStatus status;
-
- public ImageHandlerThread(DownloadStatus status) {
- this.image = (FeedImage) status.getFeedFile();
- this.status = status;
- }
-
- @Override
- public void run() {
- image.setDownloaded(true);
-
- saveDownloadStatus(status);
- sendDownloadHandledIntent();
- manager.setFeedImage(DownloadService.this, image);
- if (image.getFeed() != null) {
- manager.setFeed(DownloadService.this, image.getFeed());
- } else {
- Log.e(TAG,
- "Image has no feed, image might not be saved correctly!");
- }
- downloadsBeingHandled -= 1;
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- queryDownloads();
-
- }
- });
- }
- }
-
- /** Handles a completed media download. */
- class MediaHandlerThread implements Runnable {
- private FeedMedia media;
- private DownloadStatus status;
-
- public MediaHandlerThread(DownloadStatus status) {
- super();
- this.media = (FeedMedia) status.getFeedFile();
- this.status = status;
- }
-
- @Override
- public void run() {
- boolean chaptersRead = false;
-
- media.setDownloaded(true);
- // Get duration
- MediaPlayer mediaplayer = new MediaPlayer();
- try {
- mediaplayer.setDataSource(media.getFile_url());
- mediaplayer.prepare();
- media.setDuration(mediaplayer.getDuration());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Duration of file is " + media.getDuration());
- mediaplayer.reset();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- mediaplayer.release();
- }
-
- if (media.getItem().getChapters() == null) {
- ChapterUtils.loadChaptersFromFileUrl(media);
- if (media.getItem().getChapters() != null) {
- chaptersRead = true;
- }
- }
-
- saveDownloadStatus(status);
- sendDownloadHandledIntent();
- if (chaptersRead) {
- manager.setFeedItem(DownloadService.this, media.getItem());
- }
- manager.setFeedMedia(DownloadService.this, media);
-
- if (!FeedManager.getInstance().isInQueue(media.getItem())) {
- FeedManager.getInstance().addQueueItem(DownloadService.this,
- media.getItem());
- }
-
- downloadsBeingHandled -= 1;
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- queryDownloads();
-
- }
- });
- }
- }
-
- /** Is used to request a new download. */
- public static class Request implements Parcelable {
- private String destination;
- private String source;
-
- public Request(String destination, String source) {
- super();
- this.destination = destination;
- this.source = source;
- }
-
- private Request(Parcel in) {
- destination = in.readString();
- source = in.readString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(destination);
- dest.writeString(source);
- }
-
- public static final Parcelable.Creator<Request> CREATOR = new Parcelable.Creator<Request>() {
- public Request createFromParcel(Parcel in) {
- return new Request(in);
- }
-
- public Request[] newArray(int size) {
- return new Request[size];
- }
- };
-
- public String getDestination() {
- return destination;
- }
-
- public String getSource() {
- return source;
- }
-
- }
-
- /** Schedules the notification updater task if it hasn't been scheduled yet. */
- private void setupNotificationUpdater() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting up notification updater");
- if (notificationUpdater == null) {
- notificationUpdater = new NotificationUpdater();
- notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(
- notificationUpdater, 5L, 5L, TimeUnit.SECONDS);
- }
- }
-
- private void cancelNotificationUpdater() {
- boolean result = false;
- if (notificationUpdaterFuture != null) {
- result = notificationUpdaterFuture.cancel(true);
- }
- notificationUpdater = null;
- notificationUpdaterFuture = null;
- Log.d(TAG, "NotificationUpdater cancelled. Result: " + result);
- }
-
- private class NotificationUpdater implements Runnable {
- public void run() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- Notification n = updateNotifications();
- if (n != null) {
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(NOTIFICATION_ID, n);
- }
- }
- });
- }
- }
-
- public List<Downloader> getDownloads() {
- return downloads;
- }
+ private static final String TAG = "DownloadService";
+
+ /**
+ * Cancels one download. The intent MUST have an EXTRA_DOWNLOAD_URL extra that contains the download URL of the
+ * object whose download should be cancelled.
+ */
+ public static final String ACTION_CANCEL_DOWNLOAD = "action.de.danoeh.antennapod.service.cancelDownload";
+
+ /**
+ * Cancels all running downloads.
+ */
+ public static final String ACTION_CANCEL_ALL_DOWNLOADS = "action.de.danoeh.antennapod.service.cancelAllDownloads";
+
+ /**
+ * Extra for ACTION_CANCEL_DOWNLOAD
+ */
+ public static final String EXTRA_DOWNLOAD_URL = "downloadUrl";
+
+ /**
+ * Sent by the DownloadService when the content of the downloads list
+ * changes.
+ */
+ public static final String ACTION_DOWNLOADS_CONTENT_CHANGED = "action.de.danoeh.antennapod.service.downloadsContentChanged";
+
+ /**
+ * Extra for ACTION_ENQUEUE_DOWNLOAD intent.
+ */
+ public static final String EXTRA_REQUEST = "request";
+
+ /**
+ * Stores DownloadStatus objects of completed downloads for creating a report at the end of the lifecylce.
+ */
+ private List<DownloadStatus> completedDownloads;
+
+ private ExecutorService syncExecutor;
+ private CompletionService<Downloader> downloadExecutor;
+ /**
+ * Number of threads of downloadExecutor.
+ */
+ private static final int NUM_PARALLEL_DOWNLOADS = 4;
+
+ private DownloadRequester requester;
+
+
+ private NotificationCompat.Builder notificationCompatBuilder;
+ private Notification.BigTextStyle notificationBuilder;
+ private int NOTIFICATION_ID = 2;
+ private int REPORT_ID = 3;
+
+ /**
+ * Currently running downloads.
+ */
+ private List<Downloader> downloads;
+
+ /**
+ * Number of running downloads.
+ */
+ private AtomicInteger numberOfDownloads;
+
+ /**
+ * True if service is running.
+ */
+ public static boolean isRunning = false;
+
+ private Handler handler;
+
+ private NotificationUpdater notificationUpdater;
+ private ScheduledFuture notificationUpdaterFuture;
+ private static final int SCHED_EX_POOL_SIZE = 1;
+ private ScheduledThreadPoolExecutor schedExecutor;
+
+ private final IBinder mBinder = new LocalBinder();
+
+ public class LocalBinder extends Binder {
+ public DownloadService getService() {
+ return DownloadService.this;
+ }
+ }
+
+ private Thread downloadCompletionThread = new Thread() {
+ private static final String TAG = "downloadCompletionThread";
+
+ @Override
+ public void run() {
+ if (AppConfig.DEBUG) Log.d(TAG, "downloadCompletionThread was started");
+ while (!isInterrupted()) {
+ try {
+ Downloader downloader = downloadExecutor.take().get();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received 'Download Complete' - message.");
+ removeDownload(downloader);
+ DownloadStatus status = downloader.getResult();
+ boolean successful = status.isSuccessful();
+
+ final int type = status.getFeedfileType();
+ if (successful) {
+ if (type == Feed.FEEDFILETYPE_FEED) {
+ handleCompletedFeedDownload(downloader
+ .getDownloadRequest());
+ } else if (type == FeedImage.FEEDFILETYPE_FEEDIMAGE) {
+ handleCompletedImageDownload(status, downloader.getDownloadRequest());
+ } else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ handleCompletedFeedMediaDownload(status, downloader.getDownloadRequest());
+ }
+ } else {
+ numberOfDownloads.decrementAndGet();
+ if (!successful && !status.isCancelled()) {
+ Log.e(TAG, "Download failed");
+ saveDownloadStatus(status);
+ }
+ sendDownloadHandledIntent();
+ queryDownloadsAsync();
+ }
+ } catch (InterruptedException e) {
+ if (AppConfig.DEBUG) Log.d(TAG, "DownloadCompletionThread was interrupted");
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ numberOfDownloads.decrementAndGet();
+ }
+ }
+ if (AppConfig.DEBUG) Log.d(TAG, "End of downloadCompletionThread");
+ }
+ };
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
+ onDownloadQueued(intent);
+ } else if (numberOfDownloads.equals(0)) {
+ stopSelf();
+ }
+ return Service.START_NOT_STICKY;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onCreate() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Service started");
+ isRunning = true;
+ handler = new Handler();
+ completedDownloads = Collections.synchronizedList(new ArrayList<DownloadStatus>());
+ downloads = new ArrayList<Downloader>();
+ numberOfDownloads = new AtomicInteger(0);
+
+ IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
+ cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
+ cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
+ registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
+ syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ });
+ downloadExecutor = new ExecutorCompletionService<Downloader>(
+ Executors.newFixedThreadPool(NUM_PARALLEL_DOWNLOADS,
+ new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ }));
+ schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
+ new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ }, new RejectedExecutionHandler() {
+
+ @Override
+ public void rejectedExecution(Runnable r,
+ ThreadPoolExecutor executor) {
+ Log.w(TAG, "SchedEx rejected submission of new task");
+ }
+ }
+ );
+ downloadCompletionThread.start();
+ setupNotificationBuilders();
+ requester = DownloadRequester.getInstance();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Service shutting down");
+ isRunning = false;
+ updateReport();
+
+ stopForeground(true);
+ NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(NOTIFICATION_ID);
+
+ downloadCompletionThread.interrupt();
+ syncExecutor.shutdown();
+ schedExecutor.shutdown();
+ cancelNotificationUpdater();
+ unregisterReceiver(cancelDownloadReceiver);
+ }
+
+ @SuppressLint("NewApi")
+ private void setupNotificationBuilders() {
+ PendingIntent pIntent = PendingIntent.getActivity(this, 0, new Intent(
+ this, DownloadActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ Bitmap icon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.stat_notify_sync);
+
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
+ notificationBuilder = new Notification.BigTextStyle(
+ new Notification.Builder(this).setOngoing(true)
+ .setContentIntent(pIntent).setLargeIcon(icon)
+ .setSmallIcon(R.drawable.stat_notify_sync));
+ } else {
+ notificationCompatBuilder = new NotificationCompat.Builder(this)
+ .setOngoing(true).setContentIntent(pIntent)
+ .setLargeIcon(icon)
+ .setSmallIcon(R.drawable.stat_notify_sync);
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Notification set up");
+ }
+
+ /**
+ * Updates the contents of the service's notifications. Should be called
+ * before setupNotificationBuilders.
+ */
+ @SuppressLint("NewApi")
+ private Notification updateNotifications() {
+ String contentTitle = getString(R.string.download_notification_title);
+ String downloadsLeft = requester.getNumberOfDownloads()
+ + getString(R.string.downloads_left);
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
+
+ if (notificationBuilder != null) {
+
+ StringBuilder bigText = new StringBuilder("");
+ for (int i = 0; i < downloads.size(); i++) {
+ Downloader downloader = downloads.get(i);
+ final DownloadRequest request = downloader
+ .getDownloadRequest();
+ if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
+ if (request.getTitle() != null) {
+ if (i > 0) {
+ bigText.append("\n");
+ }
+ bigText.append("\u2022 " + request.getTitle());
+ }
+ } else if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ if (request.getTitle() != null) {
+ if (i > 0) {
+ bigText.append("\n");
+ }
+ bigText.append("\u2022 " + request.getTitle()
+ + " (" + request.getProgressPercent()
+ + "%)");
+ }
+ }
+
+ }
+ notificationBuilder.setSummaryText(downloadsLeft);
+ notificationBuilder.setBigContentTitle(contentTitle);
+ if (bigText != null) {
+ notificationBuilder.bigText(bigText.toString());
+ }
+ return notificationBuilder.build();
+ }
+ } else {
+ if (notificationCompatBuilder != null) {
+ notificationCompatBuilder.setContentTitle(contentTitle);
+ notificationCompatBuilder.setContentText(downloadsLeft);
+ return notificationCompatBuilder.getNotification();
+ }
+ }
+ return null;
+ }
+
+ private Downloader getDownloader(String downloadUrl) {
+ for (Downloader downloader : downloads) {
+ if (downloader.getDownloadRequest().getSource().equals(downloadUrl)) {
+ return downloader;
+ }
+ }
+ return null;
+ }
+
+ private BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(ACTION_CANCEL_DOWNLOAD)) {
+ String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
+ if (url == null) {
+ throw new IllegalArgumentException(
+ "ACTION_CANCEL_DOWNLOAD intent needs download url extra");
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Cancelling download with url " + url);
+ Downloader d = getDownloader(url);
+ if (d != null) {
+ d.cancel();
+ } else {
+ Log.e(TAG, "Could not cancel download with url " + url);
+ }
+
+ } else if (intent.getAction().equals(ACTION_CANCEL_ALL_DOWNLOADS)) {
+ for (Downloader d : downloads) {
+ d.cancel();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Cancelled all downloads");
+ }
+ sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+
+ }
+ queryDownloads();
+ }
+
+ };
+
+ private void onDownloadQueued(Intent intent) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received enqueue request");
+ DownloadRequest request = intent.getParcelableExtra(EXTRA_REQUEST);
+ if (request == null) {
+ throw new IllegalArgumentException(
+ "ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
+ }
+
+ Downloader downloader = getDownloader(request);
+ if (downloader != null) {
+ numberOfDownloads.incrementAndGet();
+ downloads.add(downloader);
+ downloadExecutor.submit(downloader);
+ sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+ }
+
+ queryDownloads();
+ }
+
+ private Downloader getDownloader(DownloadRequest request) {
+ if (URLUtil.isHttpUrl(request.getSource())) {
+ return new HttpDownloader(request);
+ }
+ Log.e(TAG,
+ "Could not find appropriate downloader for "
+ + request.getSource());
+ return null;
+ }
+
+ @SuppressLint("NewApi")
+ public void onDownloadCompleted(final Downloader downloader) {
+ final AsyncTask<Void, Void, Void> handlerTask = new AsyncTask<Void, Void, Void>() {
+ boolean successful;
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ if (!successful) {
+ queryDownloads();
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ removeDownload(downloader);
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+
+
+ return null;
+ }
+ };
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ handlerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ handlerTask.execute();
+ }
+ }
+
+ /**
+ * Remove download from the DownloadRequester list and from the
+ * DownloadService list.
+ */
+ private void removeDownload(final Downloader d) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Removing downloader: "
+ + d.getDownloadRequest().getSource());
+ boolean rc = downloads.remove(d);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Result of downloads.remove: " + rc);
+ DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
+ sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+ }
+
+ /**
+ * Adds a new DownloadStatus object to the list of completed downloads and
+ * saves it in the database
+ *
+ * @param status the download that is going to be saved
+ */
+ private void saveDownloadStatus(DownloadStatus status) {
+ completedDownloads.add(status);
+ DBWriter.addDownloadStatus(this, status);
+ }
+
+ private void sendDownloadHandledIntent() {
+ EventDistributor.getInstance().sendDownloadHandledBroadcast();
+ }
+
+ /**
+ * Creates a notification at the end of the service lifecycle to notify the
+ * user about the number of completed downloads. A report will only be
+ * created if the number of successfully downloaded feeds is bigger than 1
+ * or if there is at least one failed download which is not an image or if
+ * there is at least one downloaded media file.
+ */
+ private void updateReport() {
+ // check if report should be created
+ boolean createReport = false;
+ int successfulDownloads = 0;
+ int failedDownloads = 0;
+
+ // a download report is created if at least one download has failed
+ // (excluding failed image downloads)
+ for (DownloadStatus status : completedDownloads) {
+ if (status.isSuccessful()) {
+ successfulDownloads++;
+ } else if (!status.isCancelled()) {
+ if (status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
+ createReport = true;
+ }
+ failedDownloads++;
+ }
+ }
+
+ if (createReport) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating report");
+ // create notification object
+ Notification notification = new NotificationCompat.Builder(this)
+ .setTicker(
+ getString(de.danoeh.antennapod.R.string.download_report_title))
+ .setContentTitle(
+ getString(de.danoeh.antennapod.R.string.download_report_title))
+ .setContentText(
+ String.format(
+ getString(R.string.download_report_content),
+ successfulDownloads, failedDownloads))
+ .setSmallIcon(R.drawable.stat_notify_sync)
+ .setLargeIcon(
+ BitmapFactory.decodeResource(getResources(),
+ R.drawable.stat_notify_sync))
+ .setContentIntent(
+ PendingIntent.getActivity(this, 0, new Intent(this,
+ DownloadLogActivity.class), 0))
+ .setAutoCancel(true).getNotification();
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(REPORT_ID, notification);
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "No report is created");
+ }
+ completedDownloads.clear();
+ }
+
+ /**
+ * Calls query downloads on the services main thread. This method should be used instead of queryDownloads if it is
+ * used from a thread other than the main thread.
+ */
+ void queryDownloadsAsync() {
+ handler.post(new Runnable() {
+ public void run() {
+ queryDownloads();
+ ;
+ }
+ });
+ }
+
+ /**
+ * Check if there's something else to download, otherwise stop
+ */
+ void queryDownloads() {
+ if (AppConfig.DEBUG) {
+ Log.d(TAG, numberOfDownloads.get() + " downloads left");
+ }
+
+ if (numberOfDownloads.get() <= 0) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
+ stopSelf();
+ } else {
+ setupNotificationUpdater();
+ startForeground(NOTIFICATION_ID, updateNotifications());
+ }
+ }
+
+ /**
+ * Is called whenever a Feed is downloaded
+ */
+ private void handleCompletedFeedDownload(DownloadRequest request) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Handling completed Feed Download");
+ syncExecutor.execute(new FeedSyncThread(request));
+
+ }
+
+ /**
+ * Is called whenever a Feed-Image is downloaded
+ */
+ private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Handling completed Image Download");
+ syncExecutor.execute(new ImageHandlerThread(status, request));
+ }
+
+ /**
+ * Is called whenever a FeedMedia is downloaded.
+ */
+ private void handleCompletedFeedMediaDownload(DownloadStatus status, DownloadRequest request) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Handling completed FeedMedia Download");
+ syncExecutor.execute(new MediaHandlerThread(status, request));
+ }
+
+ /**
+ * Takes a single Feed, parses the corresponding file and refreshes
+ * information in the manager
+ */
+ class FeedSyncThread implements Runnable {
+ private static final String TAG = "FeedSyncThread";
+
+ private DownloadRequest request;
+
+ private DownloadError reason;
+ private boolean successful;
+
+ public FeedSyncThread(DownloadRequest request) {
+ if (request == null) {
+ throw new IllegalArgumentException("Request must not be null");
+ }
+
+ this.request = request;
+ }
+
+ public void run() {
+ Feed savedFeed = null;
+
+ Feed feed = new Feed(request.getSource(), new Date());
+ feed.setFile_url(request.getDestination());
+ feed.setDownloaded(true);
+
+ reason = null;
+ String reasonDetailed = null;
+ successful = true;
+ FeedHandler feedHandler = new FeedHandler();
+
+ try {
+ feed = feedHandler.parseFeed(feed);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, feed.getTitle() + " parsed");
+ if (checkFeedData(feed) == false) {
+ throw new InvalidFeedException();
+ }
+ // Save information of feed in DB
+ savedFeed = DBTasks.updateFeed(DownloadService.this, feed);
+ // Download Feed Image if provided and not downloaded
+ if (savedFeed.getImage() != null
+ && savedFeed.getImage().isDownloaded() == false) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Feed has image; Downloading....");
+ savedFeed.getImage().setFeed(savedFeed);
+ final Feed savedFeedRef = savedFeed;
+ handler.post(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ requester.downloadImage(DownloadService.this,
+ savedFeedRef.getImage());
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DBWriter.addDownloadStatus(
+ DownloadService.this,
+ new DownloadStatus(
+ savedFeedRef.getImage(),
+ savedFeedRef
+ .getImage()
+ .getHumanReadableIdentifier(),
+ DownloadError.ERROR_REQUEST_ERROR,
+ false, e.getMessage()));
+ }
+ }
+ });
+
+ }
+
+ } catch (SAXException e) {
+ successful = false;
+ e.printStackTrace();
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ } catch (IOException e) {
+ successful = false;
+ e.printStackTrace();
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ } catch (ParserConfigurationException e) {
+ successful = false;
+ e.printStackTrace();
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ } catch (UnsupportedFeedtypeException e) {
+ e.printStackTrace();
+ successful = false;
+ reason = DownloadError.ERROR_UNSUPPORTED_TYPE;
+ reasonDetailed = e.getMessage();
+ } catch (InvalidFeedException e) {
+ e.printStackTrace();
+ successful = false;
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ }
+
+ // cleanup();
+ if (savedFeed == null) {
+ savedFeed = feed;
+ }
+
+ saveDownloadStatus(new DownloadStatus(savedFeed,
+ savedFeed.getHumanReadableIdentifier(), reason, successful,
+ reasonDetailed));
+ sendDownloadHandledIntent();
+ numberOfDownloads.decrementAndGet();
+ queryDownloadsAsync();
+ }
+
+ /**
+ * Checks if the feed was parsed correctly.
+ */
+ private boolean checkFeedData(Feed feed) {
+ if (feed.getTitle() == null) {
+ Log.e(TAG, "Feed has no title.");
+ return false;
+ }
+ if (!hasValidFeedItems(feed)) {
+ Log.e(TAG, "Feed has invalid items");
+ return false;
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Feed appears to be valid.");
+ return true;
+
+ }
+
+ private boolean hasValidFeedItems(Feed feed) {
+ for (FeedItem item : feed.getItemsArray()) {
+ if (item.getTitle() == null) {
+ Log.e(TAG, "Item has no title");
+ return false;
+ }
+ if (item.getPubDate() == null) {
+ Log.e(TAG,
+ "Item has no pubDate. Using current time as pubDate");
+ if (item.getTitle() != null) {
+ Log.e(TAG, "Title of invalid item: " + item.getTitle());
+ }
+ item.setPubDate(new Date());
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Delete files that aren't needed anymore
+ */
+ private void cleanup(Feed feed) {
+ if (feed.getFile_url() != null) {
+ if (new File(feed.getFile_url()).delete())
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Successfully deleted cache file.");
+ else
+ Log.e(TAG, "Failed to delete cache file.");
+ feed.setFile_url(null);
+ } else if (AppConfig.DEBUG) {
+ Log.d(TAG, "Didn't delete cache file: File url is not set.");
+ }
+ }
+
+ }
+
+ /**
+ * Handles a completed image download.
+ */
+ class ImageHandlerThread implements Runnable {
+
+ private DownloadRequest request;
+ private DownloadStatus status;
+
+ public ImageHandlerThread(DownloadStatus status, DownloadRequest request) {
+ if (status == null) {
+ throw new IllegalArgumentException("Status must not be null");
+ }
+ if (request == null) {
+ throw new IllegalArgumentException("Request must not be null");
+ }
+ this.status = status;
+ this.request = request;
+ }
+
+ @Override
+ public void run() {
+ FeedImage image = DBReader.getFeedImage(DownloadService.this, request.getFeedfileId());
+ if (image == null) {
+ throw new IllegalStateException("Could not find downloaded image in database");
+ }
+
+ image.setFile_url(request.getDestination());
+ image.setDownloaded(true);
+
+ saveDownloadStatus(status);
+ sendDownloadHandledIntent();
+ DBWriter.setFeedImage(DownloadService.this, image);
+ numberOfDownloads.decrementAndGet();
+ queryDownloadsAsync();
+ }
+ }
+
+ /**
+ * Handles a completed media download.
+ */
+ class MediaHandlerThread implements Runnable {
+
+ private DownloadRequest request;
+ private DownloadStatus status;
+
+ public MediaHandlerThread(DownloadStatus status, DownloadRequest request) {
+ if (status == null) {
+ throw new IllegalArgumentException("Status must not be null");
+ }
+ if (request == null) {
+ throw new IllegalArgumentException("Request must not be null");
+ }
+
+ this.status = status;
+ this.request = request;
+ }
+
+ @Override
+ public void run() {
+ FeedMedia media = DBReader.getFeedMedia(DownloadService.this,
+ request.getFeedfileId());
+ if (media == null) {
+ throw new IllegalStateException(
+ "Could not find downloaded media object in database");
+ }
+ boolean chaptersRead = false;
+ media.setDownloaded(true);
+ media.setFile_url(request.getDestination());
+
+ // Get duration
+ MediaPlayer mediaplayer = new MediaPlayer();
+ try {
+ mediaplayer.setDataSource(media.getFile_url());
+ mediaplayer.prepare();
+ media.setDuration(mediaplayer.getDuration());
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Duration of file is " + media.getDuration());
+ mediaplayer.reset();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ mediaplayer.release();
+ }
+
+ if (media.getItem().getChapters() == null) {
+ ChapterUtils.loadChaptersFromFileUrl(media);
+ if (media.getItem().getChapters() != null) {
+ chaptersRead = true;
+ }
+ }
+
+ saveDownloadStatus(status);
+ sendDownloadHandledIntent();
+
+ try {
+ if (chaptersRead) {
+ DBWriter.setFeedItem(DownloadService.this, media.getItem()).get();
+ }
+ DBWriter.setFeedMedia(DownloadService.this, media).get();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ if (!DBTasks.isInQueue(DownloadService.this, media.getItem().getId())) {
+ DBWriter.addQueueItem(DownloadService.this, media.getItem().getId());
+ }
+
+ numberOfDownloads.decrementAndGet();
+ queryDownloadsAsync();
+ }
+ }
+
+ /**
+ * Schedules the notification updater task if it hasn't been scheduled yet.
+ */
+ private void setupNotificationUpdater() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting up notification updater");
+ if (notificationUpdater == null) {
+ notificationUpdater = new NotificationUpdater();
+ notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(
+ notificationUpdater, 5L, 5L, TimeUnit.SECONDS);
+ }
+ }
+
+ private void cancelNotificationUpdater() {
+ boolean result = false;
+ if (notificationUpdaterFuture != null) {
+ result = notificationUpdaterFuture.cancel(true);
+ }
+ notificationUpdater = null;
+ notificationUpdaterFuture = null;
+ Log.d(TAG, "NotificationUpdater cancelled. Result: " + result);
+ }
+
+ private class NotificationUpdater implements Runnable {
+ public void run() {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Notification n = updateNotifications();
+ if (n != null) {
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, n);
+ }
+ }
+ });
+ }
+ }
+
+ public List<Downloader> getDownloads() {
+ return downloads;
+ }
}
diff --git a/src/de/danoeh/antennapod/service/download/DownloadStatus.java b/src/de/danoeh/antennapod/service/download/DownloadStatus.java
new file mode 100644
index 000000000..62e54cbb4
--- /dev/null
+++ b/src/de/danoeh/antennapod/service/download/DownloadStatus.java
@@ -0,0 +1,181 @@
+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 {
+ /**
+ * Downloaders should use this constant for the size attribute if necessary
+ * so that the listadapters etc. can react properly.
+ */
+ public static final int SIZE_UNKNOWN = -1;
+
+ // ----------------------------------- ATTRIBUTES STORED IN DB
+ /** Unique id for storing the object in database. */
+ protected long id;
+ /**
+ * A human-readable string which is shown to the user so that he can
+ * identify the download. Should be the title of the item/feed/media or the
+ * URL if the download has no other title.
+ */
+ protected String title;
+ protected DownloadError reason;
+ /**
+ * A message which can be presented to the user to give more information.
+ * Should be null if Download was successful.
+ */
+ protected String reasonDetailed;
+ protected boolean successful;
+ protected Date completionDate;
+ protected long feedfileId;
+ /**
+ * Is used to determine the type of the feedfile even if the feedfile does
+ * not exist anymore. The value should be FEEDFILETYPE_FEED,
+ * FEEDFILETYPE_FEEDIMAGE or FEEDFILETYPE_FEEDMEDIA
+ */
+ protected int feedfileType;
+
+ // ------------------------------------ NOT STORED IN DB
+ protected boolean done;
+ protected boolean cancelled;
+
+ /** Constructor for restoring Download status entries from DB. */
+ public DownloadStatus(long id, String title, long feedfileId,
+ int feedfileType, boolean successful, DownloadError reason,
+ Date completionDate, String reasonDetailed) {
+ this.id = id;
+ this.title = title;
+ this.done = true;
+ this.feedfileId = feedfileId;
+ this.reason = reason;
+ this.successful = successful;
+ this.completionDate = completionDate;
+ this.reasonDetailed = reasonDetailed;
+ this.feedfileType = feedfileType;
+ }
+
+ public DownloadStatus(DownloadRequest request, DownloadError reason,
+ boolean successful, boolean cancelled, String reasonDetailed) {
+ if (request == null) {
+ throw new IllegalArgumentException("request must not be null");
+ }
+ this.title = request.getTitle();
+ this.feedfileId = request.getFeedfileId();
+ this.feedfileType = request.getFeedfileType();
+ this.reason = reason;
+ this.successful = successful;
+ this.cancelled = cancelled;
+ this.reasonDetailed = reasonDetailed;
+ this.completionDate = new Date();
+ }
+
+ /** Constructor for creating new completed downloads. */
+ public DownloadStatus(FeedFile feedfile, String title, DownloadError reason,
+ boolean successful, String reasonDetailed) {
+ if (feedfile == null) {
+ throw new IllegalArgumentException("feedfile must not be null");
+ }
+
+ this.title = title;
+ this.done = true;
+ this.feedfileId = feedfile.getId();
+ this.feedfileType = feedfile.getTypeAsInt();
+ this.reason = reason;
+ this.successful = successful;
+ this.completionDate = new Date();
+ this.reasonDetailed = reasonDetailed;
+ }
+
+ /** Constructor for creating new completed downloads. */
+ public DownloadStatus(long feedfileId, int feedfileType, String title,
+ DownloadError reason, boolean successful, String reasonDetailed) {
+ this.title = title;
+ this.done = true;
+ this.feedfileId = feedfileId;
+ this.feedfileType = feedfileType;
+ this.reason = reason;
+ this.successful = successful;
+ this.completionDate = new Date();
+ this.reasonDetailed = reasonDetailed;
+ }
+
+ @Override
+ public String toString() {
+ return "DownloadStatus [id=" + id + ", title=" + title + ", reason="
+ + reason + ", reasonDetailed=" + reasonDetailed
+ + ", successful=" + successful + ", completionDate="
+ + completionDate + ", feedfileId=" + feedfileId
+ + ", feedfileType=" + feedfileType + ", done=" + done
+ + ", cancelled=" + cancelled + "]";
+ }
+
+ 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 9ed9d9a76..84731fe9f 100644
--- a/src/de/danoeh/antennapod/service/download/Downloader.java
+++ b/src/de/danoeh/antennapod/service/download/Downloader.java
@@ -1,49 +1,50 @@
package de.danoeh.antennapod.service.download;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
+
+import java.util.concurrent.Callable;
/** Downloads files */
-public abstract class Downloader extends Thread {
+public abstract class Downloader implements Callable<Downloader> {
private static final String TAG = "Downloader";
- private DownloaderCallback downloaderCallback;
- protected boolean finished;
+ protected volatile boolean finished;
protected volatile boolean cancelled;
- protected volatile DownloadStatus status;
+ protected DownloadRequest request;
+ protected DownloadStatus result;
- public Downloader(DownloaderCallback downloaderCallback,
- DownloadStatus status) {
+ public Downloader(DownloadRequest request) {
super();
- this.downloaderCallback = downloaderCallback;
- this.status = status;
- this.status.setStatusMsg(R.string.download_pending);
+ this.request = request;
+ this.request.setStatusMsg(R.string.download_pending);
this.cancelled = false;
+ this.result = new DownloadStatus(request, null, false, false, null);
}
- /**
- * This method must be called when the download was completed, failed, or
- * was cancelled
- */
- protected void finish() {
- if (!finished) {
- finished = true;
- downloaderCallback.onDownloadCompleted(this);
+ protected abstract void download();
+
+ public final Downloader call() {
+ download();
+ if (result == null) {
+ throw new IllegalStateException(
+ "Downloader hasn't created DownloadStatus object");
}
+ finished = true;
+ return this;
}
- protected abstract void download();
+ public DownloadRequest getDownloadRequest() {
+ return request;
+ }
- @Override
- public final void run() {
- download();
- finish();
+ public DownloadStatus getResult() {
+ return result;
}
- public DownloadStatus getStatus() {
- return status;
+ public boolean isFinished() {
+ return finished;
}
public void cancel() {
diff --git a/src/de/danoeh/antennapod/service/download/HttpDownloader.java b/src/de/danoeh/antennapod/service/download/HttpDownloader.java
index 0cb89368d..c9671ceb3 100644
--- a/src/de/danoeh/antennapod/service/download/HttpDownloader.java
+++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java
@@ -26,7 +26,6 @@ import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.StorageUtils;
@@ -39,9 +38,8 @@ public class HttpDownloader extends Downloader {
private static final int CONNECTION_TIMEOUT = 30000;
private static final int SOCKET_TIMEOUT = 30000;
- public HttpDownloader(DownloaderCallback downloaderCallback,
- DownloadStatus status) {
- super(downloaderCallback, status);
+ public HttpDownloader(DownloadRequest request) {
+ super(request);
}
private DefaultHttpClient createHttpClient() {
@@ -63,11 +61,10 @@ public class HttpDownloader extends Downloader {
@Override
protected void download() {
DefaultHttpClient httpClient = null;
- BufferedOutputStream out = null;
+ OutputStream out = null;
InputStream connection = null;
try {
- HttpGet httpGet = new HttpGet(status.getFeedFile()
- .getDownload_url());
+ HttpGet httpGet = new HttpGet(request.getSource());
httpClient = createHttpClient();
HttpResponse response = httpClient.execute(httpGet);
HttpEntity httpEntity = response.getEntity();
@@ -76,8 +73,7 @@ public class HttpDownloader extends Downloader {
Log.d(TAG, "Response code is " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK && httpEntity != null) {
if (StorageUtils.storageAvailable(PodcastApp.getInstance())) {
- File destination = new File(status.getFeedFile()
- .getFile_url());
+ File destination = new File(request.getDestination());
if (!destination.exists()) {
connection = AndroidHttpClient
.getUngzippedContent(httpEntity);
@@ -86,34 +82,34 @@ public class HttpDownloader extends Downloader {
destination));
byte[] buffer = new byte[BUFFER_SIZE];
int count = 0;
- status.setStatusMsg(R.string.download_running);
+ request.setStatusMsg(R.string.download_running);
if (AppConfig.DEBUG)
Log.d(TAG, "Getting size of download");
- status.setSize(httpEntity.getContentLength());
+ request.setSize(httpEntity.getContentLength());
if (AppConfig.DEBUG)
- Log.d(TAG, "Size is " + status.getSize());
- if (status.getSize() < 0) {
- status.setSize(DownloadStatus.SIZE_UNKNOWN);
+ Log.d(TAG, "Size is " + request.getSize());
+ if (request.getSize() < 0) {
+ request.setSize(DownloadStatus.SIZE_UNKNOWN);
}
long freeSpace = StorageUtils.getFreeSpaceAvailable();
if (AppConfig.DEBUG)
Log.d(TAG, "Free space is " + freeSpace);
- if (status.getSize() == DownloadStatus.SIZE_UNKNOWN
- || status.getSize() <= freeSpace) {
+ if (request.getSize() == DownloadStatus.SIZE_UNKNOWN
+ || request.getSize() <= freeSpace) {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting download");
while (!cancelled
&& (count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
- status.setSoFar(status.getSoFar() + count);
- status.setProgressPercent((int) (((double) status
- .getSoFar() / (double) status.getSize()) * 100));
+ request.setSoFar(request.getSoFar() + count);
+ request.setProgressPercent((int) (((double) request
+ .getSoFar() / (double) request
+ .getSize()) * 100));
}
if (cancelled) {
onCancelled();
} else {
- out.flush();
onSuccess();
}
} else {
@@ -145,10 +141,8 @@ public class HttpDownloader extends Downloader {
} catch (NullPointerException e) {
// might be thrown by connection.getInputStream()
e.printStackTrace();
- onFail(DownloadError.ERROR_CONNECTION_ERROR, status.getFeedFile()
- .getDownload_url());
+ onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
} finally {
- IOUtils.closeQuietly(connection);
IOUtils.closeQuietly(out);
if (httpClient != null) {
httpClient.getConnectionManager().shutdown();
@@ -159,29 +153,28 @@ public class HttpDownloader extends Downloader {
private void onSuccess() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was successful");
- status.setSuccessful();
+ result.setSuccessful();
}
private void onFail(DownloadError reason, String reasonDetailed) {
if (AppConfig.DEBUG) {
Log.d(TAG, "Download failed");
}
- status.setFailed(reason, reasonDetailed);
+ result.setFailed(reason, reasonDetailed);
cleanup();
}
private void onCancelled() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was cancelled");
- status.setCancelled();
+ result.setCancelled();
cleanup();
}
/** Deletes unfinished downloads. */
private void cleanup() {
- if (status != null && status.getFeedFile() != null
- && status.getFeedFile().getFile_url() != null) {
- File dest = new File(status.getFeedFile().getFile_url());
+ if (request.getDestination() != null) {
+ File dest = new File(request.getDestination());
if (dest.exists()) {
boolean rc = dest.delete();
if (AppConfig.DEBUG)