diff options
author | daniel oeh <daniel.oeh@gmail.com> | 2012-08-18 00:53:09 +0200 |
---|---|---|
committer | daniel oeh <daniel.oeh@gmail.com> | 2012-08-18 00:53:09 +0200 |
commit | fdb9a296adeab9f5f169b98c8488354ec821ebc6 (patch) | |
tree | 286000e4685554e6aa9874c255a3724ad2e6ec57 | |
parent | 1b15ad65b65f1bc9b0bb74b7c9118796048713d5 (diff) | |
parent | bcbc1624123b4c7be974d9f67cd1bd31114d1d42 (diff) | |
download | AntennaPod-fdb9a296adeab9f5f169b98c8488354ec821ebc6.zip |
Merge branch 'downloadmanager' into develop
23 files changed, 876 insertions, 508 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6d9107921..a3c49a68a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -11,8 +11,6 @@ <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="14" /> - - <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <supports-screens @@ -74,7 +72,7 @@ android:theme="@style/Theme.MediaPlayer" android:screenOrientation="portrait"/> <service - android:name="de.danoeh.antennapod.service.DownloadService" + android:name=".service.download.DownloadService" android:enabled="true" /> <service android:name="de.danoeh.antennapod.service.PlaybackService" @@ -200,4 +198,4 @@ <activity android:name=".activity.VideoplayerActivity" android:configChanges="keyboardHidden|orientation" android:screenOrientation="landscape" android:theme="@style/VideoplayerTheme"></activity> </application> -</manifest>
\ No newline at end of file +</manifest> diff --git a/res/values/strings.xml b/res/values/strings.xml index 9586fa5d6..2670a78ed 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -87,7 +87,7 @@ <string name="pref_pauseOnHeadsetDisconnect_title">Headphones disconnect</string> <string name="pref_mobileUpdate_title">Mobile updates</string> <string name="pref_mobileUpdate_sum">Allow updates over the mobile data connection</string> - <string name="download_report_title">All downloads completed</string> + <string name="download_report_title">Downloads completed</string> <string name="refresh_label">Refresh</string> <string name="external_storage_error_msg">No external storage is available. Please make sure that external storage is mounted so that the app can work properly.</string> <string name="share_link_label">Share link</string> @@ -176,6 +176,9 @@ <string name="user_interface_label">User Interface</string> <string name="feed_delete_confirmation_msg">Please confirm that you want to delete this feed and ALL episodes of this feed that you have downloaded.</string> <string name="image_of_prefix">Image of:\u0020</string> + <string name="download_error_malformed_url">Malformed URL</string> + <string name="download_error_io_error">IO Error</string> + <string name="download_error_device_not_found">External storage unavailable</string> </resources>
\ No newline at end of file diff --git a/src/de/danoeh/antennapod/activity/AddFeedActivity.java b/src/de/danoeh/antennapod/activity/AddFeedActivity.java index a5185bf9b..c17ef4996 100644 --- a/src/de/danoeh/antennapod/activity/AddFeedActivity.java +++ b/src/de/danoeh/antennapod/activity/AddFeedActivity.java @@ -23,7 +23,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.asynctask.DownloadStatus; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.service.DownloadService; +import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.ConnectionTester; import de.danoeh.antennapod.util.DownloadError; diff --git a/src/de/danoeh/antennapod/activity/DownloadActivity.java b/src/de/danoeh/antennapod/activity/DownloadActivity.java index 363084a81..404cb49c6 100644 --- a/src/de/danoeh/antennapod/activity/DownloadActivity.java +++ b/src/de/danoeh/antennapod/activity/DownloadActivity.java @@ -1,8 +1,15 @@ package de.danoeh.antennapod.activity; +import java.util.List; + +import android.annotation.SuppressLint; +import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.ServiceConnection; +import android.os.AsyncTask; import android.os.Bundle; import android.os.IBinder; import android.util.Log; @@ -18,9 +25,9 @@ import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.DownloadlistAdapter; -import de.danoeh.antennapod.asynctask.DownloadObserver; import de.danoeh.antennapod.asynctask.DownloadStatus; -import de.danoeh.antennapod.service.DownloadService; +import de.danoeh.antennapod.service.download.DownloadService; +import de.danoeh.antennapod.service.download.Downloader; import de.danoeh.antennapod.storage.DownloadRequester; /** @@ -28,7 +35,7 @@ import de.danoeh.antennapod.storage.DownloadRequester; * objects created by a DownloadObserver. */ public class DownloadActivity extends SherlockListActivity implements - ActionMode.Callback, DownloadObserver.Callback { + ActionMode.Callback { private static final String TAG = "DownloadActivity"; private static final int MENU_SHOW_LOG = 0; @@ -38,7 +45,11 @@ public class DownloadActivity extends SherlockListActivity implements private ActionMode mActionMode; private DownloadStatus selectedDownload; - private DownloadObserver downloadObserver; + + private DownloadService downloadService = null; + boolean mIsBound; + + private AsyncTask<Void, Void, Void> contentRefresher; @Override protected void onCreate(Bundle savedInstanceState) { @@ -47,24 +58,25 @@ public class DownloadActivity extends SherlockListActivity implements Log.d(TAG, "Creating Activity"); requester = DownloadRequester.getInstance(); getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } @Override protected void onPause() { super.onPause(); unbindService(mConnection); - if (downloadObserver != null) { - downloadObserver.unregisterCallback(DownloadActivity.this); - } + unregisterReceiver(contentChanged); } @Override protected void onResume() { super.onResume(); - if (AppConfig.DEBUG) - Log.d(TAG, "Trying to bind service"); + registerReceiver(contentChanged, new IntentFilter( + DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED)); bindService(new Intent(this, DownloadService.class), mConnection, 0); + startContentRefresher(); + if (dla != null) { + dla.notifyDataSetChanged(); + } } @Override @@ -72,6 +84,71 @@ public class DownloadActivity extends SherlockListActivity implements super.onStop(); if (AppConfig.DEBUG) Log.d(TAG, "Stopping Activity"); + stopContentRefresher(); + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceDisconnected(ComponentName className) { + downloadService = null; + mIsBound = false; + Log.i(TAG, "Closed connection with DownloadService."); + } + + public void onServiceConnected(ComponentName name, IBinder service) { + downloadService = ((DownloadService.LocalBinder) service) + .getService(); + mIsBound = true; + if (AppConfig.DEBUG) + Log.d(TAG, "Connection to service established"); + dla = new DownloadlistAdapter(DownloadActivity.this, 0, + downloadService.getDownloads()); + setListAdapter(dla); + dla.notifyDataSetChanged(); + } + }; + + @SuppressLint("NewApi") + private void startContentRefresher() { + if (contentRefresher != null) { + contentRefresher.cancel(true); + } + contentRefresher = new AsyncTask<Void, Void, Void>() { + private final int WAITING_INTERVALL = 1000; + + @Override + protected void onProgressUpdate(Void... values) { + super.onProgressUpdate(values); + if (dla != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Refreshing content automatically"); + dla.notifyDataSetChanged(); + } + } + + @Override + protected Void doInBackground(Void... params) { + while (!isCancelled()) { + try { + Thread.sleep(WAITING_INTERVALL); + publishProgress(); + } catch (InterruptedException e) { + return null; + } + } + return null; + } + }; + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { + contentRefresher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } else { + contentRefresher.execute(); + } + } + + private void stopContentRefresher() { + if (contentRefresher != null) { + contentRefresher.cancel(true); + } } @Override @@ -82,7 +159,7 @@ public class DownloadActivity extends SherlockListActivity implements @Override public boolean onItemLongClick(AdapterView<?> arg0, View view, int position, long id) { - DownloadStatus selection = dla.getItem(position); + DownloadStatus selection = dla.getItem(position).getStatus(); if (selection != null && mActionMode != null) { mActionMode.finish(); } @@ -142,8 +219,7 @@ public class DownloadActivity extends SherlockListActivity implements boolean handled = false; switch (item.getItemId()) { case R.id.cancel_download_item: - requester.cancelDownload(this, selectedDownload.getFeedFile() - .getDownloadId()); + requester.cancelDownload(this, selectedDownload.getFeedFile()); handled = true; break; } @@ -158,53 +234,16 @@ public class DownloadActivity extends SherlockListActivity implements dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE); } - private DownloadService downloadService = null; - boolean mIsBound; + private BroadcastReceiver contentChanged = new BroadcastReceiver() { - private ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - downloadService = ((DownloadService.LocalBinder) service) - .getService(); - if (AppConfig.DEBUG) - Log.d(TAG, "Connection to service established"); - dla = new DownloadlistAdapter(DownloadActivity.this, 0, - downloadService.getDownloadObserver().getStatusList()); - setListAdapter(dla); - downloadObserver = downloadService.getDownloadObserver(); - downloadObserver.registerCallback(DownloadActivity.this); - } - - public void onServiceDisconnected(ComponentName className) { - downloadService = null; - mIsBound = false; - Log.i(TAG, "Closed connection with DownloadService."); - } - }; - - @Override - public void onProgressUpdate() { - runOnUiThread(new Runnable() { - - @Override - public void run() { + @Override + public void onReceive(Context context, Intent intent) { + if (dla != null) { + if (AppConfig.DEBUG) + Log.d(TAG, "Refreshing content"); dla.notifyDataSetChanged(); - - } - }); - } - - @Override - public void onFinish() { - if (AppConfig.DEBUG) - Log.d(TAG, "Observer has finished, clearing adapter"); - runOnUiThread(new Runnable() { - - @Override - public void run() { - dla.clear(); - dla.notifyDataSetInvalidated(); } - }); + } + }; - } } diff --git a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java index 11a15accb..2c19b5649 100644 --- a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java +++ b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java @@ -1,5 +1,9 @@ package de.danoeh.antennapod.activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Bundle; import com.actionbarsherlock.app.SherlockListActivity; @@ -9,7 +13,10 @@ import com.actionbarsherlock.view.MenuItem; import de.danoeh.antennapod.adapter.DownloadLogAdapter; import de.danoeh.antennapod.feed.FeedManager; -/** Displays completed and failed downloads in a list. The data comes from the FeedManager. */ +/** + * Displays completed and failed downloads in a list. The data comes from the + * FeedManager. + */ public class DownloadLogActivity extends SherlockListActivity { private static final String TAG = "DownloadLogActivity"; @@ -27,6 +34,20 @@ public class DownloadLogActivity extends SherlockListActivity { } @Override + protected void onPause() { + super.onPause(); + unregisterReceiver(contentUpdate); + } + + @Override + protected void onResume() { + super.onResume(); + registerReceiver(contentUpdate, new IntentFilter( + FeedManager.ACTION_DOWNLOADLOG_UPDATE)); + dla.notifyDataSetChanged(); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { return true; } @@ -43,4 +64,15 @@ public class DownloadLogActivity extends SherlockListActivity { return true; } + private BroadcastReceiver contentUpdate = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction() + .equals(FeedManager.ACTION_DOWNLOADLOG_UPDATE)) { + dla.notifyDataSetChanged(); + } + } + }; + } diff --git a/src/de/danoeh/antennapod/activity/MainActivity.java b/src/de/danoeh/antennapod/activity/MainActivity.java index eab7b6ea0..d6db17740 100644 --- a/src/de/danoeh/antennapod/activity/MainActivity.java +++ b/src/de/danoeh/antennapod/activity/MainActivity.java @@ -23,8 +23,8 @@ import de.danoeh.antennapod.feed.FeedManager; import de.danoeh.antennapod.fragment.FeedlistFragment; import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.UnreadItemlistFragment; -import de.danoeh.antennapod.service.DownloadService; import de.danoeh.antennapod.service.PlaybackService; +import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.StorageUtils; import de.danoeh.antennapod.AppConfig; diff --git a/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java index d103787f5..4346de927 100644 --- a/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java @@ -14,16 +14,17 @@ import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedFile; import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.service.download.Downloader; import de.danoeh.antennapod.util.Converter; import de.danoeh.antennapod.R; -public class DownloadlistAdapter extends ArrayAdapter<DownloadStatus> { +public class DownloadlistAdapter extends ArrayAdapter<Downloader> { private int selectedItemIndex; public static final int SELECTION_NONE = -1; public DownloadlistAdapter(Context context, int textViewResourceId, - List<DownloadStatus> objects) { + List<Downloader> objects) { super(context, textViewResourceId, objects); this.selectedItemIndex = SELECTION_NONE; } @@ -31,7 +32,7 @@ public class DownloadlistAdapter extends ArrayAdapter<DownloadStatus> { @Override public View getView(int position, View convertView, ViewGroup parent) { Holder holder; - DownloadStatus status = getItem(position); + DownloadStatus status = getItem(position).getStatus(); FeedFile feedFile = status.getFeedFile(); // Inflate layout if (convertView == null) { @@ -77,7 +78,9 @@ public class DownloadlistAdapter extends ArrayAdapter<DownloadStatus> { } } holder.title.setText(titleText); - holder.message.setText(status.getStatusMsg()); + if (status.getStatusMsg() != 0) { + holder.message.setText(status.getStatusMsg()); + } holder.downloaded.setText(Converter.byteToString(status.getSoFar()) + " / " + Converter.byteToString(status.getSize())); holder.percent.setText(status.getProgressPercent() + "%"); diff --git a/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java index 568768058..9dbc225d0 100644 --- a/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java +++ b/src/de/danoeh/antennapod/adapter/FeedItemlistAdapter.java @@ -5,6 +5,7 @@ import java.util.List; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.Converter; import de.danoeh.antennapod.R; import android.widget.ArrayAdapter; @@ -121,7 +122,8 @@ public class FeedItemlistAdapter extends ArrayAdapter<FeedItem> { holder.downloaded.setVisibility(View.GONE); } - if (item.getMedia().isDownloading()) { + if (DownloadRequester.getInstance().isDownloadingFile( + item.getMedia())) { holder.downloading.setVisibility(View.VISIBLE); } else { holder.downloading.setVisibility(View.GONE); diff --git a/src/de/danoeh/antennapod/asynctask/DownloadObserver.java b/src/de/danoeh/antennapod/asynctask/DownloadObserver.java index 5125e6b91..40ab869f3 100644 --- a/src/de/danoeh/antennapod/asynctask/DownloadObserver.java +++ b/src/de/danoeh/antennapod/asynctask/DownloadObserver.java @@ -1,4 +1,4 @@ -package de.danoeh.antennapod.asynctask; +/*package de.danoeh.antennapod.asynctask; import java.util.ArrayList; import java.util.Collections; @@ -16,16 +16,17 @@ import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; -/** Observes the status of a specific Download */ -public class DownloadObserver extends AsyncTask<Void, Void, Void> { +*//** Observes the status of a specific Download *//* +public class DownloadObserver{ + *//****** private static final String TAG = "DownloadObserver"; - /** Types of downloads to observe. */ + /** Types of downloads to observe. *//* public static final int TYPE_FEED = 0; public static final int TYPE_IMAGE = 1; public static final int TYPE_MEDIA = 2; - /** Error codes */ + *//** Error codes *//* public static final int ALREADY_DOWNLOADED = 1; public static final int NO_DOWNLOAD_FOUND = 2; @@ -165,7 +166,7 @@ public class DownloadObserver extends AsyncTask<Void, Void, Void> { } - /** Request a cursor with all running Feedfile downloads */ + *//** Request a cursor with all running Feedfile downloads *//* private Cursor getDownloadCursor() { // Collect download ids @@ -186,7 +187,7 @@ public class DownloadObserver extends AsyncTask<Void, Void, Void> { return result; } - /** Return value of a specific column */ + *//** Return value of a specific column *//* private int getDownloadStatus(Cursor c, String column) { int status = c.getInt(c.getColumnIndex(column)); return status; @@ -205,7 +206,7 @@ public class DownloadObserver extends AsyncTask<Void, Void, Void> { return context; } - /** Find a DownloadStatus entry by its FeedFile */ + *//** Find a DownloadStatus entry by its FeedFile *//* public DownloadStatus findDownloadStatus(FeedFile f) { for (DownloadStatus status : statusList) { if (status.feedfile == f) { @@ -238,3 +239,4 @@ public class DownloadObserver extends AsyncTask<Void, Void, Void> { } } +*/
\ No newline at end of file diff --git a/src/de/danoeh/antennapod/asynctask/DownloadStatus.java b/src/de/danoeh/antennapod/asynctask/DownloadStatus.java index 67cf4a6d8..e5b2bcf5c 100644 --- a/src/de/danoeh/antennapod/asynctask/DownloadStatus.java +++ b/src/de/danoeh/antennapod/asynctask/DownloadStatus.java @@ -14,6 +14,9 @@ public class DownloadStatus { /** Unique id for storing the object in database. */ protected long id; + /** Used by DownloadService to check if the status has been updated. */ + protected volatile boolean updateAvailable; + protected FeedFile feedfile; protected int progressPercent; protected long soFar; @@ -29,8 +32,8 @@ public class DownloadStatus { } /** Constructor for restoring Download status entries from DB. */ - public DownloadStatus(long id, FeedFile feedfile, boolean successful, int reason, - Date completionDate) { + public DownloadStatus(long id, FeedFile feedfile, boolean successful, + int reason, Date completionDate) { this.id = id; this.feedfile = feedfile; progressPercent = 100; @@ -41,11 +44,9 @@ public class DownloadStatus { this.done = true; this.completionDate = completionDate; } - - + /** Constructor for creating new completed downloads. */ - public DownloadStatus(FeedFile feedfile, int reason, - boolean successful) { + public DownloadStatus(FeedFile feedfile, int reason, boolean successful) { this(0, feedfile, successful, reason, new Date()); } @@ -88,8 +89,49 @@ public class DownloadStatus { public boolean isDone() { return done; } - - - + + public void setFeedfile(FeedFile feedfile) { + this.feedfile = feedfile; + } + + public void setProgressPercent(int progressPercent) { + this.progressPercent = progressPercent; + } + + public void setSoFar(long soFar) { + this.soFar = soFar; + } + + public void setSize(long size) { + this.size = size; + } + + public void setStatusMsg(int statusMsg) { + this.statusMsg = statusMsg; + } + + public void setReason(int reason) { + this.reason = reason; + } + + public void setSuccessful(boolean successful) { + this.successful = successful; + } + + public void setDone(boolean done) { + this.done = done; + } + + public void setCompletionDate(Date completionDate) { + this.completionDate = completionDate; + } + + public boolean isUpdateAvailable() { + return updateAvailable; + } + + public void setUpdateAvailable(boolean updateAvailable) { + this.updateAvailable = updateAvailable; + } }
\ No newline at end of file diff --git a/src/de/danoeh/antennapod/feed/FeedFile.java b/src/de/danoeh/antennapod/feed/FeedFile.java index c7a9b7bc1..5ec52d7d2 100644 --- a/src/de/danoeh/antennapod/feed/FeedFile.java +++ b/src/de/danoeh/antennapod/feed/FeedFile.java @@ -1,10 +1,9 @@ package de.danoeh.antennapod.feed; -/** Represents a component of a Feed that has to be downloaded*/ +/** Represents a component of a Feed that has to be downloaded */ public abstract class FeedFile extends FeedComponent { protected String file_url; protected String download_url; - protected long downloadId; // temporary id given by the Android DownloadManager protected boolean downloaded; public FeedFile(String file_url, String download_url, boolean downloaded) { @@ -21,24 +20,19 @@ public abstract class FeedFile extends FeedComponent { public String getFile_url() { return file_url; } + public void setFile_url(String file_url) { this.file_url = file_url; } + public String getDownload_url() { return download_url; } + public void setDownload_url(String download_url) { this.download_url = download_url; } - public long getDownloadId() { - return downloadId; - } - - public void setDownloadId(long downloadId) { - this.downloadId = downloadId; - } - public boolean isDownloaded() { return downloaded; } @@ -46,10 +40,4 @@ public abstract class FeedFile extends FeedComponent { public void setDownloaded(boolean downloaded) { this.downloaded = downloaded; } - - public boolean isDownloading() { - return downloadId != 0; - } - - } diff --git a/src/de/danoeh/antennapod/feed/FeedManager.java b/src/de/danoeh/antennapod/feed/FeedManager.java index 6479f9864..5582d9620 100644 --- a/src/de/danoeh/antennapod/feed/FeedManager.java +++ b/src/de/danoeh/antennapod/feed/FeedManager.java @@ -37,6 +37,7 @@ public class FeedManager { public static final String ACITON_FEED_LIST_UPDATE = "de.danoeh.antennapod.action.feed.feedlistUpdate"; public static final String ACTION_UNREAD_ITEMS_UPDATE = "de.danoeh.antennapod.action.feed.unreadItemsUpdate"; public static final String ACTION_QUEUE_UPDATE = "de.danoeh.antennapod.action.feed.queueUpdate"; + public static final String ACTION_DOWNLOADLOG_UPDATE = "de.danoeh.antennapod.action.feed.downloadLogUpdate"; public static final String EXTRA_FEED_ITEM_ID = "de.danoeh.antennapod.extra.feed.feedItemId"; public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feed.feedId"; @@ -167,8 +168,7 @@ public class FeedManager { imageFile.delete(); } } else if (requester.isDownloadingFile(feed.getImage())) { - requester.cancelDownload(context, feed.getImage() - .getDownloadId()); + requester.cancelDownload(context, feed.getImage()); } // delete stored media files and mark them as read for (FeedItem item : feed.getItems()) { @@ -184,8 +184,7 @@ public class FeedManager { mediaFile.delete(); } else if (item.getMedia() != null && requester.isDownloadingFile(item.getMedia())) { - requester.cancelDownload(context, item.getMedia() - .getDownloadId()); + requester.cancelDownload(context, item.getMedia()); } } @@ -334,20 +333,34 @@ public class FeedManager { public void addDownloadStatus(final Context context, final DownloadStatus status) { - downloadLog.add(status); - dbExec.execute(new Runnable() { + contentChanger.post(new Runnable() { @Override public void run() { - PodDBAdapter adapter = new PodDBAdapter(context); - adapter.open(); + downloadLog.add(status); + final DownloadStatus removedStatus; if (downloadLog.size() > DOWNLOAD_LOG_SIZE) { - adapter.removeDownloadStatus(downloadLog.remove(0)); + removedStatus = downloadLog.remove(0); + } else { + removedStatus = null; } - adapter.setDownloadStatus(status); - adapter.close(); + context.sendBroadcast(new Intent(ACTION_DOWNLOADLOG_UPDATE)); + dbExec.execute(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + if (removedStatus != null) { + adapter.removeDownloadStatus(removedStatus); + } + adapter.setDownloadStatus(status); + adapter.close(); + } + }); } }); + } public void addQueueItem(final Context context, final FeedItem item) { diff --git a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java index b6abdeab7..038b16f36 100644 --- a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java +++ b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java @@ -5,7 +5,7 @@ import de.danoeh.antennapod.adapter.FeedlistAdapter; import de.danoeh.antennapod.asynctask.FeedRemover; import de.danoeh.antennapod.dialog.ConfirmationDialog; import de.danoeh.antennapod.feed.*; -import de.danoeh.antennapod.service.DownloadService; +import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; import de.danoeh.antennapod.AppConfig; @@ -140,15 +140,7 @@ public class FeedlistFragment extends SherlockFragment implements @Override public void run() { - if (intent.getAction().equals( - DownloadService.ACTION_DOWNLOAD_HANDLED)) { - int type = intent.getIntExtra(DownloadService.EXTRA_DOWNLOAD_TYPE, 0); - if (type == DownloadService.DOWNLOAD_TYPE_IMAGE) { - fla.notifyDataSetChanged(); - } - } else { - fla.notifyDataSetChanged(); - } + fla.notifyDataSetChanged(); } }); } diff --git a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java index ba810507d..e8df221f2 100644 --- a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java +++ b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java @@ -27,7 +27,7 @@ import de.danoeh.antennapod.adapter.FeedItemlistAdapter; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.service.DownloadService; +import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.EpisodeFilter; import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler; diff --git a/src/de/danoeh/antennapod/service/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java index f1b056499..96846e6ed 100644 --- a/src/de/danoeh/antennapod/service/DownloadService.java +++ b/src/de/danoeh/antennapod/service/download/DownloadService.java @@ -3,60 +3,60 @@ * to complete, then stops * */ -package de.danoeh.antennapod.service; +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.Date; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; -import de.danoeh.antennapod.AppConfig; -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.activity.DownloadActivity; -import de.danoeh.antennapod.activity.AudioplayerActivity; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.asynctask.DownloadObserver; -import de.danoeh.antennapod.asynctask.DownloadStatus; -import de.danoeh.antennapod.feed.*; -import de.danoeh.antennapod.service.PlaybackService.LocalBinder; -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.DownloadError; -import de.danoeh.antennapod.util.InvalidFeedException; import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; -import android.app.DownloadManager; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.MediaPlayer; -import android.net.Uri; -import android.os.IBinder; import android.content.BroadcastReceiver; import android.content.Context; -import android.database.Cursor; +import android.content.Intent; +import android.content.IntentFilter; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.support.v4.app.NotificationCompat; -import android.util.Log; +import android.media.MediaPlayer; import android.os.AsyncTask; import android.os.Binder; -import android.os.Debug; import android.os.Handler; -import android.os.Message; -import android.os.Messenger; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; import android.preference.PreferenceManager; +import android.support.v4.app.NotificationCompat; +import android.util.Log; +import android.webkit.URLUtil; +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.PodcastApp; +import de.danoeh.antennapod.activity.DownloadActivity; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.feed.Feed; +import de.danoeh.antennapod.feed.FeedFile; +import de.danoeh.antennapod.feed.FeedImage; +import de.danoeh.antennapod.feed.FeedManager; +import de.danoeh.antennapod.feed.FeedMedia; +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.DownloadError; +import de.danoeh.antennapod.util.InvalidFeedException; public class DownloadService extends Service { private static final String TAG = "DownloadService"; @@ -67,13 +67,27 @@ public class DownloadService extends Service { * If the DownloadService receives this intent, it will execute * queryDownloads() */ - public static final String ACTION_NOTIFY_DOWNLOADS_CHANGED = "action.de.danoeh.antennapod.service.notifyDownloadsChanged"; + 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"; + + /** Is used for sending the delete intent for the report notification */ + private static final String ACTION_REPORT_DELETED = "action.de.danoeh.antennapod.service.reportDeleted"; + + /** Extra for ACTION_CANCEL_DOWNLOAD */ + public static final String EXTRA_DOWNLOAD_URL = "downloadUrl"; public static final String ACTION_DOWNLOAD_HANDLED = "action.de.danoeh.antennapod.service.download_handled"; - /** True if handled feed has an image. */ - public static final String EXTRA_FEED_HAS_IMAGE = "extra.de.danoeh.antennapod.service.feed_has_image"; + /** + * 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"; - public static final String EXTRA_IMAGE_DOWNLOAD_ID = "extra.de.danoeh.antennapod.service.image_download_id"; + + /** Extra for ACTION_ENQUEUE_DOWNLOAD intent. */ + public static final String EXTRA_REQUEST = "request"; // Download types for ACTION_DOWNLOAD_HANDLED public static final String EXTRA_DOWNLOAD_TYPE = "extra.de.danoeh.antennapod.service.downloadType"; @@ -81,9 +95,11 @@ public class DownloadService extends Service { public static final int DOWNLOAD_TYPE_MEDIA = 2; public static final int DOWNLOAD_TYPE_IMAGE = 3; - private ArrayList<DownloadStatus> completedDownloads; + private CopyOnWriteArrayList<DownloadStatus> completedDownloads; private ExecutorService syncExecutor; + private ExecutorService downloadExecutor; + private DownloadRequester requester; private FeedManager manager; private NotificationCompat.Builder notificationBuilder; @@ -91,16 +107,14 @@ public class DownloadService extends Service { private int REPORT_ID = 3; /** Needed to determine the duration of a media file */ private MediaPlayer mediaplayer; - private DownloadManager downloadManager; - private DownloadObserver downloadObserver; + private List<Downloader> downloads; private volatile boolean shutdownInitiated = false; /** True if service is running. */ public static boolean isRunning = false; - /** Is started when service waits for shutdown. */ - private Thread waiter; + private Handler handler; private final IBinder mBinder = new LocalBinder(); @@ -112,11 +126,10 @@ public class DownloadService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { - if (waiter != null) { - waiter.interrupt(); + if (intent.getParcelableExtra(EXTRA_REQUEST) != null) { + onDownloadQueued(intent); } - queryDownloads(); - return super.onStartCommand(intent, flags, startId); + return Service.START_NOT_STICKY; } @SuppressLint("NewApi") @@ -125,10 +138,18 @@ public class DownloadService extends Service { if (AppConfig.DEBUG) Log.d(TAG, "Service started"); isRunning = true; - completedDownloads = new ArrayList<DownloadStatus>(); - registerReceiver(downloadReceiver, createIntentFilter()); - registerReceiver(onDownloadsChanged, new IntentFilter( - ACTION_NOTIFY_DOWNLOADS_CHANGED)); + 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); + registerReceiver(reportDeleted, new IntentFilter(ACTION_REPORT_DELETED)); syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { @Override @@ -147,17 +168,20 @@ public class DownloadService extends Service { return t; } }); + downloadExecutor = Executors.newFixedThreadPool(2, new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setPriority(Thread.MIN_PRIORITY); + return t; + } + }); manager = FeedManager.getInstance(); requester = DownloadRequester.getInstance(); mediaplayer = new MediaPlayer(); - downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); - downloadObserver = new DownloadObserver(this); setupNotification(); - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - downloadObserver.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - downloadObserver.execute(); - } + } @Override @@ -171,46 +195,8 @@ public class DownloadService extends Service { Log.d(TAG, "Service shutting down"); isRunning = false; mediaplayer.release(); - unregisterReceiver(downloadReceiver); - unregisterReceiver(onDownloadsChanged); - downloadObserver.cancel(true); - createReport(); - } - - private IntentFilter createIntentFilter() { - IntentFilter filter = new IntentFilter(); - filter.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE); - return filter; - } - - /** Shuts down Executor service and prepares for shutdown */ - private void initiateShutdown() { - if (AppConfig.DEBUG) - Log.d(TAG, "Initiating shutdown"); - // Wait until PoolExecutor is done - waiter = new Thread() { - @Override - public void run() { - syncExecutor.shutdown(); - try { - if (AppConfig.DEBUG) - Log.d(TAG, "Starting to wait for termination"); - boolean b = syncExecutor.awaitTermination(20L, - TimeUnit.SECONDS); - if (AppConfig.DEBUG) - Log.d(TAG, - "Stopping waiting for termination; Result : " - + b); - stopForeground(true); - stopSelf(); - } catch (InterruptedException e) { - e.printStackTrace(); - Log.i(TAG, "Service shutdown was interrupted."); - shutdownInitiated = false; - } - } - }; - waiter.start(); + unregisterReceiver(cancelDownloadReceiver); + unregisterReceiver(downloadQueued); } private void setupNotification() { @@ -232,103 +218,163 @@ public class DownloadService extends Service { Log.d(TAG, "Notification set up"); } - private BroadcastReceiver onDownloadsChanged = new BroadcastReceiver() { + 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_NOTIFY_DOWNLOADS_CHANGED)) { - queryDownloads(); + 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.interrupt(); + 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.interrupt(); + 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 BroadcastReceiver downloadReceiver = new BroadcastReceiver() { - @SuppressLint("NewApi") + 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; + setupNotification(); + } + + DownloadRequester requester = DownloadRequester.getInstance(); + FeedFile feedfile = requester.getDownload(request.source); + if (feedfile != null) { + + DownloadStatus status = new DownloadStatus(feedfile); + 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(final Context context, final Intent intent) { - AsyncTask<Void, Void, Void> handler = new AsyncTask<Void, Void, Void>() { - - @Override - protected Void doInBackground(Void... params) { - int status = -1; - String file_url = null; - boolean successful = false; - int reason = 0; - if (AppConfig.DEBUG) - Log.d(TAG, "Received 'Download Complete' - message."); - long downloadId = intent.getLongExtra( - DownloadManager.EXTRA_DOWNLOAD_ID, 0); - // get status - DownloadManager.Query q = new DownloadManager.Query(); - q.setFilterById(downloadId); - Cursor c = downloadManager.query(q); - if (c.moveToFirst()) { - status = c.getInt(c - .getColumnIndex(DownloadManager.COLUMN_STATUS)); - String uriString = c - .getString(c - .getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)); - if (uriString != null) { - Uri file_uri = Uri.parse(uriString); - file_url = file_uri.getPath(); - } else { - Log.w(TAG, - "DownloadManager didn't provide a destination URI for downloaded file"); + public void onReceive(Context context, Intent intent) { + onDownloadQueued(intent); + } + + }; + + private Downloader getDownloader(DownloadStatus status) { + if (URLUtil.isHttpUrl(status.getFeedFile().getDownload_url())) { + return new HttpDownloader(this, 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>() { + + @Override + protected Void doInBackground(Void... params) { + if (AppConfig.DEBUG) + Log.d(TAG, "Received 'Download Complete' - message."); + DownloadStatus status = downloader.getStatus(); + status.setCompletionDate(new Date()); + boolean successful = status.isSuccessful(); + int reason = status.getReason(); + + 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); } - if (AppConfig.DEBUG) - Log.d(TAG, "File url given by download manager is " - + file_url); - } - if (downloadId == 0) { - Log.d(TAG, "Download ID was null"); - } - FeedFile download = requester.getFeedFile(downloadId); - if (download != null) { - if (status == DownloadManager.STATUS_SUCCESSFUL) { - if (file_url != null) { - download.setFile_url(file_url); - } - if (download.getClass() == Feed.class) { - handleCompletedFeedDownload(context, - (Feed) download); - } else if (download.getClass() == FeedImage.class) { - handleCompletedImageDownload(context, - (FeedImage) download); - } else if (download.getClass() == FeedMedia.class) { - handleCompletedFeedMediaDownload(context, - (FeedMedia) download); - } - successful = true; - - } else if (status == DownloadManager.STATUS_FAILED) { - reason = c - .getInt(c - .getColumnIndex(DownloadManager.COLUMN_REASON)); + } else { + if (!successful + && reason != DownloadError.ERROR_DOWNLOAD_CANCELLED) { Log.e(TAG, "Download failed"); - Log.e(TAG, "reason code is " + reason); - successful = false; - saveDownloadStatus(new DownloadStatus(download, - reason, successful)); - requester.removeDownload(download); - sendDownloadHandledIntent(download.getDownloadId(), - false, 0, getDownloadType(download)); - download.setDownloadId(0); - } - queryDownloads(); + saveDownloadStatus(status); + sendDownloadHandledIntent(getDownloadType(download)); + } - c.close(); - return null; } - }; - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) { - handler.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - handler.execute(); + removeDownload(downloader); + if (!successful) { + queryDownloads(); + } + 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) { + downloads.remove(d); + 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 @@ -355,41 +401,32 @@ public class DownloadService extends Service { } } - private void sendDownloadHandledIntent(long downloadId, - boolean feedHasImage, long imageDownloadId, int type) { + private void sendDownloadHandledIntent(int type) { Intent intent = new Intent(ACTION_DOWNLOAD_HANDLED); - intent.putExtra(EXTRA_DOWNLOAD_ID, downloadId); - intent.putExtra(EXTRA_FEED_HAS_IMAGE, feedHasImage); intent.putExtra(EXTRA_DOWNLOAD_TYPE, type); - if (feedHasImage) { - intent.putExtra(EXTRA_IMAGE_DOWNLOAD_ID, imageDownloadId); - } + sendBroadcast(intent); } + private BroadcastReceiver reportDeleted = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(ACTION_REPORT_DELETED)) { + completedDownloads.clear(); + } + } + }; + /** * 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 feeds is > 1 or if at least one media file was * downloaded. */ - private void createReport() { + private void updateReport() { // check if report should be created - boolean createReport = false; - int feedCount = 0; - for (DownloadStatus status : completedDownloads) { - if (status.getFeedFile().getClass() == Feed.class) { - feedCount++; - if (feedCount > 1) { - createReport = true; - break; - } - } else if (status.getFeedFile().getClass() == FeedMedia.class) { - createReport = true; - break; - } - } - if (createReport) { + if (!completedDownloads.isEmpty()) { if (AppConfig.DEBUG) Log.d(TAG, "Creating report"); int successfulDownloads = 0; @@ -417,7 +454,12 @@ public class DownloadService extends Service { .setContentIntent( PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0)) - .setAutoCancel(true).getNotification(); + .setAutoCancel(true) + .setDeleteIntent( + PendingIntent.getBroadcast(this, 0, new Intent( + ACTION_REPORT_DELETED), + PendingIntent.FLAG_UPDATE_CURRENT)) + .getNotification(); NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nm.notify(REPORT_ID, notification); @@ -428,11 +470,18 @@ public class DownloadService extends Service { } /** Check if there's something else to download, otherwise stop */ - private void queryDownloads() { - int numOfDownloads = requester.getNumberOfDownloads(); - if (!shutdownInitiated && numOfDownloads == 0) { + void queryDownloads() { + int numOfDownloads = downloads.size(); + if (AppConfig.DEBUG) + Log.d(TAG, numOfDownloads + " downloads left"); + if (AppConfig.DEBUG) + Log.d(TAG, "ShutdownInitiated: " + shutdownInitiated); + + if (numOfDownloads == 0) { + if (AppConfig.DEBUG) + Log.d(TAG, "Starting shutdown"); shutdownInitiated = true; - initiateShutdown(); + stopForeground(true); } else { // update notification notificationBuilder.setContentText(numOfDownloads @@ -440,29 +489,29 @@ public class DownloadService extends Service { NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); nm.notify(NOTIFICATION_ID, notificationBuilder.getNotification()); } + updateReport(); } /** Is called whenever a Feed is downloaded */ - private void handleCompletedFeedDownload(Context context, Feed feed) { + private void handleCompletedFeedDownload(DownloadStatus status) { if (AppConfig.DEBUG) Log.d(TAG, "Handling completed Feed Download"); - syncExecutor.execute(new FeedSyncThread(feed, this)); + syncExecutor.execute(new FeedSyncThread(status)); } /** Is called whenever a Feed-Image is downloaded */ - private void handleCompletedImageDownload(Context context, FeedImage image) { + private void handleCompletedImageDownload(DownloadStatus status) { if (AppConfig.DEBUG) Log.d(TAG, "Handling completed Image Download"); - syncExecutor.execute(new ImageHandlerThread(image, this)); + syncExecutor.execute(new ImageHandlerThread(status)); } /** Is called whenever a FeedMedia is downloaded. */ - private void handleCompletedFeedMediaDownload(Context context, - FeedMedia media) { + private void handleCompletedFeedMediaDownload(DownloadStatus status) { if (AppConfig.DEBUG) Log.d(TAG, "Handling completed FeedMedia Download"); - syncExecutor.execute(new MediaHandlerThread(media, this)); + syncExecutor.execute(new MediaHandlerThread(status)); } /** @@ -473,21 +522,18 @@ public class DownloadService extends Service { private static final String TAG = "FeedSyncThread"; private Feed feed; - private DownloadService service; + private DownloadStatus status; private int reason; private boolean successful; - public FeedSyncThread(Feed feed, DownloadService service) { - this.feed = feed; - this.service = service; + public FeedSyncThread(DownloadStatus status) { + this.feed = (Feed) status.getFeedFile(); + this.status = status; } public void run() { Feed savedFeed = null; - long imageId = 0; - boolean hasImage = false; - long downloadId = feed.getDownloadId(); reason = 0; successful = true; FeedManager manager = FeedManager.getInstance(); @@ -501,18 +547,16 @@ public class DownloadService extends Service { if (checkFeedData(feed) == false) { throw new InvalidFeedException(); } - feed.setDownloadId(0); // Save information of feed in DB - savedFeed = manager.updateFeed(service, feed); + 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); - imageId = requester.downloadImage(service, + requester.downloadImage(DownloadService.this, savedFeed.getImage()); - hasImage = true; } } catch (SAXException e) { @@ -537,14 +581,13 @@ public class DownloadService extends Service { reason = DownloadError.ERROR_PARSER_EXCEPTION; } - requester.removeDownload(feed); // cleanup(); if (savedFeed == null) { savedFeed = feed; } + saveDownloadStatus(new DownloadStatus(savedFeed, reason, successful)); - sendDownloadHandledIntent(downloadId, hasImage, imageId, - DOWNLOAD_TYPE_FEED); + sendDownloadHandledIntent(DOWNLOAD_TYPE_FEED); queryDownloads(); } @@ -579,25 +622,22 @@ public class DownloadService extends Service { /** Handles a completed image download. */ class ImageHandlerThread implements Runnable { private FeedImage image; - private DownloadService service; + private DownloadStatus status; - public ImageHandlerThread(FeedImage image, DownloadService service) { - this.image = image; - this.service = service; + public ImageHandlerThread(DownloadStatus status) { + this.image = (FeedImage) status.getFeedFile(); + this.status = status; } @Override public void run() { image.setDownloaded(true); - requester.removeDownload(image); - saveDownloadStatus(new DownloadStatus(image, 0, true)); - sendDownloadHandledIntent(image.getDownloadId(), false, 0, - DOWNLOAD_TYPE_IMAGE); - image.setDownloadId(0); - manager.setFeedImage(service, image); + saveDownloadStatus(status); + sendDownloadHandledIntent(DOWNLOAD_TYPE_IMAGE); + manager.setFeedImage(DownloadService.this, image); if (image.getFeed() != null) { - manager.setFeed(service, image.getFeed()); + manager.setFeed(DownloadService.this, image.getFeed()); } else { Log.e(TAG, "Image has no feed, image might not be saved correctly!"); @@ -609,17 +649,16 @@ public class DownloadService extends Service { /** Handles a completed media download. */ class MediaHandlerThread implements Runnable { private FeedMedia media; - private DownloadService service; + private DownloadStatus status; - public MediaHandlerThread(FeedMedia media, DownloadService service) { + public MediaHandlerThread(DownloadStatus status) { super(); - this.media = media; - this.service = service; + this.media = (FeedMedia) status.getFeedFile(); + this.status = status; } @Override public void run() { - requester.removeDownload(media); media.setDownloaded(true); // Get duration try { @@ -632,11 +671,10 @@ public class DownloadService extends Service { if (AppConfig.DEBUG) Log.d(TAG, "Duration of file is " + media.getDuration()); mediaplayer.reset(); - saveDownloadStatus(new DownloadStatus(media, 0, true)); - sendDownloadHandledIntent(media.getDownloadId(), false, 0, - DOWNLOAD_TYPE_MEDIA); - media.setDownloadId(0); - manager.setFeedMedia(service, media); + + saveDownloadStatus(status); + sendDownloadHandledIntent(DOWNLOAD_TYPE_MEDIA); + manager.setFeedMedia(DownloadService.this, media); boolean autoQueue = PreferenceManager.getDefaultSharedPreferences( getApplicationContext()).getBoolean( PodcastApp.PREF_AUTO_QUEUE, true); @@ -655,13 +693,59 @@ public class DownloadService extends Service { if (AppConfig.DEBUG) Log.d(TAG, "Item is already in queue"); } - queryDownloads(); } } - public DownloadObserver getDownloadObserver() { - return downloadObserver; + /** 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; + } + + } + + public List<Downloader> getDownloads() { + return downloads; } } diff --git a/src/de/danoeh/antennapod/service/download/Downloader.java b/src/de/danoeh/antennapod/service/download/Downloader.java new file mode 100644 index 000000000..13ee8896c --- /dev/null +++ b/src/de/danoeh/antennapod/service/download/Downloader.java @@ -0,0 +1,59 @@ +package de.danoeh.antennapod.service.download; + +import de.danoeh.antennapod.asynctask.DownloadStatus; +import android.os.Environment; +import android.os.Handler; +import android.os.StatFs; + +/** Downloads files */ +public abstract class Downloader extends Thread { + private static final String TAG = "Downloader"; + private Handler handler; + private DownloadService downloadService; + + protected boolean finished; + + protected volatile DownloadStatus status; + + public Downloader(DownloadService downloadService, DownloadStatus status) { + super(); + this.downloadService = downloadService; + this.status = status; + handler = new Handler(); + } + + /** + * This method must be called when the download was completed, failed, or + * was cancelled + */ + protected void finish() { + if (!finished) { + finished = true; + handler.post(new Runnable() { + + @Override + public void run() { + downloadService.onDownloadCompleted(Downloader.this); + } + + }); + } + } + + protected void publishProgress() { + status.setUpdateAvailable(true); + } + + protected abstract void download(); + + @Override + public final void run() { + download(); + finish(); + } + + public DownloadStatus getStatus() { + return status; + } + +}
\ No newline at end of file diff --git a/src/de/danoeh/antennapod/service/download/HttpDownloader.java b/src/de/danoeh/antennapod/service/download/HttpDownloader.java new file mode 100644 index 000000000..fb9d0c6ec --- /dev/null +++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java @@ -0,0 +1,136 @@ +package de.danoeh.antennapod.service.download; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URL; + +import android.util.Log; +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.util.DownloadError; +import de.danoeh.antennapod.util.StorageUtils; + +public class HttpDownloader extends Downloader { + private static final String TAG = "HttpDownloader"; + + private static final int BUFFER_SIZE = 8 * 1024; + private static final int CONNECTION_TIMEOUT = 5000; + + public HttpDownloader(DownloadService downloadService, DownloadStatus status) { + super(downloadService, status); + } + + @Override + protected void download() { + HttpURLConnection connection = null; + OutputStream out = null; + try { + status.setStatusMsg(R.string.download_pending); + publishProgress(); + URL url = new URL(status.getFeedFile().getDownload_url()); + connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(CONNECTION_TIMEOUT); + if (AppConfig.DEBUG) { + Log.d(TAG, "Connected to resource"); + } + if (StorageUtils.externalStorageMounted()) { + File destination = new File(status.getFeedFile().getFile_url()); + if (!destination.exists()) { + InputStream in = new BufferedInputStream( + connection.getInputStream()); + out = new BufferedOutputStream(new FileOutputStream( + destination)); + byte[] buffer = new byte[BUFFER_SIZE]; + int count = 0; + status.setStatusMsg(R.string.download_running); + if (AppConfig.DEBUG) + Log.d(TAG, "Getting size of download"); + status.setSize(connection.getContentLength()); + if (AppConfig.DEBUG) + Log.d(TAG, "Size is " + status.getSize()); + if (status.getSize() == -1 + || status.getSize() <= StorageUtils + .getFreeSpaceAvailable()) { + if (AppConfig.DEBUG) + Log.d(TAG, "Size is " + status.getSize()); + publishProgress(); + if (AppConfig.DEBUG) + Log.d(TAG, "Starting download"); + while ((count = in.read(buffer)) != -1 + && !isInterrupted()) { + out.write(buffer, 0, count); + status.setSoFar(status.getSoFar() + count); + status.setProgressPercent((int) (((double) status + .getSoFar() / (double) status.getSize()) * 100)); + } + if (isInterrupted()) { + onCancelled(); + } else { + onSuccess(); + } + } else { + onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE); + } + } else { + onFail(DownloadError.ERROR_FILE_EXISTS); + } + } else { + onFail(DownloadError.ERROR_DEVICE_NOT_FOUND); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + onFail(DownloadError.ERROR_MALFORMED_URL); + } catch (SocketTimeoutException e) { + e.printStackTrace(); + onFail(DownloadError.ERROR_CONNECTION_ERROR); + } catch (IOException e) { + e.printStackTrace(); + onFail(DownloadError.ERROR_IO_ERROR); + } finally { + if (connection != null) { + connection.disconnect(); + } + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + private void onSuccess() { + if (AppConfig.DEBUG) + Log.d(TAG, "Download was successful"); + status.setSuccessful(true); + status.setDone(true); + } + + private void onFail(int reason) { + if (AppConfig.DEBUG) { + Log.d(TAG, "Download failed"); + } + status.setReason(reason); + status.setDone(true); + status.setSuccessful(false); + } + + private void onCancelled() { + if (AppConfig.DEBUG) + Log.d(TAG, "Download was cancelled"); + status.setReason(DownloadError.ERROR_DOWNLOAD_CANCELLED); + status.setDone(true); + status.setSuccessful(false); + } + +} diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java index 5e2af1ba5..13c8f58d0 100644 --- a/src/de/danoeh/antennapod/storage/DownloadRequester.java +++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java @@ -1,17 +1,11 @@ package de.danoeh.antennapod.storage; import java.io.File; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; -import android.annotation.SuppressLint; -import android.app.DownloadManager; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; -import android.net.Uri; -import android.os.IBinder; import android.util.Log; import android.webkit.URLUtil; import de.danoeh.antennapod.AppConfig; @@ -19,31 +13,28 @@ import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedFile; import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedMedia; -import de.danoeh.antennapod.service.DownloadService; +import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.util.NumberGenerator; import de.danoeh.antennapod.util.URLChecker; -public class DownloadRequester {// TODO handle externalstorage missing +public class DownloadRequester { private static final String TAG = "DownloadRequester"; - private static final int currentApi = android.os.Build.VERSION.SDK_INT; public static String EXTRA_DOWNLOAD_ID = "extra.de.danoeh.antennapod.storage.download_id"; public static String EXTRA_ITEM_ID = "extra.de.danoeh.antennapod.storage.item_id"; public static String ACTION_DOWNLOAD_QUEUED = "action.de.danoeh.antennapod.storage.downloadQueued"; - private static boolean STORE_ON_SD = true; public static String IMAGE_DOWNLOADPATH = "images/"; public static String FEED_DOWNLOADPATH = "cache/"; public static String MEDIA_DOWNLOADPATH = "media/"; private static DownloadRequester downloader; - private DownloadManager manager; - private List<FeedFile> downloads; + Map<String, FeedFile> downloads; private DownloadRequester() { - downloads = new CopyOnWriteArrayList<FeedFile>(); + downloads = new ConcurrentHashMap<String, FeedFile>(); } public static DownloadRequester getInstance() { @@ -53,8 +44,7 @@ public class DownloadRequester {// TODO handle externalstorage missing return downloader; } - @SuppressLint("NewApi") - private long download(Context context, FeedFile item, File dest) { + private void download(Context context, FeedFile item, File dest) { if (!isDownloadingFile(item)) { if (dest.exists()) { if (AppConfig.DEBUG) @@ -64,106 +54,75 @@ public class DownloadRequester {// TODO handle externalstorage missing if (AppConfig.DEBUG) Log.d(TAG, "Requesting download of url " + item.getDownload_url()); - downloads.add(item); item.setDownload_url(URLChecker.prepareURL(item.getDownload_url())); - DownloadManager.Request request = new DownloadManager.Request( - Uri.parse(item.getDownload_url())).setDestinationUri(Uri - .fromFile(dest)); - if (AppConfig.DEBUG) - Log.d(TAG, "Version is " + currentApi); - if (currentApi >= 11) { - request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN); + item.setFile_url(dest.toString()); + downloads.put(item.getDownload_url(), item); + + DownloadService.Request request = new DownloadService.Request( + item.getFile_url(), item.getDownload_url()); + + if (!DownloadService.isRunning) { + Intent launchIntent = new Intent(context, DownloadService.class); + launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request); + context.startService(launchIntent); } else { - request.setVisibleInDownloadsUi(false); - request.setShowRunningNotification(false); + Intent queueIntent = new Intent( + DownloadService.ACTION_ENQUEUE_DOWNLOAD); + queueIntent.putExtra(DownloadService.EXTRA_REQUEST, request); + context.sendBroadcast(queueIntent); } - - // TODO Set Allowed Network Types - DownloadManager manager = (DownloadManager) context - .getSystemService(Context.DOWNLOAD_SERVICE); - - long downloadId = manager.enqueue(request); - item.setDownloadId(downloadId); - item.setFile_url(dest.toString()); - context.startService(new Intent(context, DownloadService.class)); context.sendBroadcast(new Intent(ACTION_DOWNLOAD_QUEUED)); - return downloadId; } else { Log.e(TAG, "URL " + item.getDownload_url() + " is already being downloaded"); - return 0; } } - public long downloadFeed(Context context, Feed feed) { - return download(context, feed, new File(getFeedfilePath(context), + public void downloadFeed(Context context, Feed feed) { + download(context, feed, new File(getFeedfilePath(context), getFeedfileName(feed))); } - public long downloadImage(Context context, FeedImage image) { - return download(context, image, new File(getImagefilePath(context), + public void downloadImage(Context context, FeedImage image) { + download(context, image, new File(getImagefilePath(context), getImagefileName(image))); } - public long downloadMedia(Context context, FeedMedia feedmedia) { - return download(context, feedmedia, + public void downloadMedia(Context context, FeedMedia feedmedia) { + download(context, feedmedia, new File(getMediafilePath(context, feedmedia), getMediafilename(feedmedia))); } /** * Cancels a running download. - * - * @param context - * A context needed to get the DownloadManager service - * @param id - * ID of the download to cancel * */ - public void cancelDownload(final Context context, final long id) { + public void cancelDownload(final Context context, final FeedFile f) { + cancelDownload(context, f.getDownload_url()); + } + + /** + * Cancels a running download. + * */ + public void cancelDownload(final Context context, final String downloadUrl) { if (AppConfig.DEBUG) - Log.d(TAG, "Cancelling download with id " + id); - DownloadManager dm = (DownloadManager) context - .getSystemService(Context.DOWNLOAD_SERVICE); - int removed = dm.remove(id); - if (removed > 0) { - FeedFile f = getFeedFile(id); - if (f != null) { - downloads.remove(f); - f.setFile_url(null); - f.setDownloadId(0); - } - notifyDownloadService(context); - } + Log.d(TAG, "Cancelling download with url " + downloadUrl); + Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD); + cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl); + context.sendBroadcast(cancelIntent); } /** Cancels all running downloads */ public void cancelAllDownloads(Context context) { if (AppConfig.DEBUG) Log.d(TAG, "Cancelling all running downloads"); - DownloadManager dm = (DownloadManager) context - .getSystemService(Context.DOWNLOAD_SERVICE); - for (FeedFile f : downloads) { - dm.remove(f.getDownloadId()); - f.setFile_url(null); - f.setDownloadId(0); - } - downloads.clear(); - notifyDownloadService(context); - } - - /** Get a feedfile by its download id */ - public FeedFile getFeedFile(long id) { - for (FeedFile f : downloads) { - if (f.getDownloadId() == id) { - return f; - } - } - return null; + context.sendBroadcast(new Intent( + DownloadService.ACTION_CANCEL_ALL_DOWNLOADS)); } /** Returns true if there is at least one Feed in the downloads queue. */ public boolean isDownloadingFeeds() { - for (FeedFile f : downloads) { + for (FeedFile f : downloads.values()) { if (f.getClass() == Feed.class) { return true; } @@ -173,22 +132,19 @@ public class DownloadRequester {// TODO handle externalstorage missing /** Checks if feedfile is in the downloads list */ public boolean isDownloadingFile(FeedFile item) { - for (FeedFile f : downloads) { - if (f.getDownload_url().equals(item.getDownload_url())) { - return true; - } + if (item.getDownload_url() != null) { + return downloads.containsKey(item.getDownload_url()); } return false; } + public FeedFile getDownload(String downloadUrl) { + return downloads.get(downloadUrl); + } + /** Checks if feedfile with the given download url is in the downloads list */ public boolean isDownloadingFile(String downloadUrl) { - for (FeedFile f : downloads) { - if (f.getDownload_url().equals(downloadUrl)) { - return true; - } - } - return false; + return downloads.get(downloadUrl) != null; } public boolean hasNoDownloads() { @@ -201,11 +157,9 @@ public class DownloadRequester {// TODO handle externalstorage missing /** Remove an object from the downloads-list of the requester. */ public void removeDownload(FeedFile f) { - downloads.remove(f); - } - - public List<FeedFile> getDownloads() { - return downloads; + if (downloads.remove(f.getDownload_url()) == null) { + Log.e(TAG, "Could not remove object with url " + f.getDownload_url()); + } } /** Get the number of uncompleted Downloads */ @@ -241,8 +195,4 @@ public class DownloadRequester {// TODO handle externalstorage missing media.getMime_type()); } - /** Notifies the DownloadService to check if there are any Downloads left */ - public void notifyDownloadService(Context context) { - context.sendBroadcast(new Intent(DownloadService.ACTION_NOTIFY_DOWNLOADS_CHANGED)); - } } diff --git a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java index 00f77d65a..139cdf650 100644 --- a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java +++ b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java @@ -10,49 +10,52 @@ import android.util.Log; /** Parses several date formats. */ public class SyndDateUtils { private static final String TAG = "DateUtils"; - public static final String RFC822 = "dd MMM yyyy HH:mm:ss Z"; - /** RFC 822 date format with day of the week. */ - public static final String RFC822DAY = "EEE, " + RFC822; + + public static final String[] RFC822DATES = { "EEE, dd MMM yyyy HH:mm:ss Z", + "dd MMM yyyy HH:mm:ss Z", "EEE, dd MMM yy HH:mm:ss Z", + "dd MMM yy HH:mm:ss Z", "EEE, dd MMM yyyy HH:mm:ss z", + "dd MMM yyyy HH:mm:ss z", "EEE, dd MMM yy HH:mm:ss z", + "dd MMM yy HH:mm:ss z" }; /** RFC 3339 date format for UTC dates. */ public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'"; /** RFC 3339 date format for localtime dates with offset. */ public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ"; - + private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() { @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat(RFC822DAY, Locale.US); + protected SimpleDateFormat initialValue() { + return new SimpleDateFormat(RFC822DATES[0], Locale.US); } - + }; - + private static ThreadLocal<SimpleDateFormat> RFC3339Formatter = new ThreadLocal<SimpleDateFormat>() { @Override - protected SimpleDateFormat initialValue() { + protected SimpleDateFormat initialValue() { return new SimpleDateFormat(RFC3339UTC, Locale.US); } - + }; - public static Date parseRFC822Date(final String date) { + public static Date parseRFC822Date(String date) { Date result = null; + if (date.contains("PDT")) { + date = date.replace("PDT", "PST8PDT"); + } SimpleDateFormat format = RFC822Formatter.get(); - try { - result = format.parse(date); - } catch (ParseException e) { - e.printStackTrace(); - format.applyPattern(RFC822); + for (int i = 0; i < RFC822DATES.length; i++) { try { - result = format.parse(date); - } catch (ParseException e1) { - e1.printStackTrace(); - Log.e(TAG, "Unable to parse feed date correctly"); - } finally { - format.applyPattern(RFC822DAY); // apply old pattern again + result = format.parse(date); + break; + } catch (ParseException e) { + e.printStackTrace(); } } + if (result == null) { + Log.e(TAG, "Unable to parse feed date correctly"); + } return result; } @@ -90,7 +93,11 @@ public class SyndDateUtils { return result; } - /** Takes a string of the form [HH:]MM:SS[.mmm] and converts it to milliseconds. */ + + /** + * Takes a string of the form [HH:]MM:SS[.mmm] and converts it to + * milliseconds. + */ public static long parseTimeString(final String time) { String[] parts = time.split(":"); long result = 0; @@ -102,7 +109,7 @@ public class SyndDateUtils { } result += Integer.valueOf(parts[idx]) * 60000; idx++; - result += ( Float.valueOf(parts[idx])) * 1000; + result += (Float.valueOf(parts[idx])) * 1000; return result; } } diff --git a/src/de/danoeh/antennapod/util/DownloadError.java b/src/de/danoeh/antennapod/util/DownloadError.java index b2f43a8dd..c3f44672f 100644 --- a/src/de/danoeh/antennapod/util/DownloadError.java +++ b/src/de/danoeh/antennapod/util/DownloadError.java @@ -1,27 +1,35 @@ package de.danoeh.antennapod.util; -import de.danoeh.antennapod.R; -import android.app.DownloadManager; import android.content.Context; +import de.danoeh.antennapod.R; /** Utility class for Download Errors. */ public class DownloadError { public static final int ERROR_PARSER_EXCEPTION = 1; public static final int ERROR_UNSUPPORTED_TYPE = 2; public static final int ERROR_CONNECTION_ERROR = 3; - + public static final int ERROR_MALFORMED_URL = 4; + public static final int ERROR_IO_ERROR = 5; + public static final int ERROR_FILE_EXISTS = 6; + public static final int ERROR_DOWNLOAD_CANCELLED = 7; + public static final int ERROR_DEVICE_NOT_FOUND = 8; + public static final int ERROR_HTTP_DATA_ERROR = 9; + public static final int ERROR_NOT_ENOUGH_SPACE = 10; /** Get a human-readable string for a specific error code. */ public static String getErrorString(Context context, int code) { int resId; switch(code) { - case DownloadManager.ERROR_DEVICE_NOT_FOUND: + case ERROR_NOT_ENOUGH_SPACE: resId = R.string.download_error_insufficient_space; break; - case DownloadManager.ERROR_FILE_ERROR: - resId = R.string.download_error_file_error; + case ERROR_DEVICE_NOT_FOUND: + resId = R.string.download_error_device_not_found; + break; + case ERROR_IO_ERROR: + resId = R.string.download_error_io_error; break; - case DownloadManager.ERROR_HTTP_DATA_ERROR: + case ERROR_HTTP_DATA_ERROR: resId = R.string.download_error_http_data_error; break; case ERROR_PARSER_EXCEPTION: diff --git a/src/de/danoeh/antennapod/util/StorageUtils.java b/src/de/danoeh/antennapod/util/StorageUtils.java index 942c333fb..eb3b5d3a4 100644 --- a/src/de/danoeh/antennapod/util/StorageUtils.java +++ b/src/de/danoeh/antennapod/util/StorageUtils.java @@ -4,6 +4,7 @@ import de.danoeh.antennapod.activity.StorageErrorActivity; import android.app.Activity; import android.content.Intent; import android.os.Environment; +import android.os.StatFs; /** Utility functions for handling storage errors */ public class StorageUtils { @@ -25,4 +26,14 @@ public class StorageUtils { } return storageAvailable; } + + /** Get the number of free bytes that are available on the external storage. */ + public static int getFreeSpaceAvailable() { + StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath()); + return stat.getAvailableBlocks() * stat.getBlockSize(); + } + + public static boolean externalStorageMounted() { + return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); + } } diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java index 24100d8c4..d88902c6f 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java @@ -81,8 +81,7 @@ public class FeedItemMenuHandler { manager.deleteFeedMedia(context, selectedItem.getMedia()); break; case R.id.cancel_download_item: - requester.cancelDownload(context, selectedItem.getMedia() - .getDownloadId()); + requester.cancelDownload(context, selectedItem.getMedia()); break; case R.id.mark_read_item: manager.markItemRead(context, selectedItem, true); diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java index 38adc0b50..6bb1478d6 100644 --- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java +++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java @@ -15,7 +15,7 @@ import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; -import de.danoeh.antennapod.service.DownloadService; +import de.danoeh.antennapod.service.download.DownloadService; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.util.ShareUtils; import de.danoeh.antennapod.AppConfig; |