summaryrefslogtreecommitdiff
path: root/src/de
diff options
context:
space:
mode:
Diffstat (limited to 'src/de')
-rw-r--r--src/de/danoeh/antennapod/PodcastApp.java3
-rw-r--r--src/de/danoeh/antennapod/activity/AboutActivity.java4
-rw-r--r--src/de/danoeh/antennapod/activity/AddFeedActivity.java17
-rw-r--r--src/de/danoeh/antennapod/activity/AudioplayerActivity.java82
-rw-r--r--src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java19
-rw-r--r--src/de/danoeh/antennapod/activity/DownloadActivity.java451
-rw-r--r--src/de/danoeh/antennapod/activity/DownloadLogActivity.java76
-rw-r--r--src/de/danoeh/antennapod/activity/FeedInfoActivity.java193
-rw-r--r--src/de/danoeh/antennapod/activity/FeedItemlistActivity.java314
-rw-r--r--src/de/danoeh/antennapod/activity/FlattrAuthActivity.java9
-rw-r--r--src/de/danoeh/antennapod/activity/ItemviewActivity.java129
-rw-r--r--src/de/danoeh/antennapod/activity/MainActivity.java67
-rw-r--r--src/de/danoeh/antennapod/activity/MediaplayerActivity.java28
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java15
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java309
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java277
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java126
-rw-r--r--src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java49
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java219
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java5
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java4
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java14
-rw-r--r--src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java112
-rw-r--r--src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java18
-rw-r--r--src/de/danoeh/antennapod/activity/PreferenceActivity.java705
-rw-r--r--src/de/danoeh/antennapod/activity/SearchActivity.java335
-rw-r--r--src/de/danoeh/antennapod/activity/StorageErrorActivity.java5
-rw-r--r--src/de/danoeh/antennapod/activity/VideoplayerActivity.java7
-rw-r--r--src/de/danoeh/antennapod/adapter/ChapterListAdapter.java47
-rw-r--r--src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java21
-rw-r--r--src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java39
-rw-r--r--src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java31
-rw-r--r--src/de/danoeh/antennapod/adapter/FeedlistAdapter.java94
-rw-r--r--src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java11
-rw-r--r--src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java2
-rw-r--r--src/de/danoeh/antennapod/asynctask/DownloadStatus.java198
-rw-r--r--src/de/danoeh/antennapod/asynctask/FeedRemover.java15
-rw-r--r--src/de/danoeh/antennapod/asynctask/ImageLoader.java2
-rw-r--r--src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java21
-rw-r--r--src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java3
-rw-r--r--src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java7
-rw-r--r--src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java100
-rw-r--r--src/de/danoeh/antennapod/feed/EventDistributor.java4
-rw-r--r--src/de/danoeh/antennapod/feed/Feed.java683
-rw-r--r--src/de/danoeh/antennapod/feed/FeedImage.java2
-rw-r--r--src/de/danoeh/antennapod/feed/FeedItem.java560
-rw-r--r--src/de/danoeh/antennapod/feed/FeedManager.java2014
-rw-r--r--src/de/danoeh/antennapod/feed/FeedMedia.java734
-rw-r--r--src/de/danoeh/antennapod/feed/FeedSearcher.java253
-rw-r--r--src/de/danoeh/antennapod/feed/SearchResult.java3
-rw-r--r--src/de/danoeh/antennapod/fragment/CoverFragment.java5
-rw-r--r--src/de/danoeh/antennapod/fragment/EpisodesFragment.java115
-rw-r--r--src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java16
-rw-r--r--src/de/danoeh/antennapod/fragment/FeedlistFragment.java489
-rw-r--r--src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java859
-rw-r--r--src/de/danoeh/antennapod/fragment/ItemlistFragment.java183
-rw-r--r--src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java5
-rw-r--r--src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java90
-rw-r--r--src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java14
-rw-r--r--src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java4
-rw-r--r--src/de/danoeh/antennapod/preferences/UserPreferences.java77
-rw-r--r--src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java4
-rw-r--r--src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java4
-rw-r--r--src/de/danoeh/antennapod/service/PlaybackService.java1600
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadRequest.java177
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadService.java1750
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadStatus.java182
-rw-r--r--src/de/danoeh/antennapod/service/download/Downloader.java51
-rw-r--r--src/de/danoeh/antennapod/service/download/HttpDownloader.java352
-rw-r--r--src/de/danoeh/antennapod/storage/DBReader.java757
-rw-r--r--src/de/danoeh/antennapod/storage/DBTasks.java740
-rw-r--r--src/de/danoeh/antennapod/storage/DBWriter.java731
-rw-r--r--src/de/danoeh/antennapod/storage/DownloadRequester.java566
-rw-r--r--src/de/danoeh/antennapod/storage/FeedItemStatistics.java42
-rw-r--r--src/de/danoeh/antennapod/storage/FeedSearcher.java57
-rw-r--r--src/de/danoeh/antennapod/storage/PodDBAdapter.java1840
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java2
-rw-r--r--src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java8
-rw-r--r--src/de/danoeh/antennapod/util/ChapterUtils.java8
-rw-r--r--src/de/danoeh/antennapod/util/ConnectionTester.java6
-rw-r--r--src/de/danoeh/antennapod/util/DownloadError.java86
-rw-r--r--src/de/danoeh/antennapod/util/DuckType.java115
-rw-r--r--src/de/danoeh/antennapod/util/LangUtils.java3
-rw-r--r--src/de/danoeh/antennapod/util/QueueAccess.java93
-rw-r--r--src/de/danoeh/antennapod/util/ShownotesProvider.java17
-rw-r--r--src/de/danoeh/antennapod/util/URLChecker.java11
-rw-r--r--src/de/danoeh/antennapod/util/UndoBarController.java14
-rw-r--r--src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java5
-rw-r--r--src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java2
-rw-r--r--src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java4
-rw-r--r--src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java37
-rw-r--r--src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java19
-rw-r--r--src/de/danoeh/antennapod/util/playback/AudioPlayer.java30
-rw-r--r--src/de/danoeh/antennapod/util/playback/ExternalMedia.java18
-rw-r--r--src/de/danoeh/antennapod/util/playback/IPlayer.java64
-rw-r--r--src/de/danoeh/antennapod/util/playback/Playable.java513
-rw-r--r--src/de/danoeh/antennapod/util/playback/PlaybackController.java1343
-rw-r--r--src/de/danoeh/antennapod/util/playback/VideoPlayer.java62
98 files changed, 11815 insertions, 9791 deletions
diff --git a/src/de/danoeh/antennapod/PodcastApp.java b/src/de/danoeh/antennapod/PodcastApp.java
index e9f46256b..2141f71e8 100644
--- a/src/de/danoeh/antennapod/PodcastApp.java
+++ b/src/de/danoeh/antennapod/PodcastApp.java
@@ -5,7 +5,6 @@ import android.content.res.Configuration;
import android.util.Log;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.EventDistributor;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -32,8 +31,6 @@ public class PodcastApp extends Application {
UserPreferences.createInstance(this);
PlaybackPreferences.createInstance(this);
EventDistributor.getInstance();
- FeedManager manager = FeedManager.getInstance();
- manager.loadDBData(getApplicationContext());
}
@Override
diff --git a/src/de/danoeh/antennapod/activity/AboutActivity.java b/src/de/danoeh/antennapod/activity/AboutActivity.java
index e3265d1eb..27fdbe241 100644
--- a/src/de/danoeh/antennapod/activity/AboutActivity.java
+++ b/src/de/danoeh/antennapod/activity/AboutActivity.java
@@ -1,15 +1,15 @@
package de.danoeh.antennapod.activity;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import com.actionbarsherlock.app.SherlockActivity;
import de.danoeh.antennapod.R;
/** Displays the 'about' screen */
-public class AboutActivity extends SherlockActivity {
+public class AboutActivity extends ActionBarActivity {
private WebView webview;
diff --git a/src/de/danoeh/antennapod/activity/AddFeedActivity.java b/src/de/danoeh/antennapod/activity/AddFeedActivity.java
index 7e702f28b..4085fc8d2 100644
--- a/src/de/danoeh/antennapod/activity/AddFeedActivity.java
+++ b/src/de/danoeh/antennapod/activity/AddFeedActivity.java
@@ -2,6 +2,9 @@ package de.danoeh.antennapod.activity;
import java.util.Date;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
import org.apache.commons.lang3.StringUtils;
import android.app.AlertDialog;
@@ -15,10 +18,6 @@ import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.Feed;
@@ -31,7 +30,7 @@ import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.URLChecker;
/** Activity for adding a Feed */
-public class AddFeedActivity extends SherlockActivity {
+public class AddFeedActivity extends ActionBarActivity {
private static final String TAG = "AddFeedActivity";
private DownloadRequester requester;
@@ -161,7 +160,7 @@ public class AddFeedActivity extends SherlockActivity {
}
@Override
- public void onConnectionFailure(int reason) {
+ public void onConnectionFailure(DownloadError reason) {
handleDownloadError(reason);
}
});
@@ -177,11 +176,11 @@ public class AddFeedActivity extends SherlockActivity {
progDialog.setMessage(getString(R.string.loading_label));
}
- private void handleDownloadError(int reason) {
+ private void handleDownloadError(DownloadError reason) {
final AlertDialog errorDialog = new AlertDialog.Builder(this).create();
errorDialog.setTitle(R.string.error_label);
errorDialog.setMessage(getString(R.string.error_msg_prefix) + " "
- + DownloadError.getErrorString(this, reason));
+ + reason.getErrorString(this));
errorDialog.setButton(getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
@@ -200,6 +199,8 @@ public class AddFeedActivity extends SherlockActivity {
return true;
}
+
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
index b73a17125..db4373036 100644
--- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
+++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
@@ -7,27 +7,31 @@ import android.content.res.TypedArray;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.Window;
+import android.view.View.OnLongClickListener;
import android.widget.ArrayAdapter;
+import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockListFragment;
-import com.actionbarsherlock.view.Window;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter;
import de.danoeh.antennapod.asynctask.ImageLoader;
+import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.feed.SimpleChapter;
import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
+import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable;
@@ -48,7 +52,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
private CoverFragment coverFragment;
private ItemDescriptionFragment descriptionFragment;
- private SherlockListFragment chapterFragment;
+ private ListFragment chapterFragment;
private Fragment currentlyShownFragment;
private int currentlyShownPosition = -1;
@@ -57,6 +61,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
private TextView txtvTitle;
private TextView txtvFeed;
+ private Button butPlaybackSpeed;
private ImageButton butNavLeft;
private ImageButton butNavRight;
@@ -219,7 +224,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
if (savedPosition != -1) {
switchToFragment(savedPosition);
}
-
+
}
@Override
@@ -248,8 +253,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
/**
* Changes the currently displayed fragment.
*
- * @param Must
- * be POS_COVER, POS_DESCR, or POS_CHAPTERS
+ * @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS
* */
private void switchToFragment(int pos) {
if (AppConfig.DEBUG)
@@ -280,7 +284,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
break;
case POS_CHAPTERS:
if (chapterFragment == null) {
- chapterFragment = new SherlockListFragment() {
+ chapterFragment = new ListFragment() {
@Override
public void onListItemClick(ListView l, View v,
@@ -365,6 +369,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
txtvFeed = (TextView) findViewById(R.id.txtvFeed);
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
butNavRight = (ImageButton) findViewById(R.id.butNavRight);
+ butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
butNavLeft.setOnClickListener(new OnClickListener() {
@@ -392,6 +397,65 @@ public class AudioplayerActivity extends MediaplayerActivity {
}
}
});
+
+ butPlaybackSpeed.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (controller != null && controller.canSetPlaybackSpeed()) {
+ String[] availableSpeeds = UserPreferences
+ .getPlaybackSpeedArray();
+ String currentSpeed = UserPreferences.getPlaybackSpeed();
+
+ // Provide initial value in case the speed list has changed
+ // out from under us
+ // and our current speed isn't in the new list
+ String newSpeed;
+ if (availableSpeeds.length > 0) {
+ newSpeed = availableSpeeds[0];
+ } else {
+ newSpeed = "1.0";
+ }
+
+ for (int i = 0; i < availableSpeeds.length; i++) {
+ if (availableSpeeds[i].equals(currentSpeed)) {
+ if (i == availableSpeeds.length - 1) {
+ newSpeed = availableSpeeds[0];
+ } else {
+ newSpeed = availableSpeeds[i + 1];
+ }
+ break;
+ }
+ }
+ UserPreferences.setPlaybackSpeed(newSpeed);
+ controller.setPlaybackSpeed(Float.parseFloat(newSpeed));
+ }
+ }
+ });
+
+ butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ VariableSpeedDialog.showDialog(AudioplayerActivity.this);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void onPlaybackSpeedChange() {
+ super.onPlaybackSpeedChange();
+ updateButPlaybackSpeed();
+ }
+
+ private void updateButPlaybackSpeed() {
+ if (controller == null
+ || (controller.getCurrentPlaybackSpeedMultiplier() == -1)) {
+ butPlaybackSpeed.setVisibility(View.GONE);
+ } else {
+ butPlaybackSpeed.setVisibility(View.VISIBLE);
+ butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
+ }
}
@Override
@@ -423,7 +487,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
((AudioplayerContentFragment) currentlyShownFragment)
.onDataSetChanged(media);
}
-
+ updateButPlaybackSpeed();
}
public void notifyMediaPositionChanged() {
diff --git a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
index 54c4f0589..62273c960 100644
--- a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
+++ b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
@@ -12,7 +12,11 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileObserver;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
@@ -24,11 +28,6 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -37,7 +36,7 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Let's the user choose a directory on the storage device. The selected folder
* will be sent back to the starting activity as an activity result.
*/
-public class DirectoryChooserActivity extends SherlockActivity {
+public class DirectoryChooserActivity extends ActionBarActivity {
private static final String TAG = "DirectoryChooserActivity";
private static final String CREATE_DIRECTORY_NAME = "AntennaPod";
@@ -348,7 +347,9 @@ public class DirectoryChooserActivity extends SherlockActivity {
* CREATE_DIRECTORY_NAME.
*/
private int createFolder() {
- if (selectedDir != null && selectedDir.canWrite()) {
+ if (selectedDir == null) {
+ return R.string.create_folder_error;
+ } else if (selectedDir.canWrite()) {
File newDir = new File(selectedDir, CREATE_DIRECTORY_NAME);
if (!newDir.exists()) {
boolean result = newDir.mkdir();
@@ -360,10 +361,8 @@ public class DirectoryChooserActivity extends SherlockActivity {
} else {
return R.string.create_folder_error_already_exists;
}
- } else if (selectedDir.canWrite() == false) {
- return R.string.create_folder_error_no_write_access;
} else {
- return R.string.create_folder_error;
+ return R.string.create_folder_error_no_write_access;
}
}
diff --git a/src/de/danoeh/antennapod/activity/DownloadActivity.java b/src/de/danoeh/antennapod/activity/DownloadActivity.java
index 10ebb1285..57c86f760 100644
--- a/src/de/danoeh/antennapod/activity/DownloadActivity.java
+++ b/src/de/danoeh/antennapod/activity/DownloadActivity.java
@@ -11,21 +11,23 @@ import android.content.res.TypedArray;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.view.ActionMode;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.ActionMode;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
+import android.widget.ListView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DownloadlistAdapter;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.storage.DownloadRequester;
@@ -33,221 +35,228 @@ import de.danoeh.antennapod.storage.DownloadRequester;
* Shows all running downloads in a list. The list objects are DownloadStatus
* objects created by a DownloadObserver.
*/
-public class DownloadActivity extends SherlockListActivity implements
- ActionMode.Callback {
-
- private static final String TAG = "DownloadActivity";
- private static final int MENU_SHOW_LOG = 0;
- private static final int MENU_CANCEL_ALL_DOWNLOADS = 1;
- private DownloadlistAdapter dla;
- private DownloadRequester requester;
-
- private ActionMode mActionMode;
- private DownloadStatus selectedDownload;
-
- private DownloadService downloadService = null;
- boolean mIsBound;
-
- private AsyncTask<Void, Void, Void> contentRefresher;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating Activity");
- requester = DownloadRequester.getInstance();
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- unbindService(mConnection);
- unregisterReceiver(contentChanged);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- registerReceiver(contentChanged, new IntentFilter(
- DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
- bindService(new Intent(this, DownloadService.class), mConnection, 0);
- startContentRefresher();
- if (dla != null) {
- dla.notifyDataSetChanged();
- }
- }
-
- @Override
- protected void onStop() {
- 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
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- getListView().setOnItemLongClickListener(new OnItemLongClickListener() {
-
- @Override
- public boolean onItemLongClick(AdapterView<?> arg0, View view,
- int position, long id) {
- DownloadStatus selection = dla.getItem(position).getStatus();
- if (selection != null && mActionMode != null) {
- mActionMode.finish();
- }
- dla.setSelectedItemIndex(position);
- selectedDownload = selection;
- mActionMode = startActionMode(DownloadActivity.this);
- return true;
- }
-
- });
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, MENU_SHOW_LOG, Menu.NONE,
- R.string.show_download_log).setShowAsAction(
- MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
- menu.add(Menu.NONE, MENU_CANCEL_ALL_DOWNLOADS, Menu.NONE,
- R.string.cancel_all_downloads_label).setShowAsAction(
- MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- break;
- case MENU_SHOW_LOG:
- startActivity(new Intent(this, DownloadLogActivity.class));
- break;
- case MENU_CANCEL_ALL_DOWNLOADS:
- requester.cancelAllDownloads(this);
- break;
- }
- return true;
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- if (!selectedDownload.isDone()) {
- TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.navigation_cancel });
- menu.add(Menu.NONE, R.id.cancel_download_item, Menu.NONE,
- R.string.cancel_download_label).setIcon(
- drawables.getDrawable(0));
- }
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- boolean handled = false;
- switch (item.getItemId()) {
- case R.id.cancel_download_item:
- requester.cancelDownload(this, selectedDownload.getFeedFile());
- handled = true;
- break;
- }
- mActionMode.finish();
- return handled;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- mActionMode = null;
- selectedDownload = null;
- dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE);
- }
-
- private BroadcastReceiver contentChanged = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (dla != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Refreshing content");
- dla.notifyDataSetChanged();
- }
- }
- };
+public class DownloadActivity extends ActionBarActivity implements
+ ActionMode.Callback {
+
+ private static final String TAG = "DownloadActivity";
+ private static final int MENU_SHOW_LOG = 0;
+ private static final int MENU_CANCEL_ALL_DOWNLOADS = 1;
+ private DownloadlistAdapter dla;
+ private DownloadRequester requester;
+
+ private ActionMode mActionMode;
+ private DownloadRequest selectedDownload;
+
+ private DownloadService downloadService = null;
+ boolean mIsBound;
+
+ private AsyncTask<Void, Void, Void> contentRefresher;
+
+ private ListView listview;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.listview_activity);
+
+ listview = (ListView) findViewById(R.id.listview);
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating Activity");
+ requester = DownloadRequester.getInstance();
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unbindService(mConnection);
+ unregisterReceiver(contentChanged);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ registerReceiver(contentChanged, new IntentFilter(
+ DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
+ bindService(new Intent(this, DownloadService.class), mConnection, 0);
+ startContentRefresher();
+ if (dla != null) {
+ dla.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ 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());
+ listview.setAdapter(dla);
+ dla.notifyDataSetChanged();
+ }
+ };
+
+ @SuppressLint("NewApi")
+ private void startContentRefresher() {
+ if (contentRefresher != null) {
+ contentRefresher.cancel(true);
+ }
+ contentRefresher = new AsyncTask<Void, Void, Void>() {
+ private static final int WAITING_INTERVAL = 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_INTERVAL);
+ 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
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ listview.setOnItemLongClickListener(new OnItemLongClickListener() {
+
+ @Override
+ public boolean onItemLongClick(AdapterView<?> arg0, View view,
+ int position, long id) {
+ DownloadRequest selection = dla.getItem(position)
+ .getDownloadRequest();
+ if (selection != null && mActionMode != null) {
+ mActionMode.finish();
+ }
+ dla.setSelectedItemIndex(position);
+ selectedDownload = selection;
+ mActionMode = startSupportActionMode(DownloadActivity.this);
+ return true;
+ }
+
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, MENU_SHOW_LOG, Menu.NONE,
+ R.string.show_download_log),
+ MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, MENU_CANCEL_ALL_DOWNLOADS, Menu.NONE,
+ R.string.cancel_all_downloads_label),
+ MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ break;
+ case MENU_SHOW_LOG:
+ startActivity(new Intent(this, DownloadLogActivity.class));
+ break;
+ case MENU_CANCEL_ALL_DOWNLOADS:
+ requester.cancelAllDownloads(this);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ if (selectedDownload != null) {
+ TypedArray drawables = obtainStyledAttributes(new int[]{R.attr.navigation_cancel});
+ menu.add(Menu.NONE, R.id.cancel_download_item, Menu.NONE,
+ R.string.cancel_download_label).setIcon(
+ drawables.getDrawable(0));
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ boolean handled = false;
+ switch (item.getItemId()) {
+ case R.id.cancel_download_item:
+ requester.cancelDownload(this, selectedDownload.getSource());
+ handled = true;
+ break;
+ }
+ mActionMode.finish();
+ return handled;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ mActionMode = null;
+ selectedDownload = null;
+ dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE);
+ }
+
+ private BroadcastReceiver contentChanged = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (dla != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Refreshing content");
+ dla.notifyDataSetChanged();
+ }
+ }
+ };
}
diff --git a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
index 232a7ba1d..5e48371b8 100644
--- a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
+++ b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
@@ -1,35 +1,47 @@
package de.danoeh.antennapod.activity;
+import android.os.AsyncTask;
import android.os.Bundle;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.ListView;
+import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DownloadLogAdapter;
import de.danoeh.antennapod.feed.EventDistributor;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.download.DownloadStatus;
+import de.danoeh.antennapod.storage.DBReader;
+
+import java.util.List;
/**
- * Displays completed and failed downloads in a list. The data comes from the
- * FeedManager.
+ * Displays completed and failed downloads in a list.
*/
-public class DownloadLogActivity extends SherlockListActivity {
+public class DownloadLogActivity extends ActionBarActivity {
private static final String TAG = "DownloadLogActivity";
- DownloadLogAdapter dla;
- FeedManager manager;
+ private List<DownloadStatus> downloadLog;
+ private DownloadLogAdapter dla;
+
+ private ListView listview;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
- manager = FeedManager.getInstance();
+ setContentView(R.layout.listview_activity);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
- dla = new DownloadLogAdapter(this);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setListAdapter(dla);
+ listview = (ListView) findViewById(R.id.listview);
+
+ dla = new DownloadLogAdapter(this, itemAccess);
+ listview.setAdapter(dla);
+ loadData();
}
@Override
@@ -62,12 +74,48 @@ public class DownloadLogActivity extends SherlockListActivity {
return true;
}
+ private void loadData() {
+ AsyncTask<Void, Void, List<DownloadStatus>> loadTask = new AsyncTask<Void, Void, List<DownloadStatus>>() {
+ @Override
+ protected List<DownloadStatus> doInBackground(Void... voids) {
+ return DBReader.getDownloadLog(DownloadLogActivity.this);
+ }
+
+ @Override
+ protected void onPostExecute(List<DownloadStatus> downloadStatuses) {
+ super.onPostExecute(downloadStatuses);
+ if (downloadStatuses != null) {
+ downloadLog = downloadStatuses;
+ if (dla != null) {
+ dla.notifyDataSetChanged();
+ }
+ } else {
+ Log.e(TAG, "Could not load download log");
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
+ private DownloadLogAdapter.ItemAccess itemAccess = new DownloadLogAdapter.ItemAccess() {
+
+ @Override
+ public int getCount() {
+ return (downloadLog != null) ? downloadLog.size() : 0;
+ }
+
+ @Override
+ public DownloadStatus getItem(int position) {
+ return (downloadLog != null) ? downloadLog.get(position) : null;
+ }
+ };
+
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((arg & EventDistributor.DOWNLOADLOG_UPDATE) != 0) {
- dla.notifyDataSetChanged();
+ loadData();
}
}
};
diff --git a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java
index c57a5794b..3cb46a4f8 100644
--- a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java
+++ b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java
@@ -1,113 +1,136 @@
package de.danoeh.antennapod.activity;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.TextView;
-
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.util.LangUtils;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
-/** Displays information about a feed. */
-public class FeedInfoActivity extends SherlockActivity {
- private static final String TAG = "FeedInfoActivity";
+/**
+ * Displays information about a feed.
+ */
+public class FeedInfoActivity extends ActionBarActivity {
+ private static final String TAG = "FeedInfoActivity";
+
+ public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId";
+
+ private Feed feed;
- public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId";
+ private ImageView imgvCover;
+ private TextView txtvTitle;
+ private TextView txtvDescription;
+ private TextView txtvLanguage;
+ private TextView txtvAuthor;
- private Feed feed;
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.feedinfo);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ long feedId = getIntent().getLongExtra(EXTRA_FEED_ID, -1);
- private ImageView imgvCover;
- private TextView txtvTitle;
- private TextView txtvDescription;
- private TextView txtvLanguage;
- private TextView txtvAuthor;
+ AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- setContentView(R.layout.feedinfo);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- long feedId = getIntent().getLongExtra(EXTRA_FEED_ID, -1);
- FeedManager manager = FeedManager.getInstance();
- feed = manager.getFeed(feedId);
- if (feed != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Language is " + feed.getLanguage());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Author is " + feed.getAuthor());
- imgvCover = (ImageView) findViewById(R.id.imgvCover);
- txtvTitle = (TextView) findViewById(R.id.txtvTitle);
- txtvDescription = (TextView) findViewById(R.id.txtvDescription);
- txtvLanguage = (TextView) findViewById(R.id.txtvLanguage);
- txtvAuthor = (TextView) findViewById(R.id.txtvAuthor);
- imgvCover.post(new Runnable() {
+ @Override
+ protected Feed doInBackground(Long... params) {
+ return DBReader.getFeed(FeedInfoActivity.this, params[0]);
+ }
- @Override
- public void run() {
- ImageLoader.getInstance().loadThumbnailBitmap(
- feed.getImage(), imgvCover);
- }
- });
+ @Override
+ protected void onPostExecute(Feed result) {
+ super.onPostExecute(result);
+ if (result != null) {
+ feed = result;
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Language is " + feed.getLanguage());
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Author is " + feed.getAuthor());
+ imgvCover = (ImageView) findViewById(R.id.imgvCover);
+ txtvTitle = (TextView) findViewById(R.id.txtvTitle);
+ txtvDescription = (TextView) findViewById(R.id.txtvDescription);
+ txtvLanguage = (TextView) findViewById(R.id.txtvLanguage);
+ txtvAuthor = (TextView) findViewById(R.id.txtvAuthor);
+ imgvCover.post(new Runnable() {
- txtvTitle.setText(feed.getTitle());
- txtvDescription.setText(feed.getDescription());
- if (feed.getAuthor() != null) {
- txtvAuthor.setText(feed.getAuthor());
- }
- if (feed.getLanguage() != null) {
- txtvLanguage.setText(LangUtils.getLanguageString(feed
- .getLanguage()));
- }
- } else {
- Log.e(TAG, "Activity was started with invalid arguments");
- }
+ @Override
+ public void run() {
+ ImageLoader.getInstance().loadThumbnailBitmap(
+ feed.getImage(), imgvCover);
+ }
+ });
- }
+ txtvTitle.setText(feed.getTitle());
+ txtvDescription.setText(feed.getDescription());
+ if (feed.getAuthor() != null) {
+ txtvAuthor.setText(feed.getAuthor());
+ }
+ if (feed.getLanguage() != null) {
+ txtvLanguage.setText(LangUtils
+ .getLanguageString(feed.getLanguage()));
+ }
+ supportInvalidateOptionsMenu();
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = new MenuInflater(this);
- inflater.inflate(R.menu.feedinfo, menu);
- return true;
- }
+ } else {
+ Log.e(TAG, "Activity was started with invalid arguments");
+ }
+ }
+ };
+ loadTask.execute(feedId);
+ }
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- menu.findItem(R.id.support_item).setVisible(
- feed.getPaymentLink() != null);
- menu.findItem(R.id.share_link_item).setVisible(feed.getLink() != null);
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (feed != null) {
+ MenuInflater inflater = new MenuInflater(this);
+ inflater.inflate(R.menu.feedinfo, menu);
+ return true;
+ } else {
+ return false;
+ }
+ }
- return true;
- }
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (feed != null) {
+ menu.findItem(R.id.support_item).setVisible(
+ feed.getPaymentLink() != null);
+ menu.findItem(R.id.share_link_item).setVisible(feed.getLink() != null);
+ return true;
+ } else {
+ return false;
+ }
+ }
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- default:
- try {
- return FeedMenuHandler.onOptionsItemClicked(this, item, feed);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, e.getMessage());
- }
- return super.onOptionsItemSelected(item);
- }
- }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ try {
+ return FeedMenuHandler.onOptionsItemClicked(this, item, feed);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
+ e.getMessage());
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
index fdca48e8a..8fba44e5c 100644
--- a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
+++ b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
@@ -1,151 +1,215 @@
package de.danoeh.antennapod.activity;
import android.annotation.SuppressLint;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
+import android.media.AudioManager;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.SearchView;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-import com.actionbarsherlock.view.Window;
-
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Window;
+import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FeedRemover;
import de.danoeh.antennapod.dialog.ConfirmationDialog;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
-/** Displays a List of FeedItems */
-public class FeedItemlistActivity extends SherlockFragmentActivity {
- private static final String TAG = "FeedItemlistActivity";
-
- private FeedManager manager;
-
- /** The feed which the activity displays */
- private Feed feed;
- private ItemlistFragment filf;
- private ExternalPlayerFragment externalPlayerFragment;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- StorageUtils.checkStorageAvailability(this);
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.feeditemlist_activity);
-
- manager = FeedManager.getInstance();
- long feedId = getIntent().getLongExtra(
- FeedlistFragment.EXTRA_SELECTED_FEED, -1);
- if (feedId == -1)
- Log.e(TAG, "Received invalid feed selection.");
-
- feed = manager.getFeed(feedId);
- setTitle(feed.getTitle());
-
- FragmentManager fragmentManager = getSupportFragmentManager();
- FragmentTransaction fT = fragmentManager.beginTransaction();
-
- filf = ItemlistFragment.newInstance(feed.getId());
- fT.replace(R.id.feeditemlistFragment, filf);
-
- externalPlayerFragment = new ExternalPlayerFragment();
- fT.replace(R.id.playerFragment, externalPlayerFragment);
- fT.commit();
-
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- StorageUtils.checkStorageAvailability(this);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.action_search });
- menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
- .setIcon(drawables.getDrawable(0))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
- return FeedMenuHandler
- .onCreateOptionsMenu(new MenuInflater(this), menu);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- return FeedMenuHandler.onPrepareOptionsMenu(menu, feed);
- }
-
- @SuppressLint("NewApi")
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- try {
- if (FeedMenuHandler.onOptionsItemClicked(this, item, feed)) {
- filf.getListAdapter().notifyDataSetChanged();
- } else {
- switch (item.getItemId()) {
- case R.id.remove_item:
- final FeedRemover remover = new FeedRemover(
- FeedItemlistActivity.this, feed) {
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- finish();
- }
- };
- ConfirmationDialog conDialog = new ConfirmationDialog(this,
- R.string.remove_feed_label,
- R.string.feed_delete_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(
- DialogInterface dialog) {
- dialog.dismiss();
- remover.executeAsync();
- }
- };
- conDialog.createNewDialog().show();
- break;
- case R.id.search_item:
- onSearchRequested();
- break;
- case android.R.id.home:
- Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- break;
- }
- }
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
- e.getMessage());
- }
- return true;
- }
-
- @Override
- public boolean onSearchRequested() {
- Bundle bundle = new Bundle();
- bundle.putLong(SearchActivity.EXTRA_FEED_ID, feed.getId());
- startSearch(null, false, bundle, false);
- return true;
- }
+/**
+ * Displays a List of FeedItems
+ */
+public class FeedItemlistActivity extends ActionBarActivity {
+ private static final String TAG = "FeedItemlistActivity";
+
+ /**
+ * The feed which the activity displays
+ */
+ private Feed feed;
+ private ItemlistFragment filf;
+ private ExternalPlayerFragment externalPlayerFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ StorageUtils.checkStorageAvailability(this);
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.feeditemlist_activity);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ long feedId = getIntent().getLongExtra(
+ FeedlistFragment.EXTRA_SELECTED_FEED, -1);
+ if (feedId == -1) {
+ Log.e(TAG, "Received invalid feed selection.");
+ } else {
+ loadData(feedId);
+ }
+
+ }
+
+ private void loadData(long id) {
+ AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() {
+
+ @Override
+ protected Feed doInBackground(Long... longs) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading feed data in background");
+ return DBReader.getFeed(FeedItemlistActivity.this, longs[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Feed result) {
+ super.onPostExecute(result);
+ if (result != null) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Finished loading feed data");
+ feed = result;
+ setTitle(feed.getTitle());
+
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ FragmentTransaction fT = fragmentManager.beginTransaction();
+
+ filf = ItemlistFragment.newInstance(feed.getId());
+ fT.replace(R.id.feeditemlistFragment, filf);
+
+ externalPlayerFragment = new ExternalPlayerFragment();
+ fT.replace(R.id.playerFragment, externalPlayerFragment);
+ fT.commit();
+ supportInvalidateOptionsMenu();
+ } else {
+ Log.e(TAG, "Error: Feed was null");
+ }
+ }
+ };
+ loadTask.execute(id);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ StorageUtils.checkStorageAvailability(this);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (feed != null) {
+ TypedArray drawables = obtainStyledAttributes(new int[]{R.attr.action_search});
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
+ .setIcon(drawables.getDrawable(0)),
+ MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
+ MenuItemCompat.setActionView(menu.findItem(R.id.search_item), new SearchView(this));
+
+ SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
+
+
+ searchView.setIconifiedByDefault(true);
+
+ searchView.setSearchableInfo(
+ searchManager.getSearchableInfo(getComponentName()));
+ return FeedMenuHandler
+ .onCreateOptionsMenu(new MenuInflater(this), menu);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ return FeedMenuHandler.onPrepareOptionsMenu(menu, feed);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ try {
+ if (FeedMenuHandler.onOptionsItemClicked(this, item, feed)) {
+ filf.getListAdapter().notifyDataSetChanged();
+ } else {
+ switch (item.getItemId()) {
+ case R.id.remove_item:
+ final FeedRemover remover = new FeedRemover(
+ FeedItemlistActivity.this, feed) {
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ finish();
+ }
+ };
+ ConfirmationDialog conDialog = new ConfirmationDialog(this,
+ R.string.remove_feed_label,
+ R.string.feed_delete_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(
+ DialogInterface dialog) {
+ dialog.dismiss();
+ remover.executeAsync();
+ }
+ };
+ conDialog.createNewDialog().show();
+ break;
+ case android.R.id.home:
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ break;
+ }
+ }
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
+ e.getMessage());
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ if (feed != null) {
+ Bundle bundle = new Bundle();
+ bundle.putLong(SearchActivity.EXTRA_FEED_ID, feed.getId());
+ startSearch(null, false, bundle, false);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ if (intent.getAction() != null &&
+ intent.getAction().equals(Intent.ACTION_SEARCH)) {
+ addSearchInformation(intent);
+ }
+ super.startActivity(intent);
+ }
+
+ private void addSearchInformation(Intent startIntent) {
+ startIntent.putExtra(SearchActivity.EXTRA_FEED_ID, feed.getId());
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java b/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java
index 75e513816..2ab95e287 100644
--- a/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java
+++ b/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java
@@ -1,6 +1,9 @@
package de.danoeh.antennapod.activity;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
import org.shredzone.flattr4j.exception.FlattrException;
import android.content.Intent;
@@ -12,10 +15,6 @@ import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -23,7 +22,7 @@ import de.danoeh.antennapod.util.flattr.FlattrUtils;
/** Guides the user through the authentication process */
-public class FlattrAuthActivity extends SherlockActivity {
+public class FlattrAuthActivity extends ActionBarActivity {
private static final String TAG = "FlattrAuthActivity";
private TextView txtvExplanation;
diff --git a/src/de/danoeh/antennapod/activity/ItemviewActivity.java b/src/de/danoeh/antennapod/activity/ItemviewActivity.java
index 5ead667dc..c2833760d 100644
--- a/src/de/danoeh/antennapod/activity/ItemviewActivity.java
+++ b/src/de/danoeh/antennapod/activity/ItemviewActivity.java
@@ -1,55 +1,60 @@
package de.danoeh.antennapod.activity;
-import java.text.DateFormat;
-
+import android.media.AudioManager;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.ActionBarActivity;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Window;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-import com.actionbarsherlock.view.Window;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
-import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
-import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
+import java.text.DateFormat;
+
/** Displays a single FeedItem and provides various actions */
-public class ItemviewActivity extends SherlockFragmentActivity {
+public class ItemviewActivity extends ActionBarActivity {
private static final String TAG = "ItemviewActivity";
- private FeedManager manager;
- private FeedItem item;
+ private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED;
- // Widgets
- private TextView txtvTitle;
- private TextView txtvPublished;
+ private FeedItem item;
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
- manager = FeedManager.getInstance();
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
getSupportActionBar().setDisplayShowTitleEnabled(false);
- extractFeeditem();
- populateUI();
+ EventDistributor.getInstance().register(contentUpdate);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ long itemId = getIntent().getLongExtra(
+ ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1);
+ if (itemId == -1) {
+ Log.e(TAG, "Received invalid selection of either feeditem or feed.");
+ } else {
+ loadData(itemId);
+ }
}
@Override
@@ -62,32 +67,43 @@ public class ItemviewActivity extends SherlockFragmentActivity {
@Override
public void onStop() {
super.onStop();
+ EventDistributor.getInstance().unregister(contentUpdate);
if (AppConfig.DEBUG)
Log.d(TAG, "Stopping Activity");
}
- /** Extracts FeedItem object the activity is supposed to display */
- private void extractFeeditem() {
- long itemId = getIntent().getLongExtra(
- ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1);
- long feedId = getIntent().getLongExtra(
- FeedlistFragment.EXTRA_SELECTED_FEED, -1);
- if (itemId == -1 || feedId == -1) {
- Log.e(TAG, "Received invalid selection of either feeditem or feed.");
- }
- Feed feed = manager.getFeed(feedId);
- item = manager.getFeedItem(itemId, feed);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Title of item is " + item.getTitle());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Title of feed is " + item.getFeed().getTitle());
- }
+ private void loadData(long itemId) {
+ AsyncTask<Long, Void, FeedItem> loadTask = new AsyncTask<Long, Void, FeedItem>() {
+
+ @Override
+ protected FeedItem doInBackground(Long... longs) {
+ return DBReader.getFeedItem(ItemviewActivity.this, longs[0]);
+ }
+
+ @Override
+ protected void onPostExecute(FeedItem feedItem) {
+ super.onPostExecute(feedItem);
+ if (feedItem != null && feedItem.getFeed() != null) {
+ item = feedItem;
+ populateUI();
+ supportInvalidateOptionsMenu();
+ } else {
+ if (feedItem == null) {
+ Log.e(TAG, "Error: FeedItem was null");
+ } else if (feedItem.getFeed() == null) {
+ Log.e(TAG, "Error: Feed was null");
+ }
+ }
+ }
+ };
+ loadTask.execute(itemId);
+ }
private void populateUI() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.feeditemview);
- txtvTitle = (TextView) findViewById(R.id.txtvItemname);
- txtvPublished = (TextView) findViewById(R.id.txtvPublished);
+ TextView txtvTitle = (TextView) findViewById(R.id.txtvItemname);
+ TextView txtvPublished = (TextView) findViewById(R.id.txtvPublished);
setTitle(item.getFeed().getTitle());
txtvPublished.setText(DateUtils.formatSameDayTime(item.getPubDate()
@@ -106,9 +122,13 @@ public class ItemviewActivity extends SherlockFragmentActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getSupportMenuInflater();
- inflater.inflate(R.menu.feeditem, menu);
- return true;
+ if (item != null) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.feeditem, menu);
+ return true;
+ } else {
+ return false;
+ }
}
@Override
@@ -134,13 +154,28 @@ public class ItemviewActivity extends SherlockFragmentActivity {
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
return FeedItemMenuHandler.onPrepareMenu(
- new FeedItemMenuHandler.MenuInterface() {
+ new FeedItemMenuHandler.MenuInterface() {
- @Override
- public void setItemVisibility(int id, boolean visible) {
- menu.findItem(id).setVisible(visible);
- }
- }, item, true);
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ menu.findItem(id).setVisible(visible);
+ }
+ }, item, true, QueueAccess.NotInQueueAccess());
}
+ private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
+
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EVENTS & arg) != 0) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received contentUpdate Intent.");
+ if (item != null) {
+ loadData(item.getId());
+ }
+ }
+ }
+ };
+
+
}
diff --git a/src/de/danoeh/antennapod/activity/MainActivity.java b/src/de/danoeh/antennapod/activity/MainActivity.java
index 410617b23..4f25a07f1 100644
--- a/src/de/danoeh/antennapod/activity/MainActivity.java
+++ b/src/de/danoeh/antennapod/activity/MainActivity.java
@@ -2,44 +2,47 @@ package de.danoeh.antennapod.activity;
import java.util.ArrayList;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
import android.content.Context;
import android.content.Intent;
+import android.media.AudioManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.SearchView;
import android.util.Log;
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.ActionBar.Tab;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-import com.actionbarsherlock.view.Window;
-
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Window;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.EventDistributor;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadService;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.StorageUtils;
/** The activity that is shown when the user launches the app. */
-public class MainActivity extends SherlockFragmentActivity {
+public class MainActivity extends ActionBarActivity {
private static final String TAG = "MainActivity";
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED;
- private FeedManager manager;
private ViewPager viewpager;
private TabsAdapter pagerAdapter;
private ExternalPlayerFragment externalPlayerFragment;
@@ -51,20 +54,20 @@ public class MainActivity extends SherlockFragmentActivity {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
- manager = FeedManager.getInstance();
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
- getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
viewpager = (ViewPager) findViewById(R.id.viewpager);
pagerAdapter = new TabsAdapter(this, viewpager);
viewpager.setAdapter(pagerAdapter);
- Tab feedsTab = getSupportActionBar().newTab();
+ ActionBar.Tab feedsTab = getSupportActionBar().newTab();
feedsTab.setText(R.string.podcasts_label);
- Tab episodesTab = getSupportActionBar().newTab();
+ ActionBar.Tab episodesTab = getSupportActionBar().newTab();
episodesTab.setText(R.string.episodes_label);
pagerAdapter.addTab(feedsTab, FeedlistFragment.class, null);
@@ -80,7 +83,7 @@ public class MainActivity extends SherlockFragmentActivity {
if (!appLaunched && getIntent().getAction() != null
&& getIntent().getAction().equals(Intent.ACTION_MAIN)) {
appLaunched = true;
- if (manager.getUnreadItemsSize(true) > 0) {
+ if (DBReader.getNumberOfUnreadItems(this) > 0) {
// select 'episodes' tab
getSupportActionBar().setSelectedNavigationItem(1);
}
@@ -142,7 +145,7 @@ public class MainActivity extends SherlockFragmentActivity {
startActivity(new Intent(this, AddFeedActivity.class));
return true;
case R.id.all_feed_refresh:
- manager.refreshAllFeeds(this);
+ DBTasks.refreshAllFeeds(this, null);
return true;
case R.id.show_downloads:
startActivity(new Intent(this, DownloadActivity.class));
@@ -153,9 +156,6 @@ public class MainActivity extends SherlockFragmentActivity {
case R.id.show_player:
startActivity(PlaybackService.getPlayerActivityIntent(this));
return true;
- case R.id.search_item:
- onSearchRequested();
- return true;
case R.id.show_playback_history:
startActivity(new Intent(this, PlaybackHistoryActivity.class));
return true;
@@ -173,9 +173,6 @@ public class MainActivity extends SherlockFragmentActivity {
} else {
refreshAll.setVisible(true);
}
-
- boolean hasFeeds = manager.getFeedsSize() > 0;
- menu.findItem(R.id.all_feed_refresh).setVisible(hasFeeds);
return true;
}
@@ -183,7 +180,23 @@ public class MainActivity extends SherlockFragmentActivity {
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.main, menu);
- return true;
+
+ SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ MenuItem searchItem = menu.findItem(R.id.search_item);
+ SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
+ if (searchView == null) {
+ MenuItemCompat.setActionView(searchItem, new SearchView(this));
+ searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
+ }
+ searchView.setIconifiedByDefault(true);
+
+ SearchableInfo info = searchManager.getSearchableInfo(getComponentName());
+ searchView.setSearchableInfo(
+ searchManager.getSearchableInfo(getComponentName()));
+
+
+ return true;
}
public static class TabsAdapter extends FragmentPagerAdapter implements
@@ -248,7 +261,7 @@ public class MainActivity extends SherlockFragmentActivity {
}
@Override
- public void onTabSelected(Tab tab, FragmentTransaction ft) {
+ public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
Object tag = tab.getTag();
for (int i = 0; i < mTabs.size(); i++) {
if (mTabs.get(i) == tag) {
@@ -258,12 +271,12 @@ public class MainActivity extends SherlockFragmentActivity {
}
@Override
- public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+ public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
@Override
- public void onTabReselected(Tab tab, FragmentTransaction ft) {
+ public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
}
diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
index 16b03809a..af244f2ed 100644
--- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
+++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
@@ -4,24 +4,23 @@ import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.PixelFormat;
+import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.dialog.TimeDialog;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.util.Converter;
@@ -35,12 +34,10 @@ import de.danoeh.antennapod.util.playback.PlaybackController;
* Provides general features which are both needed for playing audio and video
* files.
*/
-public abstract class MediaplayerActivity extends SherlockFragmentActivity
+public abstract class MediaplayerActivity extends ActionBarActivity
implements OnSeekBarChangeListener {
private static final String TAG = "MediaplayerActivity";
- protected FeedManager manager;
-
protected PlaybackController controller;
protected TextView txtvPosition;
@@ -132,10 +129,19 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity
public void onPlaybackEnd() {
finish();
}
+
+ @Override
+ public void onPlaybackSpeedChange() {
+ MediaplayerActivity.this.onPlaybackSpeedChange();
+ }
};
}
+ protected void onPlaybackSpeedChange() {
+
+ }
+
protected void onServiceQueried() {
supportInvalidateOptionsMenu();
}
@@ -147,9 +153,9 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity
if (AppConfig.DEBUG)
Log.d(TAG, "Creating Activity");
StorageUtils.checkStorageAvailability(this);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
- orientation = getResources().getConfiguration().orientation;
- manager = FeedManager.getInstance();
+ orientation = getResources().getConfiguration().orientation;
getWindow().setFormat(PixelFormat.TRANSPARENT);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
diff --git a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java
index bb50944cc..e3d77a68a 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java
@@ -5,13 +5,11 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-import com.viewpagerindicator.TabPageIndicator;
-
+import android.view.Menu;
+import android.view.MenuItem;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.fragment.MiroGuideChannellistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -21,14 +19,13 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* activity uses MiroGuideChannelListFragments for these lists. If the user
* selects a channel, the MiroGuideChannelViewActivity is started.
*/
-public class MiroGuideCategoryActivity extends SherlockFragmentActivity {
+public class MiroGuideCategoryActivity extends ActionBarActivity {
private static final String TAG = "MiroGuideCategoryActivity";
- public static String EXTRA_CATEGORY = "category";
+ public static final String EXTRA_CATEGORY = "category";
private ViewPager viewpager;
private CategoryPagerAdapter pagerAdapter;
- private TabPageIndicator tabs;
private String category;
@@ -40,14 +37,12 @@ public class MiroGuideCategoryActivity extends SherlockFragmentActivity {
setContentView(R.layout.miroguide_category);
viewpager = (ViewPager) findViewById(R.id.viewpager);
- tabs = (TabPageIndicator) findViewById(R.id.tabs);
category = getIntent().getStringExtra(EXTRA_CATEGORY);
if (category != null) {
getSupportActionBar().setTitle(category);
pagerAdapter = new CategoryPagerAdapter(getSupportFragmentManager());
viewpager.setAdapter(pagerAdapter);
- tabs.setViewPager(viewpager);
} else {
Log.e(TAG, "Activity was started with invalid arguments");
}
diff --git a/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java
index f9fe912cd..d4b4597f2 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java
@@ -1,13 +1,18 @@
package de.danoeh.antennapod.activity;
import java.util.Date;
+import java.util.List;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.ProgressBar;
@@ -15,21 +20,16 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.MiroGuideItemlistAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.miroguide.conn.MiroGuideException;
import de.danoeh.antennapod.miroguide.conn.MiroGuideService;
import de.danoeh.antennapod.miroguide.model.MiroGuideChannel;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
@@ -37,147 +37,164 @@ import de.danoeh.antennapod.storage.DownloadRequester;
* Displays information about one channel and lets the user add this channel to
* his library.
*/
-public class MiroGuideChannelViewActivity extends SherlockActivity {
+public class MiroGuideChannelViewActivity extends ActionBarActivity {
private static final String TAG = "MiroGuideChannelViewActivity";
- public static final String EXTRA_CHANNEL_ID = "id";
- public static final String EXTRA_CHANNEL_URL = "url";
-
- private RelativeLayout layoutContent;
- private ProgressBar progLoading;
- private TextView txtvTitle;
- private TextView txtVDescription;
- private ListView listEntries;
-
- private long channelId;
- private String channelUrl;
- private MiroGuideChannel channel;
-
- @Override
- protected void onPause() {
- super.onPause();
- channelLoader.cancel(true);
- }
-
- @SuppressLint("NewApi")
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.miroguide_channelview);
-
- layoutContent = (RelativeLayout) findViewById(R.id.layout_content);
- progLoading = (ProgressBar) findViewById(R.id.progLoading);
- txtvTitle = (TextView) findViewById(R.id.txtvTitle);
- txtVDescription = (TextView) findViewById(R.id.txtvDescription);
- listEntries = (ListView) findViewById(R.id.itemlist);
-
- channelId = getIntent().getLongExtra(EXTRA_CHANNEL_ID, -1);
- channelUrl = getIntent().getStringExtra(EXTRA_CHANNEL_URL);
-
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- channelLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- channelLoader.execute();
- }
-
- }
-
- /** Is used to load channel information asynchronously. */
- private AsyncTask<Void, Void, Void> channelLoader = new AsyncTask<Void, Void, Void>() {
- private static final String TAG = "ChannelLoader";
- private Exception exception;
-
- @Override
- protected Void doInBackground(Void... params) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Starting background task");
- MiroGuideService service = new MiroGuideService();
- try {
- channel = service.getChannel(channelId);
- } catch (MiroGuideException e) {
- e.printStackTrace();
- exception = e;
- }
- return null;
- }
-
- @SuppressLint("NewApi")
- @Override
- protected void onPostExecute(Void result) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Loading finished");
- if (exception == null) {
- txtvTitle.setText(channel.getName());
- txtVDescription.setText(channel.getDescription());
-
- MiroGuideItemlistAdapter listAdapter = new MiroGuideItemlistAdapter(
- MiroGuideChannelViewActivity.this, 0,
- channel.getItems());
- listEntries.setAdapter(listAdapter);
- progLoading.setVisibility(View.GONE);
- layoutContent.setVisibility(View.VISIBLE);
- supportInvalidateOptionsMenu();
- } else {
- finish();
- }
- }
-
- };
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = new MenuInflater(this);
- inflater.inflate(R.menu.channelview, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- boolean channelLoaded = channel != null;
- boolean beingDownloaded = channelLoaded
- && DownloadRequester.getInstance().isDownloadingFile(
- channel.getDownloadUrl());
- boolean notAdded = channelLoaded
- && !((FeedManager.getInstance().feedExists(
- channel.getDownloadUrl()) || beingDownloaded));
- menu.findItem(R.id.add_feed).setVisible(notAdded);
- menu.findItem(R.id.visit_website_item).setVisible(
- channelLoaded && channel.getWebsiteUrl() != null);
- return true;
- }
-
- @SuppressLint("NewApi")
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- case R.id.visit_website_item:
- Uri uri = Uri.parse(channel.getWebsiteUrl());
- startActivity(new Intent(Intent.ACTION_VIEW, uri));
- return true;
- case R.id.add_feed:
- try {
- DownloadRequester.getInstance().downloadFeed(
- this,
- new Feed(channel.getDownloadUrl(), new Date(), channel
- .getName()));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
- e.getMessage());
- }
- Toast toast = Toast.makeText(this, R.string.miro_feed_added,
- Toast.LENGTH_LONG);
- toast.show();
- supportInvalidateOptionsMenu();
- return true;
- default:
- return false;
- }
- }
+ public static final String EXTRA_CHANNEL_ID = "id";
+ public static final String EXTRA_CHANNEL_URL = "url";
+
+ private RelativeLayout layoutContent;
+ private ProgressBar progLoading;
+ private TextView txtvTitle;
+ private TextView txtVDescription;
+ private ListView listEntries;
+
+ private long channelId;
+ private String channelUrl;
+ private MiroGuideChannel channel;
+ private volatile List<Feed> feeds;
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ channelLoader.cancel(true);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.miroguide_channelview);
+
+ layoutContent = (RelativeLayout) findViewById(R.id.layout_content);
+ progLoading = (ProgressBar) findViewById(R.id.progLoading);
+ txtvTitle = (TextView) findViewById(R.id.txtvTitle);
+ txtVDescription = (TextView) findViewById(R.id.txtvDescription);
+ listEntries = (ListView) findViewById(R.id.itemlist);
+
+ channelId = getIntent().getLongExtra(EXTRA_CHANNEL_ID, -1);
+ channelUrl = getIntent().getStringExtra(EXTRA_CHANNEL_URL);
+
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ channelLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ channelLoader.execute();
+ }
+
+ }
+
+ /**
+ * Is used to load channel information asynchronously.
+ */
+ private AsyncTask<Void, Void, Void> channelLoader = new AsyncTask<Void, Void, Void>() {
+ private static final String TAG = "ChannelLoader";
+ private Exception exception;
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Starting background task");
+ feeds = DBReader.getFeedList(MiroGuideChannelViewActivity.this);
+ MiroGuideService service = new MiroGuideService();
+ try {
+ channel = service.getChannel(channelId);
+ } catch (MiroGuideException e) {
+ e.printStackTrace();
+ exception = e;
+ }
+ return null;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ protected void onPostExecute(Void result) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading finished");
+ if (exception == null) {
+ txtvTitle.setText(channel.getName());
+ txtVDescription.setText(channel.getDescription());
+
+ MiroGuideItemlistAdapter listAdapter = new MiroGuideItemlistAdapter(
+ MiroGuideChannelViewActivity.this, 0,
+ channel.getItems());
+ listEntries.setAdapter(listAdapter);
+ progLoading.setVisibility(View.GONE);
+ layoutContent.setVisibility(View.VISIBLE);
+ supportInvalidateOptionsMenu();
+ } else {
+ finish();
+ }
+ }
+
+ };
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = new MenuInflater(this);
+ inflater.inflate(R.menu.channelview, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ boolean channelLoaded = channel != null;
+ boolean beingDownloaded = channelLoaded
+ && DownloadRequester.getInstance().isDownloadingFile(
+ channel.getDownloadUrl());
+ boolean notAdded = channelLoaded
+ && !((feedExists(
+ channel.getDownloadUrl()) || beingDownloaded));
+ menu.findItem(R.id.add_feed).setVisible(notAdded);
+ menu.findItem(R.id.visit_website_item).setVisible(
+ channelLoaded && channel.getWebsiteUrl() != null);
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.visit_website_item:
+ Uri uri = Uri.parse(channel.getWebsiteUrl());
+ startActivity(new Intent(Intent.ACTION_VIEW, uri));
+ return true;
+ case R.id.add_feed:
+ try {
+ DownloadRequester.getInstance().downloadFeed(
+ this,
+ new Feed(channel.getDownloadUrl(), new Date(), channel
+ .getName()));
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
+ e.getMessage());
+ }
+ Toast toast = Toast.makeText(this, R.string.miro_feed_added,
+ Toast.LENGTH_LONG);
+ toast.show();
+ supportInvalidateOptionsMenu();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean feedExists(String downloadUrl) {
+ if (feeds == null) {
+ return false;
+ }
+
+ for (Feed feed : feeds) {
+ if (feed.getDownload_url().equals(downloadUrl)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java
index 8b33ef1da..40306e4da 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java
@@ -1,19 +1,24 @@
package de.danoeh.antennapod.activity;
import android.annotation.SuppressLint;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.SearchView;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
+import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.miroguide.conn.MiroGuideException;
@@ -24,132 +29,140 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Shows a list of available categories and offers a search button. If the user
* selects a category, the MiroGuideCategoryActivity is started.
*/
-public class MiroGuideMainActivity extends SherlockListActivity {
- private static final String TAG = "MiroGuideMainActivity";
-
- private static String[] categories;
- private ArrayAdapter<String> listAdapter;
-
- private TextView txtvStatus;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.miroguide_categorylist);
-
- txtvStatus = (TextView) findViewById(android.R.id.empty);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- if (categories != null) {
- createAdapter();
- } else {
- loadCategories();
- }
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- super.onListItemClick(l, v, position, id);
- String selection = listAdapter.getItem(position);
- Intent launchIntent = new Intent(this, MiroGuideCategoryActivity.class);
- launchIntent.putExtra(MiroGuideCategoryActivity.EXTRA_CATEGORY,
- selection);
- startActivity(launchIntent);
- }
-
- private void createAdapter() {
- if (categories != null) {
- listAdapter = new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_1, categories);
- txtvStatus.setText(R.string.no_items_label);
- setListAdapter(listAdapter);
- }
- }
-
- /**
- * Launches an AsyncTask to load the available categories in the background.
- */
- @SuppressLint("NewApi")
- private void loadCategories() {
- AsyncTask<Void, Void, Void> listLoader = new AsyncTask<Void, Void, Void>() {
-
- private String[] c;
- private MiroGuideException exception;
-
- @Override
- protected void onPostExecute(Void result) {
- if (exception == null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Successfully loaded categories");
- categories = c;
- createAdapter();
- } else {
- Log.e(TAG, "Error happened while trying to load categories");
- txtvStatus.setText(exception.getMessage());
- }
- }
-
- @Override
- protected void onPreExecute() {
- txtvStatus.setText(R.string.loading_categories_label);
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- MiroGuideService service = new MiroGuideService();
- try {
- c = service.getCategories();
- } catch (MiroGuideException e) {
- e.printStackTrace();
- exception = e;
- } finally {
- service.close();
- }
- return null;
- }
-
- };
-
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- listLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- listLoader.execute();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
- .setIcon(
- obtainStyledAttributes(
- new int[] { R.attr.action_search })
- .getDrawable(0))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- case R.id.search_item:
- onSearchRequested();
- return true;
- default:
- return false;
- }
- }
-
+public class MiroGuideMainActivity extends ActionBarActivity implements AdapterView.OnItemClickListener {
+ private static final String TAG = "MiroGuideMainActivity";
+
+ private static String[] categories;
+ private ArrayAdapter<String> listAdapter;
+
+ private TextView txtvStatus;
+ private ListView listView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.miroguide_categorylist);
+
+ txtvStatus = (TextView) findViewById(android.R.id.empty);
+ listView = (ListView) findViewById(android.R.id.list);
+ listView.setOnItemClickListener(this);
+ listView.setEmptyView(txtvStatus);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (categories != null) {
+ createAdapter();
+ } else {
+ loadCategories();
+ }
+ }
+
+ private void createAdapter() {
+ if (categories != null) {
+ listAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, categories);
+ txtvStatus.setText(R.string.no_items_label);
+ listView.setAdapter(listAdapter);
+ }
+ }
+
+ /**
+ * Launches an AsyncTask to load the available categories in the background.
+ */
+ @SuppressLint("NewApi")
+ private void loadCategories() {
+ AsyncTask<Void, Void, Void> listLoader = new AsyncTask<Void, Void, Void>() {
+
+ private String[] c;
+ private MiroGuideException exception;
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (exception == null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Successfully loaded categories");
+ categories = c;
+ createAdapter();
+ } else {
+ Log.e(TAG, "Error happened while trying to load categories");
+ txtvStatus.setText(exception.getMessage());
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ txtvStatus.setText(R.string.loading_categories_label);
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ MiroGuideService service = new MiroGuideService();
+ try {
+ c = service.getCategories();
+ } catch (MiroGuideException e) {
+ e.printStackTrace();
+ exception = e;
+ } finally {
+ service.close();
+ }
+ return null;
+ }
+
+ };
+
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ listLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ listLoader.execute();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
+ .setIcon(
+ obtainStyledAttributes(
+ new int[]{R.attr.action_search})
+ .getDrawable(0)),
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ MenuItemCompat.setActionView(menu.findItem(R.id.search_item), new SearchView(this));
+
+ SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
+ searchView.setIconifiedByDefault(true);
+ searchView.setSearchableInfo(
+ searchManager.getSearchableInfo(getComponentName()));
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
+ String selection = listAdapter.getItem(position);
+ Intent launchIntent = new Intent(this, MiroGuideCategoryActivity.class);
+ launchIntent.putExtra(MiroGuideCategoryActivity.EXTRA_CATEGORY,
+ selection);
+ startActivity(launchIntent);
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java
index a30777fb1..4ea0b1699 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java
@@ -4,12 +4,12 @@ import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.Menu;
+import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.fragment.MiroGuideChannellistFragment;
@@ -19,72 +19,72 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Displays results when a search for miroguide channels has been performed. It
* uses a MiroGuideChannelListFragment to display the results.
*/
-public class MiroGuideSearchActivity extends SherlockFragmentActivity {
- private static final String TAG = "MiroGuideSearchActivity";
+public class MiroGuideSearchActivity extends ActionBarActivity {
+ private static final String TAG = "MiroGuideSearchActivity";
- private MiroGuideChannellistFragment listFragment;
+ private MiroGuideChannellistFragment listFragment;
- @Override
- protected void onCreate(Bundle arg0) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(arg0);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.miroguidesearch);
- }
+ @Override
+ protected void onCreate(Bundle arg0) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(arg0);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.miroguidesearch);
+ }
- @Override
- protected void onResume() {
- super.onResume();
- Intent intent = getIntent();
- if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
- String query = intent.getStringExtra(SearchManager.QUERY);
- getSupportActionBar()
- .setSubtitle(
- getString(R.string.search_term_label) + "\""
- + query + "\"");
- handleSearchRequest(query);
- }
- }
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Intent intent = getIntent();
+ if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ getSupportActionBar()
+ .setSubtitle(
+ getString(R.string.search_term_label) + "\""
+ + query + "\"");
+ handleSearchRequest(query);
+ }
+ }
- private void handleSearchRequest(String query) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Performing search");
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- listFragment = MiroGuideChannellistFragment.newInstance("name", query,
- "name");
- ft.replace(R.id.channellistFragment, listFragment);
- ft.commit();
- }
+ private void handleSearchRequest(String query) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Performing search");
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ listFragment = MiroGuideChannellistFragment.newInstance("name", query,
+ "name");
+ ft.replace(R.id.channellistFragment, listFragment);
+ ft.commit();
+ }
- @Override
- protected void onNewIntent(Intent intent) {
- setIntent(intent);
- }
+ @Override
+ protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
- .setIcon(
- obtainStyledAttributes(
- new int[] { R.attr.action_search })
- .getDrawable(0))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
+ .setIcon(
+ obtainStyledAttributes(
+ new int[]{R.attr.action_search})
+ .getDrawable(0)),
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
- return true;
- }
+ return true;
+ }
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- case R.id.search_item:
- onSearchRequested();
- return true;
- default:
- return false;
- }
- }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.search_item:
+ onSearchRequested();
+ return true;
+ default:
+ return false;
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
index d6ff537a3..fbac7057d 100644
--- a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
+++ b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -6,6 +6,7 @@ import java.util.Date;
import javax.xml.parsers.ParserConfigurationException;
+import android.support.v7.app.ActionBarActivity;
import org.xml.sax.SAXException;
import android.app.AlertDialog;
@@ -17,13 +18,12 @@ import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.download.DownloadRequest;
+import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.service.download.DownloaderCallback;
import de.danoeh.antennapod.service.download.HttpDownloader;
@@ -42,10 +42,10 @@ import de.danoeh.antennapod.util.URLChecker;
* If the feed cannot be downloaded or parsed, an error dialog will be displayed
* and the activity will finish as soon as the error dialog is closed.
*/
-public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
+public abstract class OnlineFeedViewActivity extends ActionBarActivity {
private static final String TAG = "OnlineFeedViewActivity";
private static final String ARG_FEEDURL = "arg.feedurl";
-
+
public static final int RESULT_ERROR = 2;
private Feed feed;
@@ -70,7 +70,7 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
@Override
protected void onStop() {
super.onStop();
- if (downloader != null && downloader.getStatus().isDone() == false) {
+ if (downloader != null && !downloader.isFinished()) {
downloader.cancel();
}
}
@@ -82,15 +82,14 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
@Override
public void run() {
- DownloadStatus status = downloader.getStatus();
+ DownloadStatus status = downloader.getResult();
if (status != null) {
if (!status.isCancelled()) {
if (status.isSuccessful()) {
parseFeed();
} else {
- String errorMsg = DownloadError.getErrorString(
- OnlineFeedViewActivity.this,
- status.getReason());
+ String errorMsg = status.getReason().getErrorString(
+ OnlineFeedViewActivity.this);
if (errorMsg != null
&& status.getReasonDetailed() != null) {
errorMsg += " ("
@@ -119,10 +118,13 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
FileNameGenerator.generateFileName(feed.getDownload_url()))
.toString();
feed.setFile_url(fileUrl);
- DownloadStatus status = new DownloadStatus(feed, "OnlineFeed");
+ DownloadRequest request = new DownloadRequest(feed.getFile_url(),
+ feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED);
+ /* TODO update
HttpDownloader httpDownloader = new HttpDownloader(downloaderCallback,
- status);
+ request);
httpDownloader.start();
+ */
}
/** Displays a progress indicator. */
@@ -155,7 +157,7 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
@Override
public void run() {
- String reasonDetailed = new String();
+ String reasonDetailed = "";
boolean successful = false;
FeedHandler handler = new FeedHandler();
try {
@@ -187,9 +189,9 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
}
});
} else {
- final String errorMsg = DownloadError.getErrorString(
- OnlineFeedViewActivity.this,
- DownloadError.ERROR_PARSER_EXCEPTION)
+ final String errorMsg =
+ DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
+ OnlineFeedViewActivity.this)
+ " (" + reasonDetailed + ")";
runOnUiThread(new Runnable() {
@@ -217,13 +219,14 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
} else {
builder.setMessage(R.string.error_msg_prefix);
}
- builder.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- });
+ builder.setNeutralButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
diff --git a/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java b/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
index 9ba355baf..2ec42e9ef 100644
--- a/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
@@ -5,17 +5,17 @@ import java.util.List;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.util.SparseBooleanArray;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.opml.OpmlElement;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -24,110 +24,111 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Displays the feeds that the OPML-Importer has read and lets the user choose
* which feeds he wants to import.
*/
-public class OpmlFeedChooserActivity extends SherlockActivity {
- private static final String TAG = "OpmlFeedChooserActivity";
-
- public static final String EXTRA_SELECTED_ITEMS = "de.danoeh.antennapod.selectedItems";
-
- private Button butConfirm;
- private Button butCancel;
- private ListView feedlist;
- private ArrayAdapter<String> listAdapter;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.opml_selection);
- butConfirm = (Button) findViewById(R.id.butConfirm);
- butCancel = (Button) findViewById(R.id.butCancel);
- feedlist = (ListView) findViewById(R.id.feedlist);
-
- feedlist.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- listAdapter = new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_multiple_choice,
- getTitleList());
-
- feedlist.setAdapter(listAdapter);
-
- butCancel.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(RESULT_CANCELED);
- finish();
- }
- });
-
- butConfirm.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- Intent intent = new Intent();
- SparseBooleanArray checked = feedlist.getCheckedItemPositions();
-
- int checkedCount = 0;
- // Get number of checked items
- for (int i = 0; i < checked.size(); i++) {
- if (checked.valueAt(i)) {
- checkedCount++;
- }
- }
- int[] selection = new int[checkedCount];
- for (int i = 0, collected = 0; collected < checkedCount; i++) {
- if (checked.valueAt(i)) {
- selection[collected] = checked.keyAt(i);
- collected++;
- }
- }
- intent.putExtra(EXTRA_SELECTED_ITEMS, selection);
- setResult(RESULT_OK, intent);
- finish();
- }
- });
-
- }
-
- private List<String> getTitleList() {
- List<String> result = new ArrayList<String>();
- if (OpmlImportHolder.getReadElements() != null) {
- for (OpmlElement element : OpmlImportHolder.getReadElements()) {
- result.add(element.getText());
- }
-
- }
- return result;
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.select_all_item, Menu.NONE,
- R.string.select_all_label).setShowAsAction(
- MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(Menu.NONE, R.id.deselect_all_item, Menu.NONE,
- R.string.deselect_all_label).setShowAsAction(
- MenuItem.SHOW_AS_ACTION_IF_ROOM);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.select_all_item:
- selectAllItems(true);
- return true;
- case R.id.deselect_all_item:
- selectAllItems(false);
- return true;
- default:
- return false;
- }
- }
-
- private void selectAllItems(boolean b) {
- for (int i = 0; i < feedlist.getCount(); i++) {
- feedlist.setItemChecked(i, b);
- }
- }
+public class OpmlFeedChooserActivity extends ActionBarActivity {
+ private static final String TAG = "OpmlFeedChooserActivity";
+
+ public static final String EXTRA_SELECTED_ITEMS = "de.danoeh.antennapod.selectedItems";
+
+ private Button butConfirm;
+ private Button butCancel;
+ private ListView feedlist;
+ private ArrayAdapter<String> listAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.opml_selection);
+ butConfirm = (Button) findViewById(R.id.butConfirm);
+ butCancel = (Button) findViewById(R.id.butCancel);
+ feedlist = (ListView) findViewById(R.id.feedlist);
+
+ feedlist.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ listAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_multiple_choice,
+ getTitleList());
+
+ feedlist.setAdapter(listAdapter);
+
+ butCancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+
+ butConfirm.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent();
+ SparseBooleanArray checked = feedlist.getCheckedItemPositions();
+
+ int checkedCount = 0;
+ // Get number of checked items
+ for (int i = 0; i < checked.size(); i++) {
+ if (checked.valueAt(i)) {
+ checkedCount++;
+ }
+ }
+ int[] selection = new int[checkedCount];
+ for (int i = 0, collected = 0; collected < checkedCount; i++) {
+ if (checked.valueAt(i)) {
+ selection[collected] = checked.keyAt(i);
+ collected++;
+ }
+ }
+ intent.putExtra(EXTRA_SELECTED_ITEMS, selection);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ });
+
+ }
+
+ private List<String> getTitleList() {
+ List<String> result = new ArrayList<String>();
+ if (OpmlImportHolder.getReadElements() != null) {
+ for (OpmlElement element : OpmlImportHolder.getReadElements()) {
+ result.add(element.getText());
+ }
+
+ }
+ return result;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.select_all_item, Menu.NONE,
+ R.string.select_all_label),
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
+
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.deselect_all_item, Menu.NONE,
+ R.string.deselect_all_label),
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.select_all_item:
+ selectAllItems(true);
+ return true;
+ case R.id.deselect_all_item:
+ selectAllItems(false);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void selectAllItems(boolean b) {
+ for (int i = 0; i < feedlist.getCount(); i++) {
+ feedlist.setItemChecked(i, b);
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java
index f887fdd94..905183aa2 100644
--- a/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java
@@ -4,10 +4,9 @@ import java.io.Reader;
import java.util.ArrayList;
import android.content.Intent;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockActivity;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.asynctask.OpmlFeedQueuer;
import de.danoeh.antennapod.asynctask.OpmlImportWorker;
@@ -16,7 +15,7 @@ import de.danoeh.antennapod.opml.OpmlElement;
/**
* Base activity for Opml Import - e.g. with code what to do afterwards
* */
-public class OpmlImportBaseActivity extends SherlockActivity {
+public class OpmlImportBaseActivity extends ActionBarActivity {
private static final String TAG = "OpmlImportBaseActivity";
private OpmlImportWorker importWorker;
diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java
index dc698a851..58e3a96dd 100644
--- a/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java
@@ -7,6 +7,7 @@ import java.net.URL;
import android.app.AlertDialog;
import android.os.Bundle;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.util.LangUtils;
/** Lets the user start the OPML-import process. */
public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity {
@@ -20,7 +21,8 @@ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity {
try {
URL mOpmlURL = new URL(getIntent().getData().toString());
- BufferedReader in = new BufferedReader(new InputStreamReader(mOpmlURL.openStream()));
+ BufferedReader in = new BufferedReader(new InputStreamReader(mOpmlURL.openStream(),
+ LangUtils.UTF_8));
startImport(in);
} catch (Exception e) {
new AlertDialog.Builder(this).setMessage("Cannot open XML - Reason: " + e.getMessage()).show();
diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
index b38e0c443..ece78006f 100644
--- a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
@@ -1,26 +1,28 @@
package de.danoeh.antennapod.activity;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
import java.io.Reader;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.util.LangUtils;
import de.danoeh.antennapod.util.StorageUtils;
/**
@@ -126,8 +128,10 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity {
}
private void startImport(File file) {
+ Reader mReader = null;
try {
- Reader mReader = new FileReader(file);
+ mReader = new InputStreamReader(new FileInputStream(file),
+ LangUtils.UTF_8);
if (AppConfig.DEBUG) Log.d(TAG, "Parsing " + file.toString());
startImport(mReader);
} catch (FileNotFoundException e) {
diff --git a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
index 7269f7549..e376b08b8 100644
--- a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
+++ b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
@@ -1,10 +1,13 @@
package de.danoeh.antennapod.activity;
import android.content.Context;
-import android.content.res.TypedArray;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.v7.app.ActionBarActivity;
+import android.view.*;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -12,28 +15,32 @@ import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
import com.mobeta.android.dslv.DragSortListView;
-
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.UndoBarController;
-public class OrganizeQueueActivity extends SherlockListActivity implements
+import java.util.List;
+
+public class OrganizeQueueActivity extends ActionBarActivity implements
UndoBarController.UndoListener {
private static final String TAG = "OrganizeQueueActivity";
private static final int MENU_ID_ACCEPT = 2;
+ private List<FeedItem> queue;
+
private OrganizeAdapter adapter;
private UndoBarController undoBarController;
+ private DragSortListView listView;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
@@ -41,17 +48,42 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
setContentView(R.layout.organize_queue);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- DragSortListView listView = (DragSortListView) getListView();
+ listView = (DragSortListView) findViewById(android.R.id.list);
listView.setDropListener(dropListener);
listView.setRemoveListener(removeListener);
+ listView.setEmptyView(findViewById(android.R.id.empty));
- adapter = new OrganizeAdapter(this);
- setListAdapter(adapter);
-
+ loadData();
undoBarController = new UndoBarController(findViewById(R.id.undobar),
this);
}
+ private void loadData() {
+ AsyncTask<Void, Void, List<FeedItem>> loadTask = new AsyncTask<Void, Void, List<FeedItem>>() {
+
+ @Override
+ protected List<FeedItem> doInBackground(Void... voids) {
+ return DBReader.getQueue(OrganizeQueueActivity.this);
+ }
+
+ @Override
+ protected void onPostExecute(List<FeedItem> feedItems) {
+ super.onPostExecute(feedItems);
+ if (feedItems != null) {
+ queue = feedItems;
+ if (adapter == null) {
+ adapter = new OrganizeAdapter(OrganizeQueueActivity.this);
+ listView.setAdapter(adapter);
+ }
+ adapter.notifyDataSetChanged();
+ } else {
+ Log.e(TAG, "Queue was null");
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
@Override
protected void onPause() {
super.onPause();
@@ -61,8 +93,7 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
protected void onStop() {
super.onStop();
- FeedManager.getInstance().autodownloadUndownloadedItems(
- getApplicationContext());
+ DBTasks.autodownloadUndownloadedItems(getApplicationContext());
}
@Override
@@ -76,9 +107,7 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if (((EventDistributor.QUEUE_UPDATE | EventDistributor.FEED_LIST_UPDATE) & arg) != 0) {
- if (adapter != null) {
- adapter.notifyDataSetChanged();
- }
+ loadData();
}
}
};
@@ -87,9 +116,10 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
public void drop(int from, int to) {
- FeedManager manager = FeedManager.getInstance();
- manager.moveQueueItem(OrganizeQueueActivity.this, from, to, false);
- adapter.notifyDataSetChanged();
+ final FeedItem item = queue.remove(from);
+ queue.add(to, item);
+ adapter.notifyDataSetChanged();
+ DBWriter.moveQueueItem(OrganizeQueueActivity.this, from, to, true);
}
};
@@ -97,9 +127,8 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
public void remove(int which) {
- FeedManager manager = FeedManager.getInstance();
- FeedItem item = (FeedItem) getListAdapter().getItem(which);
- manager.removeQueueItem(OrganizeQueueActivity.this, item, false);
+ FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
+ DBWriter.removeQueueItem(OrganizeQueueActivity.this, item.getId(), true);
undoBarController.showUndoBar(false,
getString(R.string.removed_from_queue), new UndoToken(item,
which));
@@ -127,22 +156,20 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
public void onUndo(Parcelable token) {
// Perform the undo
UndoToken undoToken = (UndoToken) token;
- FeedItem feedItem = undoToken.getFeedItem();
- int position = undoToken.getPosition();
-
- FeedManager manager = FeedManager.getInstance();
- manager.addQueueItemAt(OrganizeQueueActivity.this, feedItem, position,
- false);
+ if (token != null) {
+ long itemId = undoToken.getFeedItemId();
+ int position = undoToken.getPosition();
+ DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, position, false);
+ }
}
private static class OrganizeAdapter extends BaseAdapter {
- private Context context;
- private FeedManager manager = FeedManager.getInstance();
+ private OrganizeQueueActivity organizeQueueActivity;
- public OrganizeAdapter(Context context) {
+ public OrganizeAdapter(OrganizeQueueActivity organizeQueueActivity) {
super();
- this.context = context;
+ this.organizeQueueActivity = organizeQueueActivity;
}
@Override
@@ -152,7 +179,7 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
if (convertView == null) {
holder = new Holder();
- LayoutInflater inflater = (LayoutInflater) context
+ LayoutInflater inflater = (LayoutInflater) organizeQueueActivity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(
R.layout.organize_queue_listitem, null);
@@ -189,13 +216,20 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
public int getCount() {
- int queueSize = manager.getQueueSize(true);
- return queueSize;
+ if (organizeQueueActivity.queue != null) {
+ return organizeQueueActivity.queue.size();
+ } else {
+ return 0;
+ }
}
@Override
public FeedItem getItem(int position) {
- return manager.getQueueItemAtIndex(position, true);
+ if (organizeQueueActivity.queue != null) {
+ return organizeQueueActivity.queue.get(position);
+ } else {
+ return null;
+ }
}
@Override
@@ -211,7 +245,6 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
private int position;
public UndoToken(FeedItem item, int position) {
- FeedManager manager = FeedManager.getInstance();
this.itemId = item.getId();
this.feedId = item.getFeed().getId();
this.position = position;
@@ -243,9 +276,8 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
out.writeInt(position);
}
- public FeedItem getFeedItem() {
- FeedManager manager = FeedManager.getInstance();
- return manager.getFeedItem(itemId, feedId);
+ public long getFeedItemId() {
+ return itemId;
}
public int getPosition() {
diff --git a/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java b/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java
index 1a5a2cac9..f329581e5 100644
--- a/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java
+++ b/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java
@@ -3,25 +3,25 @@ package de.danoeh.antennapod.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.Menu;
+import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBWriter;
-public class PlaybackHistoryActivity extends SherlockFragmentActivity {
+public class PlaybackHistoryActivity extends ActionBarActivity {
private static final String TAG = "PlaybackHistoryActivity";
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.clear_history_item, Menu.NONE,
- R.string.clear_history_label).setShowAsAction(
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.clear_history_item, Menu.NONE,
+ R.string.clear_history_label),
MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
@@ -36,7 +36,7 @@ public class PlaybackHistoryActivity extends SherlockFragmentActivity {
startActivity(intent);
return true;
case R.id.clear_history_item:
- FeedManager.getInstance().clearPlaybackHistory(this);
+ DBWriter.clearPlaybackHistory(this);
return true;
}
return super.onOptionsItemSelected(item);
diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java
index 9fcf57ac2..96471d06d 100644
--- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java
+++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java
@@ -19,369 +19,368 @@ import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockPreferenceActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.Menu;
+import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.asynctask.OpmlExportWorker;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
-/** The main preference activity */
-public class PreferenceActivity extends SherlockPreferenceActivity {
- private static final String TAG = "PreferenceActivity";
-
- private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp";
- private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
- private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
- private static final String PREF_OPML_EXPORT = "prefOpmlExport";
- private static final String PREF_ABOUT = "prefAbout";
- private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
- private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
-
- private CheckBoxPreference[] selectedNetworks;
+/**
+ * The main preference activity
+ */
+public class PreferenceActivity extends android.preference.PreferenceActivity {
+ private static final String TAG = "PreferenceActivity";
+
+ private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp";
+ private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
+ private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
+ private static final String PREF_OPML_EXPORT = "prefOpmlExport";
+ private static final String PREF_ABOUT = "prefAbout";
+ private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
+ private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
+ private static final String PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher";
+
+ private CheckBoxPreference[] selectedNetworks;
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
- @SuppressWarnings("deprecation")
- @Override
- public void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
addPreferencesFromResource(R.xml.preferences);
findPreference(PREF_FLATTR_THIS_APP).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- new FlattrClickWorker(PreferenceActivity.this,
- FlattrUtils.APP_URL).executeAsync();
-
- return true;
- }
- });
-
- findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- FlattrUtils.revokeAccessToken(PreferenceActivity.this);
- checkItemVisibility();
- return true;
- }
-
- });
-
- findPreference(PREF_ABOUT).setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- PreferenceActivity.this.startActivity(new Intent(
- PreferenceActivity.this, AboutActivity.class));
- return true;
- }
-
- });
-
- findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- if (FeedManager.getInstance().getFeedsSize() > 0) {
- new OpmlExportWorker(PreferenceActivity.this)
- .executeAsync();
- }
- return true;
- }
- });
-
- findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- startActivityForResult(
- new Intent(PreferenceActivity.this,
- DirectoryChooserActivity.class),
- DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED);
- return true;
- }
- });
- findPreference(UserPreferences.PREF_THEME)
- .setOnPreferenceChangeListener(
- new OnPreferenceChangeListener() {
-
- @Override
- public boolean onPreferenceChange(
- Preference preference, Object newValue) {
- Intent i = getIntent();
- i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- finish();
- startActivity(i);
- return true;
- }
- });
- findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
- .setOnPreferenceChangeListener(
- new OnPreferenceChangeListener() {
-
- @Override
- public boolean onPreferenceChange(
- Preference preference, Object newValue) {
- if (newValue instanceof Boolean) {
- setSelectedNetworksEnabled((Boolean) newValue);
- return true;
- } else {
- return false;
- }
- }
- });
- findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
- .setOnPreferenceChangeListener(
- new OnPreferenceChangeListener() {
-
- @Override
- public boolean onPreferenceChange(
- Preference preference, Object newValue) {
- if (newValue instanceof String) {
- setEpisodeCacheSizeText(Integer
- .valueOf((String) newValue));
- }
- return true;
- }
- });
- findPreference(UserPreferences.PREF_ENABLE_AUTODL)
- .setOnPreferenceClickListener(new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- checkItemVisibility();
- return true;
- }
- });
- buildUpdateIntervalPreference();
- buildAutodownloadSelectedNetworsPreference();
- setSelectedNetworksEnabled(UserPreferences
- .isEnableAutodownloadWifiFilter());
-
- }
-
- private void buildUpdateIntervalPreference() {
- ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_UPDATE_INTERVAL);
- String[] values = getResources().getStringArray(
- R.array.update_intervall_values);
- String[] entries = new String[values.length];
- for (int x = 0; x < values.length; x++) {
- Integer v = Integer.parseInt(values[x]);
- switch (v) {
- case 0:
- entries[x] = getString(R.string.pref_update_interval_hours_manual);
- break;
- case 1:
- entries[x] = v
- + " "
- + getString(R.string.pref_update_interval_hours_singular);
- break;
- default:
- entries[x] = v + " "
- + getString(R.string.pref_update_interval_hours_plural);
- break;
-
- }
- }
- pref.setEntries(entries);
-
- }
-
- private void setSelectedNetworksEnabled(boolean b) {
- if (selectedNetworks != null) {
- for (Preference p : selectedNetworks) {
- p.setEnabled(b);
- }
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- checkItemVisibility();
- setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
- setDataFolderText();
- }
-
- @SuppressWarnings("deprecation")
- private void checkItemVisibility() {
-
- boolean hasFlattrToken = FlattrUtils.hasToken();
-
- findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
- findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
-
- findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
- .setEnabled(UserPreferences.isEnableAutodownload());
- setSelectedNetworksEnabled(UserPreferences.isEnableAutodownload()
- && UserPreferences.isEnableAutodownloadWifiFilter());
-
- }
-
- private void setEpisodeCacheSizeText(int cacheSize) {
- String s;
- if (cacheSize == getResources().getInteger(
- R.integer.episode_cache_size_unlimited)) {
- s = getString(R.string.pref_episode_cache_unlimited);
- } else {
- s = Integer.toString(cacheSize)
- + getString(R.string.episodes_suffix);
- }
- findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s);
- }
-
- private void setDataFolderText() {
- File f = UserPreferences.getDataFolder(this, null);
- if (f != null) {
- findPreference(PREF_CHOOSE_DATA_DIR)
- .setSummary(f.getAbsolutePath());
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- break;
- default:
- return false;
- }
- return true;
- }
-
- @Override
- protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
- theme.applyStyle(UserPreferences.getTheme(), true);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
- String dir = data
- .getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting data folder");
- UserPreferences.setDataFolder(dir);
- }
- }
-
- private void buildAutodownloadSelectedNetworsPreference() {
- if (selectedNetworks != null) {
- clearAutodownloadSelectedNetworsPreference();
- }
- // get configured networks
- WifiManager wifiservice = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- List<WifiConfiguration> networks = wifiservice.getConfiguredNetworks();
-
- if (networks != null) {
- selectedNetworks = new CheckBoxPreference[networks.size()];
- List<String> prefValues = Arrays.asList(UserPreferences
- .getAutodownloadSelectedNetworks());
- PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
- OnPreferenceClickListener clickListener = new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- if (preference instanceof CheckBoxPreference) {
- String key = preference.getKey();
- ArrayList<String> prefValuesList = new ArrayList<String>(
- Arrays.asList(UserPreferences
- .getAutodownloadSelectedNetworks()));
- boolean newValue = ((CheckBoxPreference) preference)
- .isChecked();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Selected network " + key
- + ". New state: " + newValue);
-
- int index = prefValuesList.indexOf(key);
- if (index >= 0 && newValue == false) {
- // remove network
- prefValuesList.remove(index);
- } else if (index < 0 && newValue == true) {
- prefValuesList.add(key);
- }
-
- UserPreferences.setAutodownloadSelectedNetworks(
- PreferenceActivity.this, prefValuesList
- .toArray(new String[prefValuesList
- .size()]));
- return true;
- } else {
- return false;
- }
- }
- };
- // create preference for each known network. attach listener and set
- // value
- for (int i = 0; i < networks.size(); i++) {
- WifiConfiguration config = networks.get(i);
-
- CheckBoxPreference pref = new CheckBoxPreference(this);
- String key = Integer.toString(config.networkId);
- pref.setTitle(config.SSID);
- pref.setKey(key);
- pref.setOnPreferenceClickListener(clickListener);
- pref.setPersistent(false);
- pref.setChecked(prefValues.contains(key));
- selectedNetworks[i] = pref;
- prefScreen.addPreference(pref);
- }
- } else {
- Log.e(TAG, "Couldn't get list of configure Wi-Fi networks");
- }
- }
-
- private void clearAutodownloadSelectedNetworsPreference() {
- if (selectedNetworks != null) {
- PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
-
- for (int i = 0; i < selectedNetworks.length; i++) {
- if (selectedNetworks[i] != null) {
- prefScreen.removePreference(selectedNetworks[i]);
- }
- }
- }
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
- super.onPreferenceTreeClick(preferenceScreen, preference);
- if (preference != null)
- if (preference instanceof PreferenceScreen)
- if (((PreferenceScreen) preference).getDialog() != null)
- ((PreferenceScreen) preference)
- .getDialog()
- .getWindow()
- .getDecorView()
- .setBackgroundDrawable(
- this.getWindow().getDecorView()
- .getBackground().getConstantState()
- .newDrawable());
- return false;
- }
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ new FlattrClickWorker(PreferenceActivity.this,
+ FlattrUtils.APP_URL).executeAsync();
+
+ return true;
+ }
+ });
+
+ findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ FlattrUtils.revokeAccessToken(PreferenceActivity.this);
+ checkItemVisibility();
+ return true;
+ }
+
+ });
+
+ findPreference(PREF_ABOUT).setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ PreferenceActivity.this.startActivity(new Intent(
+ PreferenceActivity.this, AboutActivity.class));
+ return true;
+ }
+
+ });
+
+ findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ new OpmlExportWorker(PreferenceActivity.this)
+ .executeAsync();
+
+ return true;
+ }
+ });
+
+ findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ startActivityForResult(
+ new Intent(PreferenceActivity.this,
+ DirectoryChooserActivity.class),
+ DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED);
+ return true;
+ }
+ });
+ findPreference(UserPreferences.PREF_THEME)
+ .setOnPreferenceChangeListener(
+ new OnPreferenceChangeListener() {
+
+ @Override
+ public boolean onPreferenceChange(
+ Preference preference, Object newValue) {
+ Intent i = getIntent();
+ i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ finish();
+ startActivity(i);
+ return true;
+ }
+ });
+ findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
+ .setOnPreferenceChangeListener(
+ new OnPreferenceChangeListener() {
+
+ @Override
+ public boolean onPreferenceChange(
+ Preference preference, Object newValue) {
+ if (newValue instanceof Boolean) {
+ setSelectedNetworksEnabled((Boolean) newValue);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ });
+ findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
+ .setOnPreferenceChangeListener(
+ new OnPreferenceChangeListener() {
+
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ checkItemVisibility();
+ return true;
+ }
+ });
+ findPreference(PREF_PLAYBACK_SPEED_LAUNCHER)
+ .setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ VariableSpeedDialog.showDialog(PreferenceActivity.this);
+ return true;
+ }
+ });
+ buildUpdateIntervalPreference();
+ buildAutodownloadSelectedNetworsPreference();
+ setSelectedNetworksEnabled(UserPreferences
+ .isEnableAutodownloadWifiFilter());
+
+ }
+
+ private void buildUpdateIntervalPreference() {
+ ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_UPDATE_INTERVAL);
+ String[] values = getResources().getStringArray(
+ R.array.update_intervall_values);
+ String[] entries = new String[values.length];
+ for (int x = 0; x < values.length; x++) {
+ Integer v = Integer.parseInt(values[x]);
+ switch (v) {
+ case 0:
+ entries[x] = getString(R.string.pref_update_interval_hours_manual);
+ break;
+ case 1:
+ entries[x] = v
+ + " "
+ + getString(R.string.pref_update_interval_hours_singular);
+ break;
+ default:
+ entries[x] = v + " "
+ + getString(R.string.pref_update_interval_hours_plural);
+ break;
+
+ }
+ }
+ pref.setEntries(entries);
+
+ }
+
+ private void setSelectedNetworksEnabled(boolean b) {
+ if (selectedNetworks != null) {
+ for (Preference p : selectedNetworks) {
+ p.setEnabled(b);
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ checkItemVisibility();
+ setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
+ setDataFolderText();
+ }
+
+ @SuppressWarnings("deprecation")
+ private void checkItemVisibility() {
+
+ boolean hasFlattrToken = FlattrUtils.hasToken();
+
+ findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
+ findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
+
+ findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
+ .setEnabled(UserPreferences.isEnableAutodownload());
+ setSelectedNetworksEnabled(UserPreferences.isEnableAutodownload()
+ && UserPreferences.isEnableAutodownloadWifiFilter());
+
+ }
+
+ private void setEpisodeCacheSizeText(int cacheSize) {
+ String s;
+ if (cacheSize == getResources().getInteger(
+ R.integer.episode_cache_size_unlimited)) {
+ s = getString(R.string.pref_episode_cache_unlimited);
+ } else {
+ s = Integer.toString(cacheSize)
+ + getString(R.string.episodes_suffix);
+ }
+ findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s);
+ }
+
+ private void setDataFolderText() {
+ File f = UserPreferences.getDataFolder(this, null);
+ if (f != null) {
+ findPreference(PREF_CHOOSE_DATA_DIR)
+ .setSummary(f.getAbsolutePath());
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
+ theme.applyStyle(UserPreferences.getTheme(), true);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
+ String dir = data
+ .getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting data folder");
+ UserPreferences.setDataFolder(dir);
+ }
+ }
+
+ private void buildAutodownloadSelectedNetworsPreference() {
+ if (selectedNetworks != null) {
+ clearAutodownloadSelectedNetworsPreference();
+ }
+ // get configured networks
+ WifiManager wifiservice = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ List<WifiConfiguration> networks = wifiservice.getConfiguredNetworks();
+
+ if (networks != null) {
+ selectedNetworks = new CheckBoxPreference[networks.size()];
+ List<String> prefValues = Arrays.asList(UserPreferences
+ .getAutodownloadSelectedNetworks());
+ PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
+ OnPreferenceClickListener clickListener = new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference instanceof CheckBoxPreference) {
+ String key = preference.getKey();
+ ArrayList<String> prefValuesList = new ArrayList<String>(
+ Arrays.asList(UserPreferences
+ .getAutodownloadSelectedNetworks()));
+ boolean newValue = ((CheckBoxPreference) preference)
+ .isChecked();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Selected network " + key
+ + ". New state: " + newValue);
+
+ int index = prefValuesList.indexOf(key);
+ if (index >= 0 && newValue == false) {
+ // remove network
+ prefValuesList.remove(index);
+ } else if (index < 0 && newValue == true) {
+ prefValuesList.add(key);
+ }
+
+ UserPreferences.setAutodownloadSelectedNetworks(
+ PreferenceActivity.this, prefValuesList
+ .toArray(new String[prefValuesList
+ .size()]));
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+ // create preference for each known network. attach listener and set
+ // value
+ for (int i = 0; i < networks.size(); i++) {
+ WifiConfiguration config = networks.get(i);
+
+ CheckBoxPreference pref = new CheckBoxPreference(this);
+ String key = Integer.toString(config.networkId);
+ pref.setTitle(config.SSID);
+ pref.setKey(key);
+ pref.setOnPreferenceClickListener(clickListener);
+ pref.setPersistent(false);
+ pref.setChecked(prefValues.contains(key));
+ selectedNetworks[i] = pref;
+ prefScreen.addPreference(pref);
+ }
+ } else {
+ Log.e(TAG, "Couldn't get list of configure Wi-Fi networks");
+ }
+ }
+
+ private void clearAutodownloadSelectedNetworsPreference() {
+ if (selectedNetworks != null) {
+ PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
+
+ for (int i = 0; i < selectedNetworks.length; i++) {
+ if (selectedNetworks[i] != null) {
+ prefScreen.removePreference(selectedNetworks[i]);
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+ super.onPreferenceTreeClick(preferenceScreen, preference);
+ if (preference != null)
+ if (preference instanceof PreferenceScreen)
+ if (((PreferenceScreen) preference).getDialog() != null)
+ ((PreferenceScreen) preference)
+ .getDialog()
+ .getWindow()
+ .getDecorView()
+ .setBackgroundDrawable(
+ this.getWindow().getDecorView()
+ .getBackground().getConstantState()
+ .newDrawable());
+ return false;
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/SearchActivity.java b/src/de/danoeh/antennapod/activity/SearchActivity.java
index 152710112..86f7301cf 100644
--- a/src/de/danoeh/antennapod/activity/SearchActivity.java
+++ b/src/de/danoeh/antennapod/activity/SearchActivity.java
@@ -1,185 +1,198 @@
package de.danoeh.antennapod.activity;
import java.util.ArrayList;
+import java.util.List;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
+import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.SearchlistAdapter;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
-import de.danoeh.antennapod.feed.FeedSearcher;
+import de.danoeh.antennapod.storage.FeedSearcher;
import de.danoeh.antennapod.feed.SearchResult;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
-/** Displays the results when the user searches for FeedItems or Feeds. */
-public class SearchActivity extends SherlockListActivity {
- private static final String TAG = "SearchActivity";
-
- public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.searchactivity.extra.feedId";
-
- private SearchlistAdapter searchAdapter;
- private ArrayList<SearchResult> content;
-
- /** Feed that is being searched or null if the search is global. */
- private Feed selectedFeed;
-
- private TextView txtvStatus;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.searchlist);
- txtvStatus = (TextView) findViewById(android.R.id.empty);
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- setIntent(intent);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Intent intent = getIntent();
- if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
- Bundle extra = intent.getBundleExtra(SearchManager.APP_DATA);
- if (extra != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Found bundle extra");
- long feedId = extra.getLong(EXTRA_FEED_ID);
- selectedFeed = FeedManager.getInstance().getFeed(feedId);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Starting search");
- String query = intent.getStringExtra(SearchManager.QUERY);
- getSupportActionBar()
- .setSubtitle(
- getString(R.string.search_term_label) + "\""
- + query + "\"");
- handleSearchRequest(query);
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
- .setIcon(
- obtainStyledAttributes(
- new int[] { R.attr.action_search })
- .getDrawable(0))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- return true;
- case R.id.search_item:
- onSearchRequested();
- return true;
- default:
- return false;
- }
- }
-
- @Override
- public boolean onSearchRequested() {
- Bundle extra = null;
- if (selectedFeed != null) {
- extra = new Bundle();
- extra.putLong(EXTRA_FEED_ID, selectedFeed.getId());
- }
- startSearch(null, false, extra, false);
- return true;
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- super.onListItemClick(l, v, position, id);
- SearchResult selection = searchAdapter.getItem(position);
- if (selection.getComponent().getClass() == Feed.class) {
- Feed feed = (Feed) selection.getComponent();
- Intent launchIntent = new Intent(this, FeedItemlistActivity.class);
- launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED,
- feed.getId());
- startActivity(launchIntent);
-
- } else if (selection.getComponent().getClass() == FeedItem.class) {
- FeedItem item = (FeedItem) selection.getComponent();
- Intent launchIntent = new Intent(this, ItemviewActivity.class);
- launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED, item
- .getFeed().getId());
- launchIntent.putExtra(ItemlistFragment.EXTRA_SELECTED_FEEDITEM,
- item.getId());
- startActivity(launchIntent);
- }
- }
-
- @SuppressLint({ "NewApi", "NewApi" })
- private void handleSearchRequest(final String query) {
- if (searchAdapter != null) {
- searchAdapter.clear();
- searchAdapter.notifyDataSetChanged();
- }
- txtvStatus.setText(R.string.search_status_searching);
-
- Thread thread = new Thread() {
-
- @Override
- public void run() {
- Log.d(TAG, "Starting background work");
- final ArrayList<SearchResult> result = FeedSearcher
- .performSearch(SearchActivity.this, query, selectedFeed);
- if (SearchActivity.this != null) {
- SearchActivity.this.runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Background work finished");
- if (AppConfig.DEBUG)
- Log.d(TAG, "Found " + result.size()
- + " results");
- content = result;
-
- searchAdapter = new SearchlistAdapter(
- SearchActivity.this, 0, content);
- getListView().setAdapter(searchAdapter);
- searchAdapter.notifyDataSetChanged();
- if (content.isEmpty()) {
- txtvStatus
- .setText(R.string.search_status_no_results);
- }
- }
- });
- }
- }
- };
- thread.start();
-
- }
+/**
+ * Displays the results when the user searches for FeedItems or Feeds.
+ */
+public class SearchActivity extends ActionBarActivity implements AdapterView.OnItemClickListener {
+ private static final String TAG = "SearchActivity";
+
+ public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.searchactivity.extra.feedId";
+
+ private SearchlistAdapter searchAdapter;
+
+ /**
+ * ID of the feed that is being searched or null if the search is global.
+ */
+ private long feedID;
+
+ private ListView listView;
+ private TextView txtvStatus;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.searchlist);
+ listView = (ListView) findViewById(android.R.id.list);
+ txtvStatus = (TextView) findViewById(android.R.id.empty);
+
+ listView.setOnItemClickListener(this);
+ searchAdapter = new SearchlistAdapter(this, 0, new ArrayList<SearchResult>());
+ listView.setAdapter(searchAdapter);
+ listView.setEmptyView(txtvStatus);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Intent intent = getIntent();
+ if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ if (intent.hasExtra(SearchActivity.EXTRA_FEED_ID)) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Found bundle extra");
+ feedID = intent.getLongExtra(SearchActivity.EXTRA_FEED_ID, 0);
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Starting search");
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ getSupportActionBar()
+ .setSubtitle(
+ getString(R.string.search_term_label) + "\""
+ + query + "\"");
+ handleSearchRequest(query);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
+ .setIcon(
+ obtainStyledAttributes(
+ new int[]{R.attr.action_search})
+ .getDrawable(0)),
+ (MenuItem.SHOW_AS_ACTION_IF_ROOM));
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+ case R.id.search_item:
+ onSearchRequested();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ Bundle extra = null;
+ if (feedID != 0) {
+ extra = new Bundle();
+ extra.putLong(EXTRA_FEED_ID, feedID);
+ }
+ startSearch(null, false, extra, false);
+ return true;
+ }
+
+ @SuppressLint({"NewApi", "NewApi"})
+ private void handleSearchRequest(final String query) {
+ if (searchAdapter != null) {
+ searchAdapter.clear();
+ searchAdapter.notifyDataSetChanged();
+ }
+ txtvStatus.setText(R.string.search_status_searching);
+
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ Log.d(TAG, "Starting background work");
+ final Activity activity = SearchActivity.this;
+ final List<SearchResult> result = FeedSearcher
+ .performSearch(activity, query, feedID);
+ activity.runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Background work finished");
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Found " + result.size()
+ + " results");
+
+ searchAdapter.clear();
+ for (SearchResult s : result) {
+ searchAdapter.add(s);
+ }
+ searchAdapter.notifyDataSetChanged();
+ txtvStatus
+ .setText(R.string.search_status_no_results);
+ if (!searchAdapter.isEmpty()) {
+ txtvStatus.setVisibility(View.GONE);
+ } else {
+ txtvStatus.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+
+ }
+ };
+ thread.start();
+
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
+ SearchResult selection = searchAdapter.getItem(position);
+ if (selection.getComponent().getClass() == Feed.class) {
+ Feed feed = (Feed) selection.getComponent();
+ Intent launchIntent = new Intent(this, FeedItemlistActivity.class);
+ launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED,
+ feed.getId());
+ startActivity(launchIntent);
+
+ } else if (selection.getComponent().getClass() == FeedItem.class) {
+ FeedItem item = (FeedItem) selection.getComponent();
+ Intent launchIntent = new Intent(this, ItemviewActivity.class);
+ launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED, item
+ .getFeed().getId());
+ launchIntent.putExtra(ItemlistFragment.EXTRA_SELECTED_FEEDITEM,
+ item.getId());
+ startActivity(launchIntent);
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/StorageErrorActivity.java b/src/de/danoeh/antennapod/activity/StorageErrorActivity.java
index 4d9184dcf..33277ebc9 100644
--- a/src/de/danoeh/antennapod/activity/StorageErrorActivity.java
+++ b/src/de/danoeh/antennapod/activity/StorageErrorActivity.java
@@ -5,17 +5,16 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockActivity;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.StorageUtils;
/** Is show if there is now external storage available. */
-public class StorageErrorActivity extends SherlockActivity {
+public class StorageErrorActivity extends ActionBarActivity {
private static final String TAG = "StorageErrorActivity";
@Override
diff --git a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java
index b3567e417..01841f099 100644
--- a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java
+++ b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java
@@ -5,18 +5,13 @@ import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
-import android.view.MotionEvent;
-import android.view.SurfaceHolder;
-import android.view.View;
-import android.view.WindowManager;
+import android.view.*;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.VideoView;
-import com.actionbarsherlock.view.Window;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.MediaType;
diff --git a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java
index 3e9b586ce..5a8dfb2bf 100644
--- a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java
@@ -144,53 +144,6 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
TextView link;
}
- private LinkMovementMethod linkMovementMethod = new LinkMovementMethod() {
-
- @Override
- public boolean onTouchEvent(TextView widget, Spannable buffer,
- MotionEvent event) {
- Object text = widget.getText();
- if (text instanceof Spanned) {
- int action = event.getAction();
-
- if (action == MotionEvent.ACTION_UP
- || action == MotionEvent.ACTION_DOWN) {
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
- int off = layout.getOffsetForHorizontal(line, x);
-
- ClickableSpan[] link = buffer.getSpans(off, off,
- ClickableSpan.class);
-
- if (link.length != 0) {
- if (action == MotionEvent.ACTION_UP) {
- link[0].onClick(widget);
- } else if (action == MotionEvent.ACTION_DOWN) {
- Selection.setSelection(buffer,
- buffer.getSpanStart(link[0]),
- buffer.getSpanEnd(link[0]));
- }
- return true;
- }
- }
-
- }
-
- return false;
-
- }
-
- };
-
@Override
public int getCount() {
// ignore invalid chapters
diff --git a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
index f97210cf3..c067ac5d2 100644
--- a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
@@ -8,21 +8,22 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
/** Displays a list of DownloadStatus entries. */
public class DownloadLogAdapter extends BaseAdapter {
private Context context;
- private FeedManager manager = FeedManager.getInstance();
- public DownloadLogAdapter(Context context) {
+ private ItemAccess itemAccess;
+
+ public DownloadLogAdapter(Context context, ItemAccess itemAccess) {
super();
+ this.itemAccess = itemAccess;
this.context = context;
}
@@ -70,8 +71,7 @@ public class DownloadLogAdapter extends BaseAdapter {
holder.successful.setTextColor(convertView.getResources().getColor(
R.color.download_failed_red));
holder.successful.setText(R.string.download_failed);
- String reasonText = DownloadError.getErrorString(context,
- status.getReason());
+ String reasonText = status.getReason().getErrorString(context);
if (status.getReasonDetailed() != null) {
reasonText += ": " + status.getReasonDetailed();
}
@@ -92,12 +92,12 @@ public class DownloadLogAdapter extends BaseAdapter {
@Override
public int getCount() {
- return manager.getDownloadLogSize();
+ return itemAccess.getCount();
}
@Override
public DownloadStatus getItem(int position) {
- return manager.getDownloadStatusFromLogAtIndex(position);
+ return itemAccess.getItem(position);
}
@Override
@@ -105,4 +105,9 @@ public class DownloadLogAdapter extends BaseAdapter {
return position;
}
+ public static interface ItemAccess {
+ public int getCount();
+ public DownloadStatus getItem(int position);
+ }
+
}
diff --git a/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
index 685906d6f..75e837969 100644
--- a/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
@@ -10,11 +10,12 @@ import android.widget.ArrayAdapter;
import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
-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.FeedMedia;
+import de.danoeh.antennapod.service.download.DownloadRequest;
+import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.ThemeUtils;
@@ -33,8 +34,7 @@ public class DownloadlistAdapter extends ArrayAdapter<Downloader> {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
- DownloadStatus status = getItem(position).getStatus();
- FeedFile feedFile = status.getFeedFile();
+ DownloadRequest request = getItem(position).getDownloadRequest();
// Inflate layout
if (convertView == null) {
holder = new Holder();
@@ -62,31 +62,16 @@ public class DownloadlistAdapter extends ArrayAdapter<Downloader> {
} else {
convertView.setBackgroundResource(0);
}
-
- String titleText = null;
- if (feedFile.getClass() == FeedMedia.class) {
- titleText = ((FeedMedia) feedFile).getItem().getTitle();
- } else if (feedFile.getClass() == Feed.class) {
- titleText = ((Feed) feedFile).getTitle();
- } else if (feedFile.getClass() == FeedImage.class) {
- FeedImage image = (FeedImage) feedFile;
- if (image.getFeed() != null) {
- titleText = convertView.getResources().getString(
- R.string.image_of_prefix)
- + image.getFeed().getTitle();
- } else {
- titleText = ((FeedImage) feedFile).getTitle();
- }
- }
- holder.title.setText(titleText);
- if (status.getStatusMsg() != 0) {
- holder.message.setText(status.getStatusMsg());
+
+ holder.title.setText(request.getTitle());
+ if (request.getStatusMsg() != 0) {
+ holder.message.setText(request.getStatusMsg());
}
- String strDownloaded = Converter.byteToString(status.getSoFar());
- if (status.getSize() != DownloadStatus.SIZE_UNKNOWN) {
- strDownloaded += " / " + Converter.byteToString(status.getSize());
- holder.percent.setText(status.getProgressPercent() + "%");
- holder.progbar.setProgress(status.getProgressPercent());
+ String strDownloaded = Converter.byteToString(request.getSoFar());
+ if (request.getSize() != DownloadStatus.SIZE_UNKNOWN) {
+ strDownloaded += " / " + Converter.byteToString(request.getSize());
+ holder.percent.setText(request.getProgressPercent() + "%");
+ holder.progbar.setProgress(request.getProgressPercent());
holder.percent.setVisibility(View.VISIBLE);
} else {
holder.progbar.setProgress(0);
diff --git a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java
index 916e13469..b3156f765 100644
--- a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java
@@ -14,7 +14,6 @@ import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.Converter;
@@ -30,17 +29,18 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
public static final int GROUP_POS_UNREAD = 1;
private Context context;
- private FeedManager manager = FeedManager.getInstance();
+ private ItemAccess itemAccess;
private ActionButtonCallback feedItemActionCallback;
private OnGroupActionClicked groupActionCallback;
public ExternalEpisodesListAdapter(Context context,
ActionButtonCallback callback,
- OnGroupActionClicked groupActionCallback) {
+ OnGroupActionClicked groupActionCallback,
+ ItemAccess itemAccess) {
super();
this.context = context;
-
+ this.itemAccess = itemAccess;
this.feedItemActionCallback = callback;
this.groupActionCallback = groupActionCallback;
}
@@ -53,10 +53,10 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public FeedItem getChild(int groupPosition, int childPosition) {
if (groupPosition == GROUP_POS_QUEUE) {
- return manager.getQueueItemAtIndex(childPosition, true);
+ return itemAccess.getQueueItemAt(childPosition);
} else if (groupPosition == GROUP_POS_UNREAD) {
- return manager.getUnreadItemAtIndex(childPosition, true);
- }
+ return itemAccess.getUnreadItemAt(childPosition);
+ }
return null;
}
@@ -200,9 +200,9 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public int getChildrenCount(int groupPosition) {
if (groupPosition == GROUP_POS_QUEUE) {
- return manager.getQueueSize(true);
+ return itemAccess.getQueueSize();
} else if (groupPosition == GROUP_POS_UNREAD) {
- return manager.getUnreadItemsSize(true);
+ return itemAccess.getUnreadItemsSize();
}
return 0;
}
@@ -210,7 +210,7 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public int getGroupCount() {
// Hide 'unread items' group if empty
- if (manager.getUnreadItemsSize(true) > 0) {
+ if (itemAccess.getUnreadItemsSize() > 0) {
return 2;
} else {
return 1;
@@ -264,8 +264,8 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public boolean isEmpty() {
- return manager.getUnreadItemsSize(true) == 0
- && manager.getQueueSize(true) == 0;
+ return itemAccess.getUnreadItemsSize() == 0
+ && itemAccess.getQueueSize() == 0;
}
@Override
@@ -287,4 +287,11 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
public void onClick(long groupId);
}
+ public static interface ItemAccess {
+ public int getQueueSize();
+ public int getUnreadItemsSize();
+ public FeedItem getQueueItemAt(int position);
+ public FeedItem getUnreadItemAt(int position);
+ }
+
}
diff --git a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java
index 03b46cce5..89427a47e 100644
--- a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java
@@ -11,23 +11,31 @@ import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DownloadRequester;
+import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.ThemeUtils;
public class FeedlistAdapter extends BaseAdapter {
private static final String TAG = "FeedlistAdapter";
private Context context;
- private FeedManager manager = FeedManager.getInstance();
+ protected ItemAccess itemAccess;
private int selectedItemIndex;
private ImageLoader imageLoader;
public static final int SELECTION_NONE = -1;
- public FeedlistAdapter(Context context) {
+ public FeedlistAdapter(Context context, ItemAccess itemAccess) {
super();
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+ if (itemAccess == null) {
+ throw new IllegalArgumentException("itemAccess must not be null");
+ }
+
this.context = context;
+ this.itemAccess = itemAccess;
selectedItemIndex = SELECTION_NONE;
imageLoader = ImageLoader.getInstance();
}
@@ -36,6 +44,7 @@ public class FeedlistAdapter extends BaseAdapter {
public View getView(int position, View convertView, ViewGroup parent) {
final Holder holder;
final Feed feed = getItem(position);
+ final FeedItemStatistics feedItemStatistics = itemAccess.getFeedItemStatistics(position);
// Inflate Layout
if (convertView == null) {
@@ -75,42 +84,40 @@ public class FeedlistAdapter extends BaseAdapter {
}
holder.title.setText(feed.getTitle());
- int numOfItems = feed.getNumOfItems(true);
- if (DownloadRequester.getInstance().isDownloadingFile(feed)) {
- holder.lastUpdate.setText(R.string.refreshing_label);
- } else {
- if (numOfItems > 0) {
- holder.lastUpdate.setText(convertView.getResources().getString(
- R.string.most_recent_prefix)
- + DateUtils.getRelativeTimeSpanString(
- feed.getItemAtIndex(true, 0).getPubDate().getTime(),
- System.currentTimeMillis(), 0, 0));
- } else {
- holder.lastUpdate.setText("");
- }
- }
- holder.numberOfEpisodes.setText(numOfItems
- + convertView.getResources()
- .getString(R.string.episodes_suffix));
-
- int newItems = feed.getNumOfNewItems();
- int inProgressItems = feed.getNumOfStartedItems();
-
- if (newItems > 0) {
- holder.newEpisodes.setText(Integer.toString(newItems));
- holder.newEpisodesLabel.setVisibility(View.VISIBLE);
- } else {
- holder.newEpisodesLabel.setVisibility(View.INVISIBLE);
- }
-
- if (inProgressItems > 0) {
- holder.inProgressEpisodes
- .setText(Integer.toString(inProgressItems));
- holder.inProgressEpisodesLabel.setVisibility(View.VISIBLE);
- } else {
- holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE);
- }
+ if (feedItemStatistics != null) {
+ if (DownloadRequester.getInstance().isDownloadingFile(feed)) {
+ holder.lastUpdate.setText(R.string.refreshing_label);
+ } else {
+ if (feedItemStatistics.getNumberOfItems() > 0) {
+ holder.lastUpdate.setText(convertView.getResources().getString(
+ R.string.most_recent_prefix)
+ + DateUtils.getRelativeTimeSpanString(
+ feedItemStatistics.getLastUpdate().getTime(),
+ System.currentTimeMillis(), 0, 0));
+ } else {
+ holder.lastUpdate.setText("");
+ }
+ }
+ holder.numberOfEpisodes.setText(feedItemStatistics.getNumberOfItems()
+ + convertView.getResources()
+ .getString(R.string.episodes_suffix));
+
+ if (feedItemStatistics.getNumberOfNewItems() > 0) {
+ holder.newEpisodes.setText(Integer.toString(feedItemStatistics.getNumberOfNewItems()));
+ holder.newEpisodesLabel.setVisibility(View.VISIBLE);
+ } else {
+ holder.newEpisodesLabel.setVisibility(View.INVISIBLE);
+ }
+
+ if (feedItemStatistics.getNumberOfInProgressItems() > 0) {
+ holder.inProgressEpisodes
+ .setText(Integer.toString(feedItemStatistics.getNumberOfInProgressItems()));
+ holder.inProgressEpisodesLabel.setVisibility(View.VISIBLE);
+ } else {
+ holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE);
+ }
+ }
final String imageUrl = (feed.getImage() != null) ? feed.getImage()
.getFile_url() : null;
holder.image.setTag(imageUrl);
@@ -145,12 +152,12 @@ public class FeedlistAdapter extends BaseAdapter {
@Override
public int getCount() {
- return manager.getFeedsSize();
+ return itemAccess.getCount();
}
@Override
public Feed getItem(int position) {
- return manager.getFeedAtIndex(position);
+ return itemAccess.getItem(position);
}
@Override
@@ -158,4 +165,11 @@ public class FeedlistAdapter extends BaseAdapter {
return position;
}
+ public interface ItemAccess {
+ int getCount();
+
+ Feed getItem(int position);
+
+ FeedItemStatistics getFeedItemStatistics(int position);
+ }
}
diff --git a/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java
index e5c12f018..b8bec44c8 100644
--- a/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java
@@ -14,13 +14,14 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
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.storage.DownloadRequester;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.ThemeUtils;
+import java.util.Iterator;
+
/** List adapter for items of feeds that the user has already subscribed to. */
public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
@@ -31,7 +32,7 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
public static final int SELECTION_NONE = -1;
public InternalFeedItemlistAdapter(Context context,
- DefaultFeedItemlistAdapter.ItemAccess itemAccess,
+ ItemAccess itemAccess,
ActionButtonCallback callback, boolean showFeedtitle) {
super(context, itemAccess);
this.callback = callback;
@@ -155,7 +156,7 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
}
holder.lenSize.setVisibility(View.VISIBLE);
- if (FeedManager.getInstance().isInQueue(item)) {
+ if (((ItemAccess) itemAccess).isInQueue(item)) {
holder.inPlaylist.setVisibility(View.VISIBLE);
} else {
holder.inPlaylist.setVisibility(View.GONE);
@@ -224,4 +225,8 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
notifyDataSetChanged();
}
+ public static interface ItemAccess extends DefaultFeedItemlistAdapter.ItemAccess {
+ public boolean isInQueue(FeedItem item);
+ }
+
}
diff --git a/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java b/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java
index 4380bc6ea..7ba68ae22 100644
--- a/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java
+++ b/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java
@@ -47,7 +47,7 @@ public class BitmapDecodeWorkerTask extends Thread {
*/
protected boolean tagsMatching(ImageView target) {
return target.getTag() == null
- || target.getTag() == imageResource.getImageLoaderCacheKey();
+ || target.getTag().equals(imageResource.getImageLoaderCacheKey());
}
protected void onPostExecute() {
diff --git a/src/de/danoeh/antennapod/asynctask/DownloadStatus.java b/src/de/danoeh/antennapod/asynctask/DownloadStatus.java
deleted file mode 100644
index e9225d33b..000000000
--- a/src/de/danoeh/antennapod/asynctask/DownloadStatus.java
+++ /dev/null
@@ -1,198 +0,0 @@
-package de.danoeh.antennapod.asynctask;
-
-import java.util.Date;
-
-import de.danoeh.antennapod.feed.FeedFile;
-
-/** 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;
-
- public Date getCompletionDate() {
- return completionDate;
- }
-
- // ----------------------------------- 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 int 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 FeedFile feedfile;
- /**
- * 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 int progressPercent;
- protected long soFar;
- protected long size;
- protected int statusMsg;
- protected boolean done;
- protected boolean cancelled;
-
- public DownloadStatus(FeedFile feedfile, String title) {
- this.feedfile = feedfile;
- if (feedfile != null) {
- feedfileType = feedfile.getTypeAsInt();
- }
- this.title = title;
- }
-
- /** Constructor for restoring Download status entries from DB. */
- public DownloadStatus(long id, String title, FeedFile feedfile,
- int feedfileType, boolean successful, int reason,
- Date completionDate, String reasonDetailed) {
- progressPercent = 100;
- soFar = 0;
- size = 0;
-
- this.id = id;
- this.title = title;
- this.done = true;
- this.feedfile = feedfile;
- this.reason = reason;
- this.successful = successful;
- this.completionDate = completionDate;
- this.reasonDetailed = reasonDetailed;
- this.feedfileType = feedfileType;
- }
-
- /** Constructor for creating new completed downloads. */
- public DownloadStatus(FeedFile feedfile, String title, int reason,
- boolean successful, String reasonDetailed) {
- this(0, title, feedfile, feedfile.getTypeAsInt(), successful, reason,
- new Date(), reasonDetailed);
- }
-
- @Override
- public String toString() {
- return "DownloadStatus [id=" + id + ", title=" + title + ", reason="
- + reason + ", reasonDetailed=" + reasonDetailed
- + ", successful=" + successful + ", completionDate="
- + completionDate + ", feedfile=" + feedfile + ", feedfileType="
- + feedfileType + ", progressPercent=" + progressPercent
- + ", soFar=" + soFar + ", size=" + size + ", statusMsg="
- + statusMsg + ", done=" + done + ", cancelled=" + cancelled
- + "]";
- }
-
- public FeedFile getFeedFile() {
- return feedfile;
- }
-
- public int getProgressPercent() {
- return progressPercent;
- }
-
- public long getSoFar() {
- return soFar;
- }
-
- public long getSize() {
- return size;
- }
-
- public int getStatusMsg() {
- return statusMsg;
- }
-
- public int getReason() {
- return reason;
- }
-
- public boolean isSuccessful() {
- return successful;
- }
-
- public long getId() {
- return id;
- }
-
- public void setId(long id) {
- this.id = id;
- }
-
- public boolean isDone() {
- return done;
- }
-
- 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 String getReasonDetailed() {
- return reasonDetailed;
- }
-
- public void setReasonDetailed(String reasonDetailed) {
- this.reasonDetailed = reasonDetailed;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public int getFeedfileType() {
- return feedfileType;
- }
-
- public boolean isCancelled() {
- return cancelled;
- }
-
- public void setCancelled(boolean cancelled) {
- this.cancelled = cancelled;
- }
-
-} \ No newline at end of file
diff --git a/src/de/danoeh/antennapod/asynctask/FeedRemover.java b/src/de/danoeh/antennapod/asynctask/FeedRemover.java
index 829a14602..244312a6e 100644
--- a/src/de/danoeh/antennapod/asynctask/FeedRemover.java
+++ b/src/de/danoeh/antennapod/asynctask/FeedRemover.java
@@ -7,7 +7,9 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.AsyncTask;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBWriter;
+
+import java.util.concurrent.ExecutionException;
/** Removes a feed in the background. */
public class FeedRemover extends AsyncTask<Void, Void, Void> {
@@ -23,9 +25,14 @@ public class FeedRemover extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
- FeedManager manager = FeedManager.getInstance();
- manager.deleteFeed(context, feed);
- return null;
+ try {
+ DBWriter.deleteFeed(context, feed.getId()).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ return null;
}
@Override
diff --git a/src/de/danoeh/antennapod/asynctask/ImageLoader.java b/src/de/danoeh/antennapod/asynctask/ImageLoader.java
index fb807f469..45a99e704 100644
--- a/src/de/danoeh/antennapod/asynctask/ImageLoader.java
+++ b/src/de/danoeh/antennapod/asynctask/ImageLoader.java
@@ -77,7 +77,7 @@ public class ImageLoader {
});
}
- public static ImageLoader getInstance() {
+ public static synchronized ImageLoader getInstance() {
if (singleton == null) {
singleton = new ImageLoader();
}
diff --git a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
index 978f53ac6..745bc7079 100644
--- a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
+++ b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
@@ -1,8 +1,9 @@
package de.danoeh.antennapod.asynctask;
import java.io.File;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.util.Arrays;
import android.annotation.SuppressLint;
@@ -14,9 +15,10 @@ import android.os.AsyncTask;
import android.util.Log;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.opml.OpmlWriter;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.util.LangUtils;
+import de.danoeh.antennapod.storage.DBReader;
/** Writes an OPML file into the export directory in the background. */
public class OpmlExportWorker extends AsyncTask<Void, Void, Void> {
@@ -49,14 +51,21 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> {
output.delete();
}
}
+ OutputStreamWriter writer = null;
try {
- FileWriter writer = new FileWriter(output);
- opmlWriter.writeDocument(Arrays.asList(FeedManager.getInstance().getFeedsArray()),
- writer);
- writer.close();
+ writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8);
+ opmlWriter.writeDocument(DBReader.getFeedList(context), writer);
} catch (IOException e) {
e.printStackTrace();
exception = e;
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException ioe) {
+ exception = ioe;
+ }
+ }
}
return null;
}
diff --git a/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java b/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java
index 4d9c9867e..64e678086 100644
--- a/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java
+++ b/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.asynctask;
+import java.util.Arrays;
import java.util.Date;
import android.annotation.SuppressLint;
@@ -22,7 +23,7 @@ public class OpmlFeedQueuer extends AsyncTask<Void, Void, Void> {
public OpmlFeedQueuer(Context context, int[] selection) {
super();
this.context = context;
- this.selection = selection;
+ this.selection = Arrays.copyOf(selection, selection.length);
}
@Override
diff --git a/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java
index 5af06895f..4816c25ab 100644
--- a/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java
+++ b/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java
@@ -64,6 +64,13 @@ public class OpmlImportWorker extends
@Override
protected void onPostExecute(ArrayList<OpmlElement> result) {
+ if (mReader != null) {
+ try {
+ mReader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
progDialog.dismiss();
if (exception != null) {
if (AppConfig.DEBUG)
diff --git a/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java
new file mode 100644
index 000000000..e6cbe37d1
--- /dev/null
+++ b/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java
@@ -0,0 +1,100 @@
+package de.danoeh.antennapod.dialog;
+
+import java.util.Arrays;
+import java.util.List;
+
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.preferences.UserPreferences;
+
+public class VariableSpeedDialog {
+ private VariableSpeedDialog() {
+ }
+
+ public static void showDialog(final Context context) {
+ if (com.aocate.media.MediaPlayer.isPrestoLibraryInstalled(context)) {
+ showSpeedSelectorDialog(context);
+ } else {
+ showGetPluginDialog(context);
+ }
+ }
+
+ private static void showGetPluginDialog(final Context context) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.no_playback_plugin_title);
+ builder.setMessage(R.string.no_playback_plugin_msg);
+ builder.setNegativeButton(R.string.close_label, null);
+ builder.setPositiveButton(R.string.download_plugin_label,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ try {
+ Intent playStoreIntent = new Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=com.falconware.prestissimo"));
+ context.startActivity(playStoreIntent);
+ } catch (ActivityNotFoundException e) {
+ // this is usually thrown on an emulator if the Android market is not installed
+ e.printStackTrace();
+ }
+ }
+ });
+ builder.create().show();
+ }
+
+ private static void showSpeedSelectorDialog(final Context context) {
+ final String[] speedValues = context.getResources().getStringArray(
+ R.array.playback_speed_values);
+ // According to Java spec these get initialized to false on creation
+ final boolean[] speedChecked = new boolean[speedValues.length];
+
+ // Build the "isChecked" array so that multiChoice dialog is
+ // populated correctly
+ List<String> selectedSpeedList = Arrays.asList(UserPreferences
+ .getPlaybackSpeedArray());
+ for (int i = 0; i < speedValues.length; i++) {
+ speedChecked[i] = selectedSpeedList.contains(speedValues[i]);
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.set_playback_speed_label);
+ builder.setMultiChoiceItems(R.array.playback_speed_values,
+ speedChecked, new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which,
+ boolean isChecked) {
+ speedChecked[which] = isChecked;
+ }
+
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int choiceCount = 0;
+ for (int i = 0; i < speedChecked.length; i++) {
+ if (speedChecked[i]) {
+ choiceCount++;
+ }
+ }
+ String[] newSpeedValues = new String[choiceCount];
+ int newSpeedIndex = 0;
+ for (int i = 0; i < speedChecked.length; i++) {
+ if (speedChecked[i]) {
+ newSpeedValues[newSpeedIndex++] = speedValues[i];
+ }
+ }
+
+ UserPreferences.setPlaybackSpeedArray(newSpeedValues);
+
+ }
+ });
+ builder.create().show();
+ }
+}
diff --git a/src/de/danoeh/antennapod/feed/EventDistributor.java b/src/de/danoeh/antennapod/feed/EventDistributor.java
index 1fc7e2c35..56333da52 100644
--- a/src/de/danoeh/antennapod/feed/EventDistributor.java
+++ b/src/de/danoeh/antennapod/feed/EventDistributor.java
@@ -39,7 +39,7 @@ public class EventDistributor extends Observable {
events = new ConcurrentLinkedQueue<Integer>();
}
- public static EventDistributor getInstance() {
+ public static synchronized EventDistributor getInstance() {
if (instance == null) {
instance = new EventDistributor();
}
@@ -92,7 +92,7 @@ public class EventDistributor extends Observable {
super.addObserver(observer);
if (!(observer instanceof EventListener)) {
throw new IllegalArgumentException(
- "Observer must be instance of FeedManager.EventListener");
+ "Observer must be instance of EventListener");
}
}
diff --git a/src/de/danoeh/antennapod/feed/Feed.java b/src/de/danoeh/antennapod/feed/Feed.java
index 6220bde00..032930f83 100644
--- a/src/de/danoeh/antennapod/feed/Feed.java
+++ b/src/de/danoeh/antennapod/feed/Feed.java
@@ -10,339 +10,360 @@ import de.danoeh.antennapod.util.EpisodeFilter;
/**
* Data Object for a whole feed
- *
+ *
* @author daniel
- *
*/
public class Feed extends FeedFile {
- public static final int FEEDFILETYPE_FEED = 0;
- public static final String TYPE_RSS2 = "rss";
- public static final String TYPE_RSS091 = "rss";
- public static final String TYPE_ATOM1 = "atom";
-
- private String title;
- /** Contains 'id'-element in Atom feed. */
- private String feedIdentifier;
- /** Link to the website. */
- private String link;
- private String description;
- private String language;
- /** Name of the author */
- private String author;
- private FeedImage image;
- private List<FeedItem> items;
- /** Date of last refresh. */
- private Date lastUpdate;
- private String paymentLink;
- /** Feed type, for example RSS 2 or Atom */
- private String type;
-
- public Feed(Date lastUpdate) {
- super();
- items = Collections.synchronizedList(new ArrayList<FeedItem>());
- this.lastUpdate = lastUpdate;
- }
-
- /**
- * This constructor is used for requesting a feed download. It should NOT be
- * used if the title of the feed is already known.
- * */
- public Feed(String url, Date lastUpdate) {
- this(lastUpdate);
- this.download_url = url;
- }
-
- /**
- * This constructor is used for requesting a feed download. It should be
- * used if the title of the feed is already known.
- * */
- public Feed(String url, Date lastUpdate, String title) {
- this(url, lastUpdate);
- this.title = title;
- }
-
- /**
- * Returns the number of FeedItems where 'read' is false. If the 'display
- * only episodes' - preference is set to true, this method will only count
- * items with episodes.
- * */
- public int getNumOfNewItems() {
- int count = 0;
- for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.NEW) {
- if (!UserPreferences.isDisplayOnlyEpisodes()
- || item.getMedia() != null) {
- count++;
- }
- }
- }
- return count;
- }
-
- /**
- * Returns the number of FeedItems where the media started to play but
- * wasn't finished yet.
- * */
- public int getNumOfStartedItems() {
- int count = 0;
-
- for (FeedItem item : items) {
- FeedItem.State state = item.getState();
- if (state == FeedItem.State.IN_PROGRESS
- || state == FeedItem.State.PLAYING) {
- count++;
- }
- }
- return count;
- }
-
- /**
- * Returns true if at least one item in the itemlist is unread.
- *
- * @param enableEpisodeFilter
- * true if this method should only count items with episodes if
- * the 'display only episodes' - preference is set to true by the
- * user.
- */
- public boolean hasNewItems(boolean enableEpisodeFilter) {
- for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.NEW) {
- if (!(enableEpisodeFilter && UserPreferences
- .isDisplayOnlyEpisodes()) || item.getMedia() != null) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns the number of FeedItems.
- *
- * @param enableEpisodeFilter
- * true if this method should only count items with episodes if
- * the 'display only episodes' - preference is set to true by the
- * user.
- * */
- public int getNumOfItems(boolean enableEpisodeFilter) {
- if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
- return EpisodeFilter.countItemsWithEpisodes(items);
- } else {
- return items.size();
- }
- }
-
- /**
- * Returns the item at the specified index.
- *
- * @param enableEpisodeFilter
- * true if this method should ignore items without episdodes if
- * the episodes filter has been enabled by the user.
- */
- public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) {
- if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
- return EpisodeFilter.accessEpisodeByIndex(items, position);
- } else {
- return items.get(position);
- }
- }
-
- /**
- * Returns the value that uniquely identifies this Feed. If the
- * feedIdentifier attribute is not null, it will be returned. Else it will
- * try to return the title. If the title is not given, it will use the link
- * of the feed.
- * */
- public String getIdentifyingValue() {
- if (feedIdentifier != null && !feedIdentifier.isEmpty()) {
- return feedIdentifier;
- } else if (title != null && !title.isEmpty()) {
- return title;
- } else {
- return link;
- }
- }
-
- @Override
- public String getHumanReadableIdentifier() {
- if (title != null) {
- return title;
- } else {
- return download_url;
- }
- }
-
- /** Calls cacheDescriptions on all items. */
- protected void cacheDescriptionsOfItems() {
- if (items != null) {
- for (FeedItem item : items) {
- item.cacheDescriptions();
- }
- }
- }
-
- public void updateFromOther(Feed other) {
- super.updateFromOther(other);
- if (other.title != null) {
- title = other.title;
- }
- if (other.feedIdentifier != null) {
- feedIdentifier = other.feedIdentifier;
- }
- if (other.link != null) {
- link = other.link;
- }
- if (other.description != null) {
- description = other.description;
- }
- if (other.language != null) {
- language = other.language;
- }
- if (other.author != null) {
- author = other.author;
- }
- if (other.paymentLink != null) {
- paymentLink = other.paymentLink;
- }
- }
-
- public boolean compareWithOther(Feed other) {
- if (super.compareWithOther(other)) {
- return true;
- }
- if (!title.equals(other.title)) {
- return true;
- }
- if (other.feedIdentifier != null) {
- if (feedIdentifier == null
- || !feedIdentifier.equals(other.feedIdentifier)) {
- return true;
- }
- }
- if (other.link != null) {
- if (link == null || !link.equals(other.link)) {
- return true;
- }
- }
- if (other.description != null) {
- if (description == null || !description.equals(other.description)) {
- return true;
- }
- }
- if (other.language != null) {
- if (language == null || !language.equals(other.language)) {
- return true;
- }
- }
- if (other.author != null) {
- if (author == null || !author.equals(other.author)) {
- return true;
- }
- }
- if (other.paymentLink != null) {
- if (paymentLink == null || !paymentLink.equals(other.paymentLink)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public int getTypeAsInt() {
- return FEEDFILETYPE_FEED;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getLink() {
- return link;
- }
-
- public void setLink(String link) {
- this.link = link;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- public FeedImage getImage() {
- return image;
- }
-
- public void setImage(FeedImage image) {
- this.image = image;
- }
-
- List<FeedItem> getItems() {
- return items;
- }
-
- public void setItems(ArrayList<FeedItem> items) {
- this.items = Collections.synchronizedList(items);
- }
-
- /** Returns an array that contains all the feeditems of this feed. */
- public FeedItem[] getItemsArray() {
- return items.toArray(new FeedItem[items.size()]);
- }
-
- public Date getLastUpdate() {
- return lastUpdate;
- }
-
- public void setLastUpdate(Date lastUpdate) {
- this.lastUpdate = lastUpdate;
- }
-
- public String getFeedIdentifier() {
- return feedIdentifier;
- }
-
- public void setFeedIdentifier(String feedIdentifier) {
- this.feedIdentifier = feedIdentifier;
- }
-
- public String getPaymentLink() {
- return paymentLink;
- }
-
- public void setPaymentLink(String paymentLink) {
- this.paymentLink = paymentLink;
- }
-
- public String getLanguage() {
- return language;
- }
-
- public void setLanguage(String language) {
- this.language = language;
- }
-
- public String getAuthor() {
- return author;
- }
-
- public void setAuthor(String author) {
- this.author = author;
- }
-
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
- }
+ public static final int FEEDFILETYPE_FEED = 0;
+ public static final String TYPE_RSS2 = "rss";
+ public static final String TYPE_RSS091 = "rss";
+ public static final String TYPE_ATOM1 = "atom";
+
+ private String title;
+ /**
+ * Contains 'id'-element in Atom feed.
+ */
+ private String feedIdentifier;
+ /**
+ * Link to the website.
+ */
+ private String link;
+ private String description;
+ private String language;
+ /**
+ * Name of the author
+ */
+ private String author;
+ private FeedImage image;
+ private List<FeedItem> items;
+ /**
+ * Date of last refresh.
+ */
+ private Date lastUpdate;
+ private String paymentLink;
+ /**
+ * Feed type, for example RSS 2 or Atom
+ */
+ private String type;
+
+ /**
+ * This constructor is used for restoring a feed from the database.
+ */
+ public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink,
+ String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
+ String downloadUrl, boolean downloaded) {
+ super(fileUrl, downloadUrl, downloaded);
+ this.id = id;
+ this.title = title;
+ if (lastUpdate != null) {
+ this.lastUpdate = (Date) lastUpdate.clone();
+ } else {
+ this.lastUpdate = null;
+ }
+ this.link = link;
+ this.description = description;
+ this.paymentLink = paymentLink;
+ this.author = author;
+ this.language = language;
+ this.type = type;
+ this.feedIdentifier = feedIdentifier;
+ this.image = image;
+
+ items = new ArrayList<FeedItem>();
+ }
+
+ /**
+ * This constructor can be used when parsing feed data. Only the 'lastUpdate' and 'items' field are initialized.
+ */
+ public Feed() {
+ super();
+ items = new ArrayList<FeedItem>();
+ lastUpdate = new Date();
+ }
+
+ /**
+ * This constructor is used for requesting a feed download (it must not be used for anything else!). It should NOT be
+ * used if the title of the feed is already known.
+ */
+ public Feed(String url, Date lastUpdate) {
+ super(null, url, false);
+ this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
+ }
+
+ /**
+ * This constructor is used for requesting a feed download (it must not be used for anything else!). It should be
+ * used if the title of the feed is already known.
+ */
+ public Feed(String url, Date lastUpdate, String title) {
+ this(url, lastUpdate);
+ this.title = title;
+ }
+
+ /**
+ * Returns the number of FeedItems where 'read' is false. If the 'display
+ * only episodes' - preference is set to true, this method will only count
+ * items with episodes.
+ */
+ public int getNumOfNewItems() {
+ int count = 0;
+ for (FeedItem item : items) {
+ if (item.getState() == FeedItem.State.NEW) {
+ if (!UserPreferences.isDisplayOnlyEpisodes()
+ || item.getMedia() != null) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns the number of FeedItems where the media started to play but
+ * wasn't finished yet.
+ */
+ public int getNumOfStartedItems() {
+ int count = 0;
+
+ for (FeedItem item : items) {
+ FeedItem.State state = item.getState();
+ if (state == FeedItem.State.IN_PROGRESS
+ || state == FeedItem.State.PLAYING) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns true if at least one item in the itemlist is unread.
+ *
+ * @param enableEpisodeFilter true if this method should only count items with episodes if
+ * the 'display only episodes' - preference is set to true by the
+ * user.
+ */
+ public boolean hasNewItems(boolean enableEpisodeFilter) {
+ for (FeedItem item : items) {
+ if (item.getState() == FeedItem.State.NEW) {
+ if (!(enableEpisodeFilter && UserPreferences
+ .isDisplayOnlyEpisodes()) || item.getMedia() != null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of FeedItems.
+ *
+ * @param enableEpisodeFilter true if this method should only count items with episodes if
+ * the 'display only episodes' - preference is set to true by the
+ * user.
+ */
+ public int getNumOfItems(boolean enableEpisodeFilter) {
+ if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
+ return EpisodeFilter.countItemsWithEpisodes(items);
+ } else {
+ return items.size();
+ }
+ }
+
+ /**
+ * Returns the item at the specified index.
+ *
+ * @param enableEpisodeFilter true if this method should ignore items without episdodes if
+ * the episodes filter has been enabled by the user.
+ */
+ public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) {
+ if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
+ return EpisodeFilter.accessEpisodeByIndex(items, position);
+ } else {
+ return items.get(position);
+ }
+ }
+
+ /**
+ * Returns the value that uniquely identifies this Feed. If the
+ * feedIdentifier attribute is not null, it will be returned. Else it will
+ * try to return the title. If the title is not given, it will use the link
+ * of the feed.
+ */
+ public String getIdentifyingValue() {
+ if (feedIdentifier != null && !feedIdentifier.isEmpty()) {
+ return feedIdentifier;
+ } else if (title != null && !title.isEmpty()) {
+ return title;
+ } else {
+ return link;
+ }
+ }
+
+ @Override
+ public String getHumanReadableIdentifier() {
+ if (title != null) {
+ return title;
+ } else {
+ return download_url;
+ }
+ }
+
+ public void updateFromOther(Feed other) {
+ super.updateFromOther(other);
+ if (other.title != null) {
+ title = other.title;
+ }
+ if (other.feedIdentifier != null) {
+ feedIdentifier = other.feedIdentifier;
+ }
+ if (other.link != null) {
+ link = other.link;
+ }
+ if (other.description != null) {
+ description = other.description;
+ }
+ if (other.language != null) {
+ language = other.language;
+ }
+ if (other.author != null) {
+ author = other.author;
+ }
+ if (other.paymentLink != null) {
+ paymentLink = other.paymentLink;
+ }
+ }
+
+ public boolean compareWithOther(Feed other) {
+ if (super.compareWithOther(other)) {
+ return true;
+ }
+ if (!title.equals(other.title)) {
+ return true;
+ }
+ if (other.feedIdentifier != null) {
+ if (feedIdentifier == null
+ || !feedIdentifier.equals(other.feedIdentifier)) {
+ return true;
+ }
+ }
+ if (other.link != null) {
+ if (link == null || !link.equals(other.link)) {
+ return true;
+ }
+ }
+ if (other.description != null) {
+ if (description == null || !description.equals(other.description)) {
+ return true;
+ }
+ }
+ if (other.language != null) {
+ if (language == null || !language.equals(other.language)) {
+ return true;
+ }
+ }
+ if (other.author != null) {
+ if (author == null || !author.equals(other.author)) {
+ return true;
+ }
+ }
+ if (other.paymentLink != null) {
+ if (paymentLink == null || !paymentLink.equals(other.paymentLink)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getTypeAsInt() {
+ return FEEDFILETYPE_FEED;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public void setLink(String link) {
+ this.link = link;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public FeedImage getImage() {
+ return image;
+ }
+
+ public void setImage(FeedImage image) {
+ this.image = image;
+ }
+
+ public List<FeedItem> getItems() {
+ return items;
+ }
+
+ public void setItems(List<FeedItem> list) {
+ this.items = list;
+ }
+
+ public Date getLastUpdate() {
+ return (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
+ }
+
+ public void setLastUpdate(Date lastUpdate) {
+ this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
+ }
+
+ public String getFeedIdentifier() {
+ return feedIdentifier;
+ }
+
+ public void setFeedIdentifier(String feedIdentifier) {
+ this.feedIdentifier = feedIdentifier;
+ }
+
+ public String getPaymentLink() {
+ return paymentLink;
+ }
+
+ public void setPaymentLink(String paymentLink) {
+ this.paymentLink = paymentLink;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
}
diff --git a/src/de/danoeh/antennapod/feed/FeedImage.java b/src/de/danoeh/antennapod/feed/FeedImage.java
index 09595f5eb..3cc99d1c2 100644
--- a/src/de/danoeh/antennapod/feed/FeedImage.java
+++ b/src/de/danoeh/antennapod/feed/FeedImage.java
@@ -9,7 +9,7 @@ import org.apache.commons.io.IOUtils;
import de.danoeh.antennapod.asynctask.ImageLoader;
-;
+
public class FeedImage extends FeedFile implements
ImageLoader.ImageWorkerTaskResource {
diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java
index 0df384b60..a80460ece 100644
--- a/src/de/danoeh/antennapod/feed/FeedItem.java
+++ b/src/de/danoeh/antennapod/feed/FeedItem.java
@@ -4,279 +4,305 @@ import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.Date;
import java.util.List;
+import java.util.concurrent.Callable;
+import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.asynctask.ImageLoader;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.util.ShownotesProvider;
/**
* Data Object for a XML message
- *
+ *
* @author daniel
- *
*/
public class FeedItem extends FeedComponent implements
- ImageLoader.ImageWorkerTaskResource {
-
- /** The id/guid that can be found in the rss/atom feed. Might not be set. */
- private String itemIdentifier;
- private String title;
- /**
- * The description of a feeditem. This field should only be set by the
- * parser.
- */
- private String description;
- /**
- * The content of the content-encoded tag of a feeditem. This field should
- * only be set by the parser.
- */
- private String contentEncoded;
-
- private SoftReference<String> cachedDescription;
- private SoftReference<String> cachedContentEncoded;
-
- private String link;
- private Date pubDate;
- private FeedMedia media;
- private Feed feed;
- private boolean read;
- private String paymentLink;
- private List<Chapter> chapters;
-
- public FeedItem() {
- this.read = true;
- }
-
- public void updateFromOther(FeedItem other) {
- super.updateFromOther(other);
- if (other.title != null) {
- title = other.title;
- }
- if (other.getDescription() != null) {
- description = other.getDescription();
- }
- if (other.getContentEncoded() != null) {
- contentEncoded = other.contentEncoded;
- }
- if (other.link != null) {
- link = other.link;
- }
- if (other.pubDate != null && other.pubDate != pubDate) {
- pubDate = other.pubDate;
- }
- if (other.media != null) {
- if (media == null) {
- media = other.media;
- } else if (media.compareWithOther(other)) {
- media.updateFromOther(other);
- }
- }
- if (other.paymentLink != null) {
- paymentLink = other.paymentLink;
- }
- if (other.chapters != null) {
- if (chapters == null) {
- chapters = other.chapters;
- }
- }
- }
-
- /**
- * Moves the 'description' and 'contentEncoded' field of feeditem to their
- * SoftReference fields.
- */
- protected void cacheDescriptions() {
- if (description != null) {
- cachedDescription = new SoftReference<String>(description);
- }
- if (contentEncoded != null) {
- cachedContentEncoded = new SoftReference<String>(contentEncoded);
- }
- description = null;
- contentEncoded = null;
- }
-
- /**
- * Returns the value that uniquely identifies this FeedItem. If the
- * itemIdentifier attribute is not null, it will be returned. Else it will
- * try to return the title. If the title is not given, it will use the link
- * of the entry.
- * */
- public String getIdentifyingValue() {
- if (itemIdentifier != null) {
- return itemIdentifier;
- } else if (title != null) {
- return title;
- } else {
- return link;
- }
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getDescription() {
- if (description == null && cachedDescription != null) {
- return cachedDescription.get();
- }
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- public String getLink() {
- return link;
- }
-
- public void setLink(String link) {
- this.link = link;
- }
-
- public Date getPubDate() {
- return pubDate;
- }
-
- public void setPubDate(Date pubDate) {
- this.pubDate = pubDate;
- }
-
- public FeedMedia getMedia() {
- return media;
- }
-
- public void setMedia(FeedMedia media) {
- this.media = media;
- }
-
- public Feed getFeed() {
- return feed;
- }
-
- public void setFeed(Feed feed) {
- this.feed = feed;
- }
-
- public boolean isRead() {
- return read || isInProgress();
- }
-
- public void setRead(boolean read) {
- this.read = read;
- }
-
- private boolean isInProgress() {
- return (media != null && media.isInProgress());
- }
-
- public String getContentEncoded() {
- if (contentEncoded == null && cachedContentEncoded != null) {
- return cachedContentEncoded.get();
-
- }
- return contentEncoded;
- }
-
- public void setContentEncoded(String contentEncoded) {
- this.contentEncoded = contentEncoded;
- }
-
- public String getPaymentLink() {
- return paymentLink;
- }
-
- public void setPaymentLink(String paymentLink) {
- this.paymentLink = paymentLink;
- }
-
- public List<Chapter> getChapters() {
- return chapters;
- }
-
- public void setChapters(List<Chapter> chapters) {
- this.chapters = chapters;
- }
-
- public String getItemIdentifier() {
- return itemIdentifier;
- }
-
- public void setItemIdentifier(String itemIdentifier) {
- this.itemIdentifier = itemIdentifier;
- }
-
- public boolean hasMedia() {
- return media != null;
- }
-
- private boolean isPlaying() {
- if (media != null) {
- return media.isPlaying();
- }
- return false;
- }
-
- public void setCachedDescription(String d) {
- cachedDescription = new SoftReference<String>(d);
- }
-
- public void setCachedContentEncoded(String c) {
- cachedContentEncoded = new SoftReference<String>(c);
- }
-
- public enum State {
- NEW, IN_PROGRESS, READ, PLAYING
- }
-
- public State getState() {
- if (hasMedia()) {
- if (isPlaying()) {
- return State.PLAYING;
- }
- if (isInProgress()) {
- return State.IN_PROGRESS;
- }
- }
- return (isRead() ? State.READ : State.NEW);
- }
-
- @Override
- public InputStream openImageInputStream() {
- InputStream out = null;
- if (hasMedia()) {
- out = media.openImageInputStream();
- }
- if (out == null && feed.getImage() != null) {
- out = feed.getImage().openImageInputStream();
- }
- return out;
- }
-
- @Override
- public InputStream reopenImageInputStream(InputStream input) {
- InputStream out = null;
- if (hasMedia()) {
- out = media.reopenImageInputStream(input);
- }
- if (out == null && feed.getImage() != null) {
- out = feed.getImage().reopenImageInputStream(input);
- }
- return out;
- }
-
- @Override
- public String getImageLoaderCacheKey() {
- String out = null;
- if (hasMedia()) {
- out = media.getImageLoaderCacheKey();
- }
- if (out == null && feed.getImage() != null) {
- out = feed.getImage().getImageLoaderCacheKey();
- }
- return out;
- }
+ ImageLoader.ImageWorkerTaskResource, ShownotesProvider {
+
+ /**
+ * The id/guid that can be found in the rss/atom feed. Might not be set.
+ */
+ private String itemIdentifier;
+ private String title;
+ /**
+ * The description of a feeditem.
+ */
+ private String description;
+ /**
+ * The content of the content-encoded tag of a feeditem.
+ */
+ private String contentEncoded;
+
+ private String link;
+ private Date pubDate;
+ private FeedMedia media;
+
+ private Feed feed;
+ private long feedId;
+
+ private boolean read;
+ private String paymentLink;
+ private List<Chapter> chapters;
+
+ public FeedItem() {
+ this.read = true;
+ }
+
+ /**
+ * This constructor should be used for creating test objects.
+ * */
+ public FeedItem(long id, String title, String itemIdentifier, String link, Date pubDate, boolean read, Feed feed) {
+ this.id = id;
+ this.title = title;
+ this.itemIdentifier = itemIdentifier;
+ this.link = link;
+ this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null;
+ this.read = read;
+ this.feed = feed;
+ }
+
+ public void updateFromOther(FeedItem other) {
+ super.updateFromOther(other);
+ if (other.title != null) {
+ title = other.title;
+ }
+ if (other.getDescription() != null) {
+ description = other.getDescription();
+ }
+ if (other.getContentEncoded() != null) {
+ contentEncoded = other.contentEncoded;
+ }
+ if (other.link != null) {
+ link = other.link;
+ }
+ if (other.pubDate != null && other.pubDate != pubDate) {
+ pubDate = other.pubDate;
+ }
+ if (other.media != null) {
+ if (media == null) {
+ media = other.media;
+ } else if (media.compareWithOther(other)) {
+ media.updateFromOther(other);
+ }
+ }
+ if (other.paymentLink != null) {
+ paymentLink = other.paymentLink;
+ }
+ if (other.chapters != null) {
+ if (chapters == null) {
+ chapters = other.chapters;
+ }
+ }
+ }
+
+ /**
+ * Returns the value that uniquely identifies this FeedItem. If the
+ * itemIdentifier attribute is not null, it will be returned. Else it will
+ * try to return the title. If the title is not given, it will use the link
+ * of the entry.
+ */
+ public String getIdentifyingValue() {
+ if (itemIdentifier != null) {
+ return itemIdentifier;
+ } else if (title != null) {
+ return title;
+ } else {
+ return link;
+ }
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public void setLink(String link) {
+ this.link = link;
+ }
+
+ public Date getPubDate() {
+ if (pubDate != null) {
+ return (Date) pubDate.clone();
+ } else {
+ return null;
+ }
+ }
+
+ public void setPubDate(Date pubDate) {
+ if (pubDate != null) {
+ this.pubDate = (Date) pubDate.clone();
+ } else {
+ this.pubDate = null;
+ }
+ }
+
+ public FeedMedia getMedia() {
+ return media;
+ }
+
+ /**
+ * Sets the media object of this FeedItem. If the given
+ * FeedMedia object is not null, it's 'item'-attribute value
+ * will also be set to this item.
+ * */
+ public void setMedia(FeedMedia media) {
+ this.media = media;
+ if (media != null && media.getItem() != this) {
+ media.setItem(this);
+ }
+ }
+
+ public Feed getFeed() {
+ return feed;
+ }
+
+ public void setFeed(Feed feed) {
+ this.feed = feed;
+ }
+
+ public boolean isRead() {
+ return read || isInProgress();
+ }
+
+ public void setRead(boolean read) {
+ this.read = read;
+ }
+
+ private boolean isInProgress() {
+ return (media != null && media.isInProgress());
+ }
+
+ public String getContentEncoded() {
+ return contentEncoded;
+ }
+
+ public void setContentEncoded(String contentEncoded) {
+ this.contentEncoded = contentEncoded;
+ }
+
+ public String getPaymentLink() {
+ return paymentLink;
+ }
+
+ public void setPaymentLink(String paymentLink) {
+ this.paymentLink = paymentLink;
+ }
+
+ public List<Chapter> getChapters() {
+ return chapters;
+ }
+
+ public void setChapters(List<Chapter> chapters) {
+ this.chapters = chapters;
+ }
+
+ public String getItemIdentifier() {
+ return itemIdentifier;
+ }
+
+ public void setItemIdentifier(String itemIdentifier) {
+ this.itemIdentifier = itemIdentifier;
+ }
+
+ public boolean hasMedia() {
+ return media != null;
+ }
+
+ private boolean isPlaying() {
+ if (media != null) {
+ return media.isPlaying();
+ }
+ return false;
+ }
+
+ @Override
+ public Callable<String> loadShownotes() {
+ return new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+
+ if (contentEncoded == null || description == null) {
+ DBReader.loadExtraInformationOfFeedItem(PodcastApp.getInstance(), FeedItem.this);
+
+ }
+ return (contentEncoded != null) ? contentEncoded : description;
+ }
+ };
+ }
+
+ public enum State {
+ NEW, IN_PROGRESS, READ, PLAYING
+ }
+
+ public State getState() {
+ if (hasMedia()) {
+ if (isPlaying()) {
+ return State.PLAYING;
+ }
+ if (isInProgress()) {
+ return State.IN_PROGRESS;
+ }
+ }
+ return (isRead() ? State.READ : State.NEW);
+ }
+
+ @Override
+ public InputStream openImageInputStream() {
+ InputStream out = null;
+ if (hasMedia()) {
+ out = media.openImageInputStream();
+ }
+ if (out == null && feed.getImage() != null) {
+ out = feed.getImage().openImageInputStream();
+ }
+ return out;
+ }
+
+ @Override
+ public InputStream reopenImageInputStream(InputStream input) {
+ InputStream out = null;
+ if (hasMedia()) {
+ out = media.reopenImageInputStream(input);
+ }
+ if (out == null && feed.getImage() != null) {
+ out = feed.getImage().reopenImageInputStream(input);
+ }
+ return out;
+ }
+
+ @Override
+ public String getImageLoaderCacheKey() {
+ String out = null;
+ if (hasMedia()) {
+ out = media.getImageLoaderCacheKey();
+ }
+ if (out == null && feed.getImage() != null) {
+ out = feed.getImage().getImageLoaderCacheKey();
+ }
+ return out;
+ }
+
+ public long getFeedId() {
+ return feedId;
+ }
+
+ public void setFeedId(long feedId) {
+ this.feedId = feedId;
+ }
+
}
diff --git a/src/de/danoeh/antennapod/feed/FeedManager.java b/src/de/danoeh/antennapod/feed/FeedManager.java
deleted file mode 100644
index a1a8c6c32..000000000
--- a/src/de/danoeh/antennapod/feed/FeedManager.java
+++ /dev/null
@@ -1,2014 +0,0 @@
-package de.danoeh.antennapod.feed;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.Comparator;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
-import de.danoeh.antennapod.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.preferences.UserPreferences;
-import de.danoeh.antennapod.service.PlaybackService;
-import de.danoeh.antennapod.storage.DownloadRequestException;
-import de.danoeh.antennapod.storage.DownloadRequester;
-import de.danoeh.antennapod.storage.PodDBAdapter;
-import de.danoeh.antennapod.util.DownloadError;
-import de.danoeh.antennapod.util.EpisodeFilter;
-import de.danoeh.antennapod.util.FeedtitleComparator;
-import de.danoeh.antennapod.util.NetworkUtils;
-import de.danoeh.antennapod.util.comparator.DownloadStatusComparator;
-import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
-import de.danoeh.antennapod.util.comparator.PlaybackCompletionDateComparator;
-import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
-
-/**
- * Singleton class that - provides access to all Feeds and FeedItems and to
- * several lists of FeedItems. - provides methods for modifying the
- * application's data - takes care of updating the information stored in the
- * database when something is modified
- *
- * An instance of this class can be retrieved via getInstance().
- * */
-public class FeedManager {
- private static final String TAG = "FeedManager";
-
- /** Number of completed Download status entries to store. */
- private static final int DOWNLOAD_LOG_SIZE = 50;
-
- private static FeedManager singleton;
-
- private List<Feed> feeds;
-
- /** Contains all items where 'read' is false */
- private List<FeedItem> unreadItems;
-
- /** Contains completed Download status entries */
- private List<DownloadStatus> downloadLog;
-
- /** Contains the queue of items to be played. */
- private List<FeedItem> queue;
-
- /** Contains the last played items */
- private List<FeedItem> playbackHistory;
-
- /** Maximum number of items in the playback history. */
- private static final int PLAYBACK_HISTORY_SIZE = 15;
-
- private DownloadRequester requester = DownloadRequester.getInstance();
- private EventDistributor eventDist = EventDistributor.getInstance();
-
- /**
- * Should be used to change the content of the arrays from another thread to
- * ensure that arrays are only modified on the main thread.
- */
- private Handler contentChanger;
-
- /** Ensures that there are no parallel db operations. */
- private Executor dbExec;
-
- /** Prevents user from starting several feed updates at the same time. */
- private static boolean isStartingFeedRefresh = false;
-
- private FeedManager() {
- feeds = Collections.synchronizedList(new ArrayList<Feed>());
- unreadItems = Collections.synchronizedList(new ArrayList<FeedItem>());
- downloadLog = new ArrayList<DownloadStatus>();
- queue = Collections.synchronizedList(new ArrayList<FeedItem>());
- playbackHistory = Collections
- .synchronizedList(new ArrayList<FeedItem>());
- contentChanger = new Handler();
- dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- });
- }
-
- /** Creates a new instance of this class if necessary and returns it. */
- public static FeedManager getInstance() {
- if (singleton == null) {
- singleton = new FeedManager();
- }
- return singleton;
- }
-
- /**
- * Play FeedMedia and start the playback service + launch Mediaplayer
- * Activity. The FeedItem will be added at the top of the queue if it isn't
- * in there yet.
- *
- * @param context
- * for starting the playbackservice
- * @param media
- * that shall be played
- * @param showPlayer
- * if Mediaplayer activity shall be started
- * @param startWhenPrepared
- * if Mediaplayer shall be started after it has been prepared
- * @param shouldStream
- * if Mediaplayer should stream the file
- */
- public void playMedia(Context context, FeedMedia media, boolean showPlayer,
- boolean startWhenPrepared, boolean shouldStream) {
- try {
- if (!shouldStream) {
- if (media.fileExists() == false) {
- throw new MediaFileNotFoundException(
- "No episode was found at " + media.getFile_url(),
- media);
- }
- }
- // Start playback Service
- Intent launchIntent = new Intent(context, PlaybackService.class);
- launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
- startWhenPrepared);
- launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
- shouldStream);
- launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
- true);
- context.startService(launchIntent);
- if (showPlayer) {
- // Launch Mediaplayer
- context.startActivity(PlaybackService.getPlayerActivityIntent(
- context, media));
- }
- if (!queue.contains(media.getItem())) {
- addQueueItemAt(context, media.getItem(), 0, false);
- }
- } catch (MediaFileNotFoundException e) {
- e.printStackTrace();
- if (media.isPlaying()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- notifyMissingFeedMediaFile(context, media);
- }
- }
-
- /** Remove media item that has been downloaded. */
- public boolean deleteFeedMedia(Context context, FeedMedia media) {
- boolean result = false;
- if (media.isDownloaded()) {
- File mediaFile = new File(media.file_url);
- if (mediaFile.exists()) {
- result = mediaFile.delete();
- }
- media.setDownloaded(false);
- media.setFile_url(null);
- setFeedMedia(context, media);
-
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(context);
- if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
- if (media.getId() == PlaybackPreferences
- .getCurrentlyPlayingFeedMediaId()) {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(
- PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM,
- true);
- editor.commit();
- }
- if (PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == media
- .getId()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Deleting File. Result: " + result);
- return result;
- }
-
- /** Remove a feed with all its items and media files and its image. */
- public void deleteFeed(final Context context, final Feed feed) {
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(context.getApplicationContext());
- if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
- && PlaybackPreferences.getLastPlayedFeedId() == feed.getId()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- SharedPreferences.Editor editor = prefs.edit();
- editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID,
- -1);
- editor.commit();
- }
-
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- feeds.remove(feed);
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- DownloadRequester requester = DownloadRequester
- .getInstance();
- adapter.open();
- // delete image file
- if (feed.getImage() != null) {
- if (feed.getImage().isDownloaded()
- && feed.getImage().getFile_url() != null) {
- File imageFile = new File(feed.getImage()
- .getFile_url());
- imageFile.delete();
- } else if (requester.isDownloadingFile(feed
- .getImage())) {
- requester.cancelDownload(context,
- feed.getImage());
- }
- }
- // delete stored media files and mark them as read
- for (FeedItem item : feed.getItems()) {
- if (item.getState() == FeedItem.State.NEW) {
- unreadItems.remove(item);
- }
- if (queue.contains(item)) {
- removeQueueItem(item, adapter);
- }
- removeItemFromPlaybackHistory(context, item);
- if (item.getMedia() != null
- && item.getMedia().isDownloaded()) {
- File mediaFile = new File(item.getMedia()
- .getFile_url());
- mediaFile.delete();
- } else if (item.getMedia() != null
- && requester.isDownloadingFile(item
- .getMedia())) {
- requester.cancelDownload(context,
- item.getMedia());
- }
- }
-
- adapter.removeFeed(feed);
- adapter.close();
- eventDist.sendFeedUpdateBroadcast();
- }
-
- });
- }
- });
-
- }
-
- /**
- * Makes sure that playback history is sorted and is not larger than
- * PLAYBACK_HISTORY_SIZE.
- *
- * @return an array of all feeditems that were remove from the playback
- * history or null if no items were removed.
- */
- private FeedItem[] cleanupPlaybackHistory() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cleaning up playback history.");
-
- Collections.sort(playbackHistory,
- new PlaybackCompletionDateComparator());
- final int initialSize = playbackHistory.size();
- if (initialSize > PLAYBACK_HISTORY_SIZE) {
- FeedItem[] removed = new FeedItem[initialSize
- - PLAYBACK_HISTORY_SIZE];
-
- for (int i = 0; i < removed.length; i++) {
- removed[i] = playbackHistory.remove(playbackHistory.size() - 1);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Removed " + removed.length
- + " items from playback history.");
- return removed;
- }
- return null;
- }
-
- /**
- * Executes cleanupPlaybackHistory and deletes the playbackCompletionDate of
- * all item that were removed from the history.
- */
- private void cleanupPlaybackHistoryWithDBCleanup(final Context context) {
- final FeedItem[] removedItems = cleanupPlaybackHistory();
- if (removedItems != null) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- for (FeedItem item : removedItems) {
- if (item.getMedia() != null) {
- item.getMedia().setPlaybackCompletionDate(null);
- adapter.setMedia(item.getMedia());
- }
- }
- adapter.close();
- }
- });
- }
- }
-
- /** Removes all items from the playback history. */
- public void clearPlaybackHistory(final Context context) {
- if (!playbackHistory.isEmpty()) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Clearing playback history.");
- final FeedItem[] items = playbackHistory
- .toArray(new FeedItem[playbackHistory.size()]);
- playbackHistory.clear();
- eventDist.sendPlaybackHistoryUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- for (FeedItem item : items) {
- if (item.getMedia() != null
- && item.getMedia().getPlaybackCompletionDate() != null) {
- item.getMedia().setPlaybackCompletionDate(null);
- adapter.setMedia(item.getMedia());
- }
- }
- adapter.close();
- }
- });
- }
- }
-
- /** Adds a FeedItem to the playback history. */
- public void addItemToPlaybackHistory(Context context, FeedItem item) {
- if (item.getMedia() != null
- && item.getMedia().getPlaybackCompletionDate() != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Adding new item to playback history");
- if (!playbackHistory.contains(item)) {
- playbackHistory.add(item);
- }
- cleanupPlaybackHistoryWithDBCleanup(context);
- eventDist.sendPlaybackHistoryUpdateBroadcast();
- }
- }
-
- private void removeItemFromPlaybackHistory(Context context, FeedItem item) {
- playbackHistory.remove(item);
- eventDist.sendPlaybackHistoryUpdateBroadcast();
- }
-
- /**
- * Sets the 'read'-attribute of a FeedItem. Should be used by all Classes
- * instead of the setters of FeedItem.
- */
- public void markItemRead(final Context context, final FeedItem item,
- final boolean read, boolean resetMediaPosition) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting item with title " + item.getTitle()
- + " as read/unread");
-
- item.setRead(read);
- if (item.hasMedia() && resetMediaPosition) {
- item.getMedia().setPosition(0);
- }
- setFeedItem(context, item);
- if (item.hasMedia() && resetMediaPosition)
- setFeedMedia(context, item.getMedia());
-
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- if (read == true) {
- unreadItems.remove(item);
- } else {
- unreadItems.add(item);
- Collections.sort(unreadItems,
- new FeedItemPubdateComparator());
- }
- eventDist.sendUnreadItemsUpdateBroadcast();
- }
- });
-
- }
-
- /**
- * Sets the 'read' attribute of all FeedItems of a specific feed to true
- */
- public void markFeedRead(Context context, Feed feed) {
- for (FeedItem item : feed.getItems()) {
- if (unreadItems.contains(item)) {
- markItemRead(context, item, true, false);
- }
- }
- }
-
- /** Marks all items in the unread items list as read */
- public void markAllItemsRead(final Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "marking all items as read");
- for (FeedItem item : unreadItems) {
- item.setRead(true);
- }
- final ArrayList<FeedItem> unreadItemsCopy = new ArrayList<FeedItem>(
- unreadItems);
- unreadItems.clear();
- eventDist.sendUnreadItemsUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- for (FeedItem item : unreadItemsCopy) {
- setFeedItem(item, adapter);
- if (item.hasMedia())
- setFeedMedia(context, item.getMedia());
- }
- adapter.close();
- }
- });
-
- }
-
- /** Updates all feeds in the feed list. */
- public void refreshAllFeeds(final Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Refreshing all feeds.");
- refreshFeeds(context, feeds);
- }
-
- /** Updates all feeds in the feed list. */
- public void refreshExpiredFeeds(final Context context) {
- long millis = UserPreferences.getUpdateInterval();
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "Refreshing expired feeds, " + millis + " ms");
-
- if (millis > 0) {
- List<Feed> feedList = new ArrayList<Feed>();
- long now = Calendar.getInstance().getTime().getTime();
-
- // Allow a 10 minute window
- millis -= 10 * 60 * 1000;
- for (Feed feed : feeds) {
- Date date = feed.getLastUpdate();
- if (date != null) {
- if (date.getTime() + millis <= now) {
- if (AppConfig.DEBUG) {
- Log.d(TAG, "Adding expired feed " + feed.getTitle());
- }
- feedList.add(feed);
- } else {
- if (AppConfig.DEBUG) {
- Log.d(TAG, "Skipping feed " + feed.getTitle());
- }
- }
- }
- }
- if (feedList.size() > 0) {
- refreshFeeds(context, feedList);
- }
- }
- }
-
- @SuppressLint("NewApi")
- private void refreshFeeds(final Context context, final List<Feed> feedList) {
- if (!isStartingFeedRefresh) {
- isStartingFeedRefresh = true;
- AsyncTask<Void, Void, Void> updateWorker = new AsyncTask<Void, Void, Void>() {
-
- @Override
- protected void onPostExecute(Void result) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "All feeds have been sent to the downloadmanager");
- isStartingFeedRefresh = false;
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- for (Feed feed : feedList) {
- try {
- refreshFeed(context, feed);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- addDownloadStatus(
- context,
- new DownloadStatus(feed, feed
- .getHumanReadableIdentifier(),
- DownloadError.ERROR_REQUEST_ERROR,
- false, e.getMessage()));
- }
- }
- return null;
- }
-
- };
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- updateWorker.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- updateWorker.execute();
- }
- }
-
- }
-
- /**
- * Notifies the feed manager that the an image file is invalid. It will try
- * to redownload it
- */
- public void notifyInvalidImageFile(Context context, FeedImage image) {
- Log.i(TAG,
- "The feedmanager was notified about an invalid image download. It will now try to redownload the image file");
- try {
- requester.downloadImage(context, image);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- Log.w(TAG, "Failed to download invalid feed image");
- }
- }
-
- /**
- * Notifies the feed manager that a downloaded episode doesn't exist
- * anymore. It will update the values of the FeedMedia object accordingly.
- */
- public void notifyMissingFeedMediaFile(Context context, FeedMedia media) {
- Log.i(TAG,
- "The feedmanager was notified about a missing episode. It will update its database now.");
- media.setDownloaded(false);
- media.setFile_url(null);
- setFeedMedia(context, media);
- eventDist.sendFeedUpdateBroadcast();
- }
-
- /** Updates a specific feed. */
- public void refreshFeed(Context context, Feed feed)
- throws DownloadRequestException {
- requester.downloadFeed(context, new Feed(feed.getDownload_url(),
- new Date(), feed.getTitle()));
- }
-
- /** Adds a download status object to the download log. */
- public void addDownloadStatus(final Context context,
- final DownloadStatus status) {
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- downloadLog.add(status);
- Collections.sort(downloadLog, new DownloadStatusComparator());
- final DownloadStatus removedStatus;
- if (downloadLog.size() > DOWNLOAD_LOG_SIZE) {
- removedStatus = downloadLog.remove(downloadLog.size() - 1);
- } else {
- removedStatus = null;
- }
- eventDist.sendDownloadLogUpdateBroadcast();
- 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();
- }
- });
- }
- });
-
- }
-
- /** Downloads all items in the queue that have not been downloaded yet. */
- public void downloadAllItemsInQueue(final Context context) {
- if (!queue.isEmpty()) {
- try {
- downloadFeedItem(context,
- queue.toArray(new FeedItem[queue.size()]));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
- }
- }
-
- public void downloadFeedItem(final Context context, FeedItem... items)
- throws DownloadRequestException {
- downloadFeedItem(true, context, items);
- }
-
- /** Downloads FeedItems if they have not been downloaded yet. */
- private void downloadFeedItem(boolean performAutoCleanup,
- final Context context, final FeedItem... items)
- throws DownloadRequestException {
- if (performAutoCleanup) {
- new Thread() {
-
- @Override
- public void run() {
- performAutoCleanup(context,
- getPerformAutoCleanupArgs(items.length));
- }
-
- }.start();
- }
- for (FeedItem item : items) {
- if (item.getMedia() != null
- && !requester.isDownloadingFile(item.getMedia())
- && !item.getMedia().isDownloaded()) {
- if (items.length > 1) {
- try {
- requester.downloadMedia(context, item.getMedia());
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- addDownloadStatus(context,
- new DownloadStatus(item.getMedia(), item
- .getMedia()
- .getHumanReadableIdentifier(),
- DownloadError.ERROR_REQUEST_ERROR,
- false, e.getMessage()));
- }
- } else {
- requester.downloadMedia(context, item.getMedia());
- }
- }
- }
- }
-
- /**
- * This method will try to download undownloaded items in the queue or the
- * unread items list. If not enough space is available, an episode cleanup
- * will be performed first.
- *
- * This method will not try to download the currently playing item.
- */
- public void autodownloadUndownloadedItems(Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Performing auto-dl of undownloaded episodes");
- if (NetworkUtils.autodownloadNetworkAvailable(context)
- && UserPreferences.isEnableAutodownload()) {
- int undownloadedEpisodes = getNumberOfUndownloadedEpisodes();
- int downloadedEpisodes = getNumberOfDownloadedEpisodes();
- int deletedEpisodes = performAutoCleanup(context,
- getPerformAutoCleanupArgs(undownloadedEpisodes));
- int episodeSpaceLeft = undownloadedEpisodes;
- boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
- .getEpisodeCacheSizeUnlimited();
-
- if (!cacheIsUnlimited
- && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
- + undownloadedEpisodes) {
- episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
- - (downloadedEpisodes - deletedEpisodes);
- }
-
- List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
- if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
- for (int i = 0; i < queue.size(); i++) { // ignore playing item
- FeedItem item = queue.get(i);
- if (item.hasMedia() && !item.getMedia().isDownloaded()
- && !item.getMedia().isPlaying()) {
- itemsToDownload.add(item);
- episodeSpaceLeft--;
- undownloadedEpisodes--;
- if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
- break;
- }
- }
- }
- }
- if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
- for (FeedItem item : unreadItems) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()) {
- itemsToDownload.add(item);
- episodeSpaceLeft--;
- undownloadedEpisodes--;
- if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
- break;
- }
- }
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Enqueueing " + itemsToDownload.size()
- + " items for download");
-
- try {
- downloadFeedItem(false, context,
- itemsToDownload.toArray(new FeedItem[itemsToDownload
- .size()]));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
-
- }
- }
-
- /**
- * This method will determine the number of episodes that have to be deleted
- * depending on a given number of episodes.
- *
- * @return The argument that has to be passed to performAutoCleanup() so
- * that the number of episodes fits into the episode cache.
- * */
- private int getPerformAutoCleanupArgs(final int episodeNumber) {
- if (episodeNumber >= 0
- && UserPreferences.getEpisodeCacheSize() != UserPreferences
- .getEpisodeCacheSizeUnlimited()) {
- int downloadedEpisodes = getNumberOfDownloadedEpisodes();
- if (downloadedEpisodes + episodeNumber >= UserPreferences
- .getEpisodeCacheSize()) {
-
- return downloadedEpisodes + episodeNumber
- - UserPreferences.getEpisodeCacheSize();
- }
- }
- return 0;
- }
-
- /**
- * Performs an auto-cleanup so that the number of downloaded episodes is
- * below or equal to the episode cache size. The method will be executed in
- * the caller's thread.
- */
- public void performAutoCleanup(Context context) {
- performAutoCleanup(context, getPerformAutoCleanupArgs(0));
- }
-
- /**
- * This method will try to delete a given number of episodes. An episode
- * will only be deleted if it is not in the queue.
- *
- * @return The number of episodes that were actually deleted
- * */
- private int performAutoCleanup(Context context, final int episodeNumber) {
- List<FeedItem> candidates = new ArrayList<FeedItem>();
- List<FeedItem> delete;
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- if (item.hasMedia() && item.getMedia().isDownloaded()
- && !isInQueue(item) && item.isRead()) {
- candidates.add(item);
- }
- }
- }
-
- Collections.sort(candidates, new Comparator<FeedItem>() {
- @Override
- public int compare(FeedItem lhs, FeedItem rhs) {
- Date l = lhs.getMedia().getPlaybackCompletionDate();
- Date r = rhs.getMedia().getPlaybackCompletionDate();
-
- if (l == null) {
- l = new Date(0);
- }
- if (r == null) {
- r = new Date(0);
- }
- return l.compareTo(r);
- }
- });
-
- if (candidates.size() > episodeNumber) {
- delete = candidates.subList(0, episodeNumber);
- } else {
- delete = candidates;
- }
-
- for (FeedItem item : delete) {
- deleteFeedMedia(context, item.getMedia());
- }
-
- int counter = delete.size();
-
- if (AppConfig.DEBUG)
- Log.d(TAG, String.format(
- "Auto-delete deleted %d episodes (%d requested)", counter,
- episodeNumber));
-
- return counter;
- }
-
- /**
- * Counts items in the queue and the unread items list which haven't been
- * downloaded yet.
- *
- * This method will not count the playing item
- */
- private int getNumberOfUndownloadedEpisodes() {
- int counter = 0;
- for (FeedItem item : queue) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()
- && !item.getMedia().isPlaying()) {
- counter++;
- }
- }
- for (FeedItem item : unreadItems) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()) {
- counter++;
- }
- }
- return counter;
-
- }
-
- /** Counts all downloaded items. */
- private int getNumberOfDownloadedEpisodes() {
- int counter = 0;
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- if (item.hasMedia() && item.getMedia().isDownloaded()) {
- counter++;
- }
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Number of downloaded episodes: " + counter);
- return counter;
- }
-
- /**
- * Enqueues all items that are currently in the unreadItems list and marks
- * them as 'read'.
- */
- public void enqueueAllNewItems(final Context context) {
- if (!unreadItems.isEmpty()) {
- addQueueItem(context,
- unreadItems.toArray(new FeedItem[unreadItems.size()]));
- markAllItemsRead(context);
- }
- }
-
- /**
- * Adds a feeditem to the queue at the specified index if it is not in the
- * queue yet. The item is marked as 'read'.
- */
- public void addQueueItemAt(final Context context, final FeedItem item,
- final int index, final boolean performAutoDownload) {
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- if (!queue.contains(item)) {
- queue.add(index, item);
- if (!item.isRead()) {
- markItemRead(context, item, true, false);
- }
- }
- eventDist.sendQueueUpdateBroadcast();
-
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
- if (performAutoDownload) {
- new Thread() {
- @Override
- public void run() {
- autodownloadUndownloadedItems(context);
- }
- }.start();
- }
- }
- });
-
- }
-
- /**
- * Adds FeedItems to the queue if they are not in the queue yet. The items
- * are marked as 'read'.
- */
- public void addQueueItem(final Context context, final FeedItem... items) {
- if (items.length > 0) {
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- for (FeedItem item : items) {
- if (!queue.contains(item)) {
- queue.add(item);
- if (!item.isRead()) {
- markItemRead(context, item, true, false);
- }
- }
- }
- eventDist.sendQueueUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
- new Thread() {
- @Override
- public void run() {
- autodownloadUndownloadedItems(context);
- }
- }.start();
- }
- });
- }
-
- }
-
- /**
- * Return the item that comes after this item in the queue or null if this
- * item is not in the queue or if this item has no successor.
- */
- public FeedItem getQueueSuccessorOfItem(FeedItem item) {
- if (isInQueue(item)) {
- int itemIndex = queue.indexOf(item);
- if (itemIndex != -1 && itemIndex < (queue.size() - 1)) {
- return queue.get(itemIndex + 1);
- }
- }
- return null;
- }
-
- /** Removes all items in queue */
- public void clearQueue(final Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Clearing queue");
- Iterator<FeedItem> iter = queue.iterator();
- while (iter.hasNext()) {
- FeedItem item = iter.next();
- if (item.getState() != FeedItem.State.PLAYING) {
- iter.remove();
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "FeedItem is playing and is therefore not removed from the queue");
- }
- }
- eventDist.sendQueueUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
-
- }
-
- /** Removes a FeedItem from the queue. Uses external PodDBAdapter. */
- private void removeQueueItem(FeedItem item, PodDBAdapter adapter) {
- boolean removed = queue.remove(item);
- if (removed) {
- adapter.setQueue(queue);
- }
- }
-
- /** Removes a FeedItem from the queue. */
- public void removeQueueItem(final Context context, FeedItem item,
- final boolean performAutoDownload) {
- boolean removed = queue.remove(item);
- if (removed) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
-
- }
- if (performAutoDownload) {
- new Thread() {
- @Override
- public void run() {
- autodownloadUndownloadedItems(context);
- }
- }.start();
- }
- eventDist.sendQueueUpdateBroadcast();
- }
-
- /**
- * Moves the queue item at the specified index to another position. If the
- * indices are out of range, no operation will be performed.
- *
- * @param from
- * index of the item that is going to be moved
- * @param to
- * destination index of item
- * @param broadcastUpdate
- * true if the method should send a queue update broadcast after
- * the operation has been performed. This should be set to false
- * if the order of the queue is changed through drag & drop
- * reordering to avoid visual glitches.
- */
- public void moveQueueItem(final Context context, int from, int to,
- boolean broadcastUpdate) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Moving queue item from index " + from + " to index "
- + to);
- if (from >= 0 && from < queue.size() && to >= 0 && to < queue.size()) {
- FeedItem item = queue.remove(from);
- queue.add(to, item);
- dbExec.execute(new Runnable() {
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
- if (broadcastUpdate) {
- eventDist.sendQueueUpdateBroadcast();
- }
- }
- }
-
- /** Returns true if the specified item is in the queue. */
- public boolean isInQueue(FeedItem item) {
- return queue.contains(item);
- }
-
- /**
- * Returns the FeedItem at the beginning of the queue or null if the queue
- * is empty.
- */
- public FeedItem getFirstQueueItem() {
- if (queue.isEmpty()) {
- return null;
- } else {
- return queue.get(0);
- }
- }
-
- private void addNewFeed(final Context context, final Feed feed) {
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- feeds.add(feed);
- Collections.sort(feeds, new FeedtitleComparator());
- eventDist.sendFeedUpdateBroadcast();
- }
- });
- setCompleteFeed(context, feed);
- }
-
- /**
- * Updates an existing feed or adds it as a new one if it doesn't exist.
- *
- * @return The saved Feed with a database ID
- */
- public Feed updateFeed(final Context context, final Feed newFeed) {
- // Look up feed in the feedslist
- final Feed savedFeed = searchFeedByIdentifyingValue(newFeed
- .getIdentifyingValue());
- if (savedFeed == null) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Found no existing Feed with title "
- + newFeed.getTitle() + ". Adding as new one.");
- // Add a new Feed
- addNewFeed(context, newFeed);
- return newFeed;
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Feed with title " + newFeed.getTitle()
- + " already exists. Syncing new with existing one.");
- if (savedFeed.compareWithOther(newFeed)) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Feed has updated attribute values. Updating old feed's attributes");
- savedFeed.updateFromOther(newFeed);
- }
- // Look for new or updated Items
- for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
- final FeedItem item = newFeed.getItems().get(idx);
- FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed,
- item.getIdentifyingValue());
- if (oldItem == null) {
- // item is new
- final int i = idx;
- item.setFeed(savedFeed);
- contentChanger.post(new Runnable() {
- @Override
- public void run() {
- savedFeed.getItems().add(i, item);
-
- }
- });
- markItemRead(context, item, false, false);
- } else {
- oldItem.updateFromOther(item);
- }
- }
- // update attributes
- savedFeed.setLastUpdate(newFeed.getLastUpdate());
- savedFeed.setType(newFeed.getType());
- setCompleteFeed(context, savedFeed);
- new Thread() {
- @Override
- public void run() {
- autodownloadUndownloadedItems(context);
- }
- }.start();
- return savedFeed;
- }
-
- }
-
- /** Get a Feed by its identifying value. */
- private Feed searchFeedByIdentifyingValue(String identifier) {
- for (Feed feed : feeds) {
- if (feed.getIdentifyingValue().equals(identifier)) {
- return feed;
- }
- }
- return null;
- }
-
- /**
- * Returns true if a feed with the given download link is already in the
- * feedlist.
- */
- public boolean feedExists(String downloadUrl) {
- for (Feed feed : feeds) {
- if (feed.getDownload_url().equals(downloadUrl)) {
- return true;
- }
- }
- return false;
- }
-
- /** Get a FeedItem by its identifying value. */
- private FeedItem searchFeedItemByIdentifyingValue(Feed feed,
- String identifier) {
- for (FeedItem item : feed.getItems()) {
- if (item.getIdentifyingValue().equals(identifier)) {
- return item;
- }
- }
- return null;
- }
-
- /** Updates Information of an existing Feed. Uses external adapter. */
- private void setFeed(Feed feed, PodDBAdapter adapter) {
- if (adapter != null) {
- adapter.setFeed(feed);
- feed.cacheDescriptionsOfItems();
- } else {
- Log.w(TAG, "Adapter in setFeed was null");
- }
- }
-
- /** Updates Information of an existing Feeditem. Uses external adapter. */
- private void setFeedItem(FeedItem item, PodDBAdapter adapter) {
- if (adapter != null) {
- adapter.setSingleFeedItem(item);
- } else {
- Log.w(TAG, "Adapter in setFeedItem was null");
- }
- }
-
- /** Updates Information of an existing Feedimage. Uses external adapter. */
- private void setFeedImage(FeedImage image, PodDBAdapter adapter) {
- if (adapter != null) {
- adapter.setImage(image);
- } else {
- Log.w(TAG, "Adapter in setFeedImage was null");
- }
- }
-
- /**
- * Updates Information of an existing Feedmedia object. Uses external
- * adapter.
- */
- private void setFeedImage(FeedMedia media, PodDBAdapter adapter) {
- if (adapter != null) {
- adapter.setMedia(media);
- } else {
- Log.w(TAG, "Adapter in setFeedMedia was null");
- }
- }
-
- /**
- * Updates Information of an existing Feed. Creates and opens its own
- * adapter.
- */
- public void setFeed(final Context context, final Feed feed) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setFeed(feed);
- feed.cacheDescriptionsOfItems();
- adapter.close();
- }
- });
-
- }
-
- /**
- * Updates Information of an existing Feed and its FeedItems. Creates and
- * opens its own adapter.
- */
- public void setCompleteFeed(final Context context, final Feed feed) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setCompleteFeed(feed);
- feed.cacheDescriptionsOfItems();
- adapter.close();
- }
- });
-
- }
-
- /**
- * Updates information of an existing FeedItem. Creates and opens its own
- * adapter.
- */
- public void setFeedItem(final Context context, final FeedItem item) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setSingleFeedItem(item);
- adapter.close();
- }
- });
-
- }
-
- /**
- * Updates information of an existing FeedImage. Creates and opens its own
- * adapter.
- */
- public void setFeedImage(final Context context, final FeedImage image) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setImage(image);
- adapter.close();
- }
- });
-
- }
-
- /**
- * Updates information of an existing FeedMedia object. Creates and opens
- * its own adapter.
- */
- public void setFeedMedia(final Context context, final FeedMedia media) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setMedia(media);
- adapter.close();
- }
- });
-
- }
-
- /** Get a Feed by its id */
- public Feed getFeed(long id) {
- for (Feed f : feeds) {
- if (f.id == id) {
- return f;
- }
- }
- Log.e(TAG, "Couldn't find Feed with id " + id);
- return null;
- }
-
- /** Get a Feed Image by its id */
- public FeedImage getFeedImage(long id) {
- for (Feed f : feeds) {
- FeedImage image = f.getImage();
- if (image != null && image.getId() == id) {
- return image;
- }
- }
- return null;
- }
-
- /** Get a Feed Item by its id and its feed */
- public FeedItem getFeedItem(long id, Feed feed) {
- if (feed != null) {
- for (FeedItem item : feed.getItems()) {
- if (item.getId() == id) {
- return item;
- }
- }
- }
- Log.e(TAG, "Couldn't find FeedItem with id " + id);
- return null;
- }
-
- /** Get a FeedItem by its id and the id of its feed. */
- public FeedItem getFeedItem(long itemId, long feedId) {
- Feed feed = getFeed(feedId);
- if (feed != null && feed.getItems() != null) {
- for (FeedItem item : feed.getItems()) {
- if (item.getId() == itemId) {
- return item;
- }
- }
- }
- return null;
- }
-
- /** Get a FeedMedia object by the id of the Media object and the feed object */
- public FeedMedia getFeedMedia(long id, Feed feed) {
- if (feed != null) {
- for (FeedItem item : feed.getItems()) {
- if (item.getMedia() != null && item.getMedia().getId() == id) {
- return item.getMedia();
- }
- }
- }
- Log.e(TAG, "Couldn't find FeedMedia with id " + id);
- if (feed == null)
- Log.e(TAG, "Feed was null");
- return null;
- }
-
- /** Get a FeedMedia object by the id of the Media object. */
- public FeedMedia getFeedMedia(long id) {
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- if (item.getMedia() != null && item.getMedia().getId() == id) {
- return item.getMedia();
- }
- }
- }
- Log.w(TAG, "Couldn't find FeedMedia with id " + id);
- return null;
- }
-
- /** Get a download status object from the download log by its FeedFile. */
- public DownloadStatus getDownloadStatus(FeedFile feedFile) {
- for (DownloadStatus status : downloadLog) {
- if (status.getFeedFile() == feedFile) {
- return status;
- }
- }
- return null;
- }
-
- /** Reads the database */
- public void loadDBData(Context context) {
- feeds.clear();
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- extractFeedlistFromCursor(context, adapter);
- extractDownloadLogFromCursor(context, adapter);
- extractQueueFromCursor(context, adapter);
- adapter.close();
- Collections.sort(feeds, new FeedtitleComparator());
- Collections.sort(unreadItems, new FeedItemPubdateComparator());
- cleanupPlaybackHistory();
- }
-
- private void extractFeedlistFromCursor(Context context, PodDBAdapter adapter) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Extracting Feedlist");
- Cursor feedlistCursor = adapter.getAllFeedsCursor();
- if (feedlistCursor.moveToFirst()) {
- do {
- Date lastUpdate = new Date(
- feedlistCursor
- .getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX));
- Feed feed = new Feed(lastUpdate);
-
- feed.id = feedlistCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
- feed.setTitle(feedlistCursor
- .getString(PodDBAdapter.KEY_TITLE_INDEX));
- feed.setLink(feedlistCursor
- .getString(PodDBAdapter.KEY_LINK_INDEX));
- feed.setDescription(feedlistCursor
- .getString(PodDBAdapter.KEY_DESCRIPTION_INDEX));
- feed.setPaymentLink(feedlistCursor
- .getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX));
- feed.setAuthor(feedlistCursor
- .getString(PodDBAdapter.KEY_AUTHOR_INDEX));
- feed.setLanguage(feedlistCursor
- .getString(PodDBAdapter.KEY_LANGUAGE_INDEX));
- feed.setType(feedlistCursor
- .getString(PodDBAdapter.KEY_TYPE_INDEX));
- feed.setFeedIdentifier(feedlistCursor
- .getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX));
- long imageIndex = feedlistCursor
- .getLong(PodDBAdapter.KEY_IMAGE_INDEX);
- if (imageIndex != 0) {
- feed.setImage(adapter.getFeedImage(imageIndex));
- feed.getImage().setFeed(feed);
- }
- feed.file_url = feedlistCursor
- .getString(PodDBAdapter.KEY_FILE_URL_INDEX);
- feed.download_url = feedlistCursor
- .getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX);
- feed.setDownloaded(feedlistCursor
- .getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0);
- // Get FeedItem-Object
- Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed);
- feed.setItems(extractFeedItemsFromCursor(context, feed,
- itemlistCursor, adapter));
- itemlistCursor.close();
-
- feeds.add(feed);
- } while (feedlistCursor.moveToNext());
- }
- feedlistCursor.close();
-
- }
-
- private ArrayList<FeedItem> extractFeedItemsFromCursor(Context context,
- Feed feed, Cursor itemlistCursor, PodDBAdapter adapter) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
- ArrayList<FeedItem> items = new ArrayList<FeedItem>();
- ArrayList<String> mediaIds = new ArrayList<String>();
-
- if (itemlistCursor.moveToFirst()) {
- do {
- FeedItem item = new FeedItem();
-
- item.id = itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID);
- item.setFeed(feed);
- item.setTitle(itemlistCursor
- .getString(PodDBAdapter.IDX_FI_SMALL_TITLE));
- item.setLink(itemlistCursor
- .getString(PodDBAdapter.IDX_FI_SMALL_LINK));
- item.setPubDate(new Date(itemlistCursor
- .getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE)));
- item.setPaymentLink(itemlistCursor
- .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK));
- long mediaId = itemlistCursor
- .getLong(PodDBAdapter.IDX_FI_SMALL_MEDIA);
- if (mediaId != 0) {
- mediaIds.add(String.valueOf(mediaId));
- item.setMedia(new FeedMedia(mediaId, item));
- }
- item.setRead((itemlistCursor
- .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0) ? true
- : false);
- item.setItemIdentifier(itemlistCursor
- .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
- if (item.getState() == FeedItem.State.NEW) {
- unreadItems.add(item);
- }
-
- // extract chapters
- boolean hasSimpleChapters = itemlistCursor
- .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0;
- if (hasSimpleChapters) {
- Cursor chapterCursor = adapter
- .getSimpleChaptersOfFeedItemCursor(item);
- if (chapterCursor.moveToFirst()) {
- item.setChapters(new ArrayList<Chapter>());
- do {
- int chapterType = chapterCursor
- .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
- Chapter chapter = null;
- long start = chapterCursor
- .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
- String title = chapterCursor
- .getString(PodDBAdapter.KEY_TITLE_INDEX);
- String link = chapterCursor
- .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
-
- switch (chapterType) {
- case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
- chapter = new SimpleChapter(start, title, item,
- link);
- break;
- case ID3Chapter.CHAPTERTYPE_ID3CHAPTER:
- chapter = new ID3Chapter(start, title, item,
- link);
- break;
- case VorbisCommentChapter.CHAPTERTYPE_VORBISCOMMENT_CHAPTER:
- chapter = new VorbisCommentChapter(start,
- title, item, link);
- break;
- }
- chapter.setId(chapterCursor
- .getLong(PodDBAdapter.KEY_ID_INDEX));
- item.getChapters().add(chapter);
- } while (chapterCursor.moveToNext());
- }
- chapterCursor.close();
- }
- items.add(item);
- } while (itemlistCursor.moveToNext());
- }
- extractMediafromFeedItemlist(adapter, items, mediaIds);
- Collections.sort(items, new FeedItemPubdateComparator());
- return items;
- }
-
- private void extractMediafromFeedItemlist(PodDBAdapter adapter,
- ArrayList<FeedItem> items, ArrayList<String> mediaIds) {
- ArrayList<FeedItem> itemsCopy = new ArrayList<FeedItem>(items);
- Cursor cursor = adapter.getFeedMediaCursor(mediaIds
- .toArray(new String[mediaIds.size()]));
- if (cursor.moveToFirst()) {
- do {
- long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
- // find matching feed item
- FeedItem item = getMatchingItemForMedia(mediaId, itemsCopy);
- itemsCopy.remove(item);
- if (item != null) {
- Date playbackCompletionDate = null;
- long playbackCompletionTime = cursor
- .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX);
- if (playbackCompletionTime > 0) {
- playbackCompletionDate = new Date(
- playbackCompletionTime);
- }
-
- item.setMedia(new FeedMedia(
- mediaId,
- item,
- cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX),
- cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX),
- cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX),
- cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX),
- cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
- cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
- cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0,
- playbackCompletionDate));
- if (playbackCompletionDate != null) {
- playbackHistory.add(item);
- }
-
- }
- } while (cursor.moveToNext());
- cursor.close();
- }
- }
-
- private FeedItem getMatchingItemForMedia(long mediaId,
- ArrayList<FeedItem> items) {
- for (FeedItem item : items) {
- if (item.getMedia() != null && item.getMedia().getId() == mediaId) {
- return item;
- }
- }
- return null;
- }
-
- private void extractDownloadLogFromCursor(Context context,
- PodDBAdapter adapter) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Extracting DownloadLog");
- Cursor logCursor = adapter.getDownloadLogCursor();
- if (logCursor.moveToFirst()) {
- do {
- long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
- FeedFile feedfile = null;
-
- long feedfileId = logCursor
- .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
- int feedfileType = logCursor
- .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
- if (feedfileId != 0) {
- switch (feedfileType) {
- case Feed.FEEDFILETYPE_FEED:
- feedfile = getFeed(feedfileId);
- break;
- case FeedImage.FEEDFILETYPE_FEEDIMAGE:
- feedfile = getFeedImage(feedfileId);
- break;
- case FeedMedia.FEEDFILETYPE_FEEDMEDIA:
- feedfile = getFeedMedia(feedfileId);
- }
- }
- boolean successful = logCursor
- .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
- int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
- String reasonDetailed = logCursor
- .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
- String title = logCursor
- .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
- Date completionDate = new Date(
- logCursor
- .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
- downloadLog.add(new DownloadStatus(id, title, feedfile,
- feedfileType, successful, reason, completionDate,
- reasonDetailed));
-
- } while (logCursor.moveToNext());
- }
- logCursor.close();
- Collections.sort(downloadLog, new DownloadStatusComparator());
- }
-
- private void extractQueueFromCursor(Context context, PodDBAdapter adapter) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Extracting Queue");
- Cursor cursor = adapter.getQueueCursor();
-
- // Sort cursor results by ID with TreeMap
- TreeMap<Integer, FeedItem> map = new TreeMap<Integer, FeedItem>();
-
- if (cursor.moveToFirst()) {
- do {
- int index = cursor.getInt(PodDBAdapter.KEY_ID_INDEX);
- Feed feed = getFeed(cursor
- .getLong(PodDBAdapter.KEY_QUEUE_FEED_INDEX));
- if (feed != null) {
- FeedItem item = getFeedItem(
- cursor.getLong(PodDBAdapter.KEY_FEEDITEM_INDEX),
- feed);
- if (item != null) {
- map.put(index, item);
- }
- }
- } while (cursor.moveToNext());
- }
- cursor.close();
-
- for (Map.Entry<Integer, FeedItem> entry : map.entrySet()) {
- FeedItem item = entry.getValue();
- queue.add(item);
- }
- }
-
- /**
- * Loads description and contentEncoded values from the database and caches
- * it in the feeditem. The task callback will contain a String-array with
- * the description at index 0 and the value of contentEncoded at index 1.
- */
- public void loadExtraInformationOfItem(final Context context,
- final FeedItem item, FeedManager.TaskCallback<String[]> callback) {
- if (AppConfig.DEBUG) {
- Log.d(TAG,
- "Loading extra information of item with id " + item.getId());
- if (item.getTitle() != null) {
- Log.d(TAG, "Title: " + item.getTitle());
- }
- }
- dbExec.execute(new FeedManager.Task<String[]>(new Handler(), callback) {
-
- @Override
- public void execute() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- Cursor extraCursor = adapter.getExtraInformationOfItem(item);
- if (extraCursor.moveToFirst()) {
- String description = extraCursor
- .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
- String contentEncoded = extraCursor
- .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
- item.setCachedDescription(description);
- item.setCachedContentEncoded(contentEncoded);
- setResult(new String[] { description, contentEncoded });
- }
- adapter.close();
- }
- });
- }
-
- /**
- * Searches the descriptions of FeedItems of a specific feed for a given
- * string.
- *
- * @param feed
- * The feed whose items should be searched.
- * @param query
- * The search string
- * @param callback
- * A callback which will be used to return the search result
- * */
- public void searchFeedItemDescription(final Context context,
- final Feed feed, final String query,
- FeedManager.QueryTaskCallback callback) {
- dbExec.execute(new FeedManager.QueryTask(context, new Handler(),
- callback) {
-
- @Override
- public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemDescriptions(feed,
- query);
- setResult(searchResult);
- }
- });
- }
-
- /**
- * Searches the 'contentEncoded' field of FeedItems of a specific feed for a
- * given string.
- *
- * @param feed
- * The feed whose items should be searched.
- * @param query
- * The search string
- * @param callback
- * A callback which will be used to return the search result
- * */
- public void searchFeedItemContentEncoded(final Context context,
- final Feed feed, final String query,
- FeedManager.QueryTaskCallback callback) {
- dbExec.execute(new FeedManager.QueryTask(context, new Handler(),
- callback) {
-
- @Override
- public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemContentEncoded(feed,
- query);
- setResult(searchResult);
- }
- });
- }
-
- /** Returns the number of feeds that are currently in the feeds list. */
- public int getFeedsSize() {
- return feeds.size();
- }
-
- /** Returns the feed at the specified index of the feeds list. */
- public Feed getFeedAtIndex(int index) {
- return feeds.get(index);
- }
-
- /** Returns an array that contains all feeds of the feed manager. */
- public Feed[] getFeedsArray() {
- return feeds.toArray(new Feed[feeds.size()]);
- }
-
- List<Feed> getFeeds() {
- return feeds;
- }
-
- /**
- * Returns the number of items that are currently in the queue.
- *
- * @param enableEpisodeFilter
- * true if items without episodes should be ignored by this
- * method if the episode filter was enabled by the user.
- * */
- public int getQueueSize(boolean enableEpisodeFilter) {
- if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) {
- return EpisodeFilter.countItemsWithEpisodes(queue);
- } else {
- return queue.size();
- }
- }
-
- /**
- * Returns the FeedItem at the specified index of the queue.
- *
- * @param enableEpisodeFilter
- * true if items without episodes should be ignored by this
- * method if the episode filter was enabled by the user.
- *
- * @throws IndexOutOfBoundsException
- * if index is out of range
- * */
- public FeedItem getQueueItemAtIndex(int index, boolean enableEpisodeFilter) {
- if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) {
- return EpisodeFilter.accessEpisodeByIndex(queue, index);
- } else {
- return queue.get(index);
- }
- }
-
- /**
- * Returns the index of the episode that is currently being played in the
- * queue or -1 if the queue is empty or no episode in the queue is being
- * played.
- * */
- public int getQueuePlayingEpisodeIndex() {
- FeedManager manager = FeedManager.getInstance();
- int queueSize = manager.getQueueSize(true);
- if (queueSize == 0) {
- return -1;
- } else {
- for (int x = 0; x < queueSize; x++) {
- FeedItem item = getQueueItemAtIndex(x, true);
- if (item.getState() == FeedItem.State.PLAYING) {
- return x;
- }
- }
- return -1;
- }
- }
-
- /**
- * Returns the number of unread items.
- *
- * @param enableEpisodeFilter
- * true if items without episodes should be ignored by this
- * method if the episode filter was enabled by the user.
- * */
- public int getUnreadItemsSize(boolean enableEpisodeFilter) {
- if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) {
- return EpisodeFilter.countItemsWithEpisodes(unreadItems);
- } else {
- return unreadItems.size();
- }
- }
-
- /**
- * Returns the FeedItem at the specified index of the unread items list.
- *
- * @param enableEpisodeFilter
- * true if items without episodes should be ignored by this
- * method if the episode filter was enabled by the user.
- *
- * @throws IndexOutOfBoundsException
- * if index is out of range
- * */
- public FeedItem getUnreadItemAtIndex(int index, boolean enableEpisodeFilter) {
- if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) {
- return EpisodeFilter.accessEpisodeByIndex(unreadItems, index);
- } else {
- return unreadItems.get(index);
- }
- }
-
- /**
- * Returns the number of items in the playback history.
- * */
- public int getPlaybackHistorySize() {
- return playbackHistory.size();
- }
-
- /**
- * Returns the FeedItem at the specified index of the playback history.
- *
- * @throws IndexOutOfBoundsException
- * if index is out of range
- * */
- public FeedItem getPlaybackHistoryItemIndex(int index) {
- return playbackHistory.get(index);
- }
-
- /** Returns the number of items in the download log */
- public int getDownloadLogSize() {
- return downloadLog.size();
- }
-
- /** Returns the download status at the specified index of the download log. */
- public DownloadStatus getDownloadStatusFromLogAtIndex(int index) {
- return downloadLog.get(index);
- }
-
- /** Is called by a FeedManagerTask after completion. */
- public interface TaskCallback<V> {
- void onCompletion(V result);
- }
-
- /** Is called by a FeedManager.QueryTask after completion. */
- public interface QueryTaskCallback {
- void handleResult(Cursor result);
-
- void onCompletion();
- }
-
- /** A runnable that can post a callback to a handler after completion. */
- abstract class Task<V> implements Runnable {
- private Handler handler;
- private TaskCallback<V> callback;
- private V result;
-
- /**
- * Standard contructor. No callbacks are going to be posted to a
- * handler.
- */
- public Task() {
- super();
- }
-
- /**
- * The Task will post a Runnable to 'handler' that will execute the
- * 'callback' after completion.
- */
- public Task(Handler handler, TaskCallback<V> callback) {
- super();
- this.handler = handler;
- this.callback = callback;
- }
-
- @Override
- public final void run() {
- execute();
- if (handler != null && callback != null) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- callback.onCompletion(result);
- }
- });
- }
- }
-
- /** This method will be executed in the same thread as the run() method. */
- public abstract void execute();
-
- public void setResult(V result) {
- this.result = result;
- }
- }
-
- /**
- * A runnable which should be used for database queries. The onCompletion
- * method is executed on the database executor to handle Cursors correctly.
- * This class automatically creates a PodDBAdapter object and closes it when
- * it is no longer in use.
- */
- abstract class QueryTask implements Runnable {
- private QueryTaskCallback callback;
- private Cursor result;
- private Context context;
- private Handler handler;
-
- public QueryTask(Context context, Handler handler,
- QueryTaskCallback callback) {
- this.callback = callback;
- this.context = context;
- this.handler = handler;
- }
-
- @Override
- public final void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- execute(adapter);
- callback.handleResult(result);
- if (result != null && !result.isClosed()) {
- result.close();
- }
- adapter.close();
- if (handler != null && callback != null) {
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- callback.onCompletion();
- }
-
- });
- }
- }
-
- public abstract void execute(PodDBAdapter adapter);
-
- protected void setResult(Cursor c) {
- result = c;
- }
- }
-
-}
diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java
index 1368cf854..492867983 100644
--- a/src/de/danoeh/antennapod/feed/FeedMedia.java
+++ b/src/de/danoeh/antennapod/feed/FeedMedia.java
@@ -4,6 +4,7 @@ import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
+import java.util.concurrent.Callable;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@@ -11,358 +12,393 @@ import android.os.Parcel;
import android.os.Parcelable;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.ChapterUtils;
import de.danoeh.antennapod.util.playback.Playable;
public class FeedMedia extends FeedFile implements Playable {
- public static final int FEEDFILETYPE_FEEDMEDIA = 2;
- public static final int PLAYABLE_TYPE_FEEDMEDIA = 1;
-
- public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId";
- public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
-
- private int duration;
- private int position; // Current position in file
- private long size; // File size in Byte
- private String mime_type;
- private FeedItem item;
- private Date playbackCompletionDate;
-
- public FeedMedia(FeedItem i, String download_url, long size,
- String mime_type) {
- super(null, download_url, false);
- this.item = i;
- this.size = size;
- this.mime_type = mime_type;
- }
-
- public FeedMedia(long id, FeedItem item, int duration, int position,
- long size, String mime_type, String file_url, String download_url,
- boolean downloaded, Date playbackCompletionDate) {
- super(file_url, download_url, downloaded);
- this.id = id;
- this.item = item;
- this.duration = duration;
- this.position = position;
- this.size = size;
- this.mime_type = mime_type;
- this.playbackCompletionDate = playbackCompletionDate;
- }
-
- public FeedMedia(long id, FeedItem item) {
- super();
- this.id = id;
- this.item = item;
- }
-
- @Override
- public String getHumanReadableIdentifier() {
- if (item != null && item.getTitle() != null) {
- return item.getTitle();
- } else {
- return download_url;
- }
- }
-
- /** Uses mimetype to determine the type of media. */
- public MediaType getMediaType() {
- if (mime_type == null || mime_type.isEmpty()) {
- return MediaType.UNKNOWN;
- } else {
- if (mime_type.startsWith("audio")) {
- return MediaType.AUDIO;
- } else if (mime_type.startsWith("video")) {
- return MediaType.VIDEO;
- } else if (mime_type.equals("application/ogg")) {
- return MediaType.AUDIO;
- }
- }
- return MediaType.UNKNOWN;
- }
-
- public void updateFromOther(FeedMedia other) {
- super.updateFromOther(other);
- if (other.size > 0) {
- size = other.size;
- }
- if (other.mime_type != null) {
- mime_type = other.mime_type;
- }
- }
-
- public boolean compareWithOther(FeedMedia other) {
- if (super.compareWithOther(other)) {
- return true;
- }
- if (other.mime_type != null) {
- if (mime_type == null || !mime_type.equals(other.mime_type)) {
- return true;
- }
- }
- if (other.size > 0 && other.size != size) {
- return true;
- }
- return false;
- }
-
- /**
- * Reads playback preferences to determine whether this FeedMedia object is
- * currently being played.
- */
- public boolean isPlaying() {
- return PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
- && PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id;
- }
-
- @Override
- public int getTypeAsInt() {
- return FEEDFILETYPE_FEEDMEDIA;
- }
-
- public int getDuration() {
- return duration;
- }
-
- public void setDuration(int duration) {
- this.duration = duration;
- }
-
- public int getPosition() {
- return position;
- }
-
- public void setPosition(int position) {
- this.position = position;
- }
-
- public long getSize() {
- return size;
- }
-
- public void setSize(long size) {
- this.size = size;
- }
-
- public String getMime_type() {
- return mime_type;
- }
-
- public void setMime_type(String mime_type) {
- this.mime_type = mime_type;
- }
-
- public FeedItem getItem() {
- return item;
- }
-
- public void setItem(FeedItem item) {
- this.item = item;
- }
-
- public Date getPlaybackCompletionDate() {
- return playbackCompletionDate;
- }
-
- public void setPlaybackCompletionDate(Date playbackCompletionDate) {
- this.playbackCompletionDate = playbackCompletionDate;
- }
-
- public boolean isInProgress() {
- return (this.position > 0);
- }
-
- public FeedImage getImage() {
- if (item != null && item.getFeed() != null) {
- return item.getFeed().getImage();
- }
- return null;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeLong(item.getFeed().getId());
- dest.writeLong(item.getId());
- }
-
- @Override
- public void writeToPreferences(Editor prefEditor) {
- prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
- prefEditor.putLong(PREF_MEDIA_ID, id);
- }
-
- @Override
- public void loadMetadata() throws PlayableException {
- }
-
- @Override
- public void loadChapterMarks() {
- if (getChapters() == null && !localFileAvailable()) {
- ChapterUtils.loadChaptersFromStreamUrl(this);
- if (getChapters() != null) {
- FeedManager.getInstance().setFeedItem(PodcastApp.getInstance(),
- item);
- }
- }
-
- }
-
- @Override
- public String getEpisodeTitle() {
- if (getItem().getTitle() != null) {
- return getItem().getTitle();
- } else {
- return getItem().getIdentifyingValue();
- }
- }
-
- @Override
- public List<Chapter> getChapters() {
- return getItem().getChapters();
- }
-
- @Override
- public String getWebsiteLink() {
- return getItem().getLink();
- }
-
- @Override
- public String getFeedTitle() {
- return getItem().getFeed().getTitle();
- }
-
- @Override
- public Object getIdentifier() {
- return id;
- }
-
- @Override
- public String getLocalMediaUrl() {
- return file_url;
- }
-
- @Override
- public String getStreamUrl() {
- return download_url;
- }
-
- @Override
- public boolean localFileAvailable() {
- return isDownloaded() && file_url != null;
- }
-
- @Override
- public boolean streamAvailable() {
- return download_url != null;
- }
-
- @Override
- public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
- position = newPosition;
- FeedManager.getInstance().setFeedMedia(PodcastApp.getInstance(), this);
- }
-
- @Override
- public void onPlaybackStart() {
- }
-
- @Override
- public void onPlaybackCompleted() {
-
- }
-
- @Override
- public int getPlayableType() {
- return PLAYABLE_TYPE_FEEDMEDIA;
- }
-
- @Override
- public void setChapters(List<Chapter> chapters) {
- getItem().setChapters(chapters);
- }
-
- @Override
- public String getPaymentLink() {
- return getItem().getPaymentLink();
- }
-
- @Override
- public void loadShownotes(final ShownoteLoaderCallback callback) {
- String contentEncoded = item.getContentEncoded();
- if (item.getDescription() == null || contentEncoded == null) {
- FeedManager.getInstance().loadExtraInformationOfItem(
- PodcastApp.getInstance(), item,
- new FeedManager.TaskCallback<String[]>() {
- @Override
- public void onCompletion(String[] result) {
- if (result[1] != null) {
- callback.onShownotesLoaded(result[1]);
- } else {
- callback.onShownotesLoaded(result[0]);
-
- }
-
- }
- });
- } else {
- callback.onShownotesLoaded(contentEncoded);
- }
- }
-
- public static final Parcelable.Creator<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() {
- public FeedMedia createFromParcel(Parcel in) {
- long feedId = in.readLong();
- long itemId = in.readLong();
- FeedItem item = FeedManager.getInstance().getFeedItem(itemId,
- feedId);
- if (item != null) {
- return item.getMedia();
- } else {
- return null;
- }
- }
-
- public FeedMedia[] newArray(int size) {
- return new FeedMedia[size];
- }
- };
-
- @Override
- public InputStream openImageInputStream() {
- InputStream out = new Playable.DefaultPlayableImageLoader(this)
- .openImageInputStream();
- if (out == null) {
- if (item.getFeed().getImage() != null) {
- return item.getFeed().getImage().openImageInputStream();
- }
- }
- return out;
- }
-
- @Override
- public String getImageLoaderCacheKey() {
- String out = new Playable.DefaultPlayableImageLoader(this)
- .getImageLoaderCacheKey();
- if (out == null) {
- if (item.getFeed().getImage() != null) {
- return item.getFeed().getImage().getImageLoaderCacheKey();
- }
- }
- return out;
- }
-
- @Override
- public InputStream reopenImageInputStream(InputStream input) {
- if (input instanceof FileInputStream) {
- return item.getFeed().getImage().reopenImageInputStream(input);
- } else {
- return new Playable.DefaultPlayableImageLoader(this)
- .reopenImageInputStream(input);
- }
- }
+ public static final int FEEDFILETYPE_FEEDMEDIA = 2;
+ public static final int PLAYABLE_TYPE_FEEDMEDIA = 1;
+
+ public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId";
+ public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
+
+ private int duration;
+ private int position; // Current position in file
+ private long size; // File size in Byte
+ private String mime_type;
+ private volatile FeedItem item;
+ private Date playbackCompletionDate;
+
+ /* Used for loading item when restoring from parcel. */
+ private long itemID;
+
+ public FeedMedia(FeedItem i, String download_url, long size,
+ String mime_type) {
+ super(null, download_url, false);
+ this.item = i;
+ this.size = size;
+ this.mime_type = mime_type;
+ }
+
+ public FeedMedia(long id, FeedItem item, int duration, int position,
+ long size, String mime_type, String file_url, String download_url,
+ boolean downloaded, Date playbackCompletionDate) {
+ super(file_url, download_url, downloaded);
+ this.id = id;
+ this.item = item;
+ this.duration = duration;
+ this.position = position;
+ this.size = size;
+ this.mime_type = mime_type;
+ this.playbackCompletionDate = playbackCompletionDate == null
+ ? null : (Date) playbackCompletionDate.clone();
+ }
+
+ public FeedMedia(long id, FeedItem item) {
+ super();
+ this.id = id;
+ this.item = item;
+ }
+
+ @Override
+ public String getHumanReadableIdentifier() {
+ if (item != null && item.getTitle() != null) {
+ return item.getTitle();
+ } else {
+ return download_url;
+ }
+ }
+
+ /**
+ * Uses mimetype to determine the type of media.
+ */
+ public MediaType getMediaType() {
+ if (mime_type == null || mime_type.isEmpty()) {
+ return MediaType.UNKNOWN;
+ } else {
+ if (mime_type.startsWith("audio")) {
+ return MediaType.AUDIO;
+ } else if (mime_type.startsWith("video")) {
+ return MediaType.VIDEO;
+ } else if (mime_type.equals("application/ogg")) {
+ return MediaType.AUDIO;
+ }
+ }
+ return MediaType.UNKNOWN;
+ }
+
+ public void updateFromOther(FeedMedia other) {
+ super.updateFromOther(other);
+ if (other.size > 0) {
+ size = other.size;
+ }
+ if (other.mime_type != null) {
+ mime_type = other.mime_type;
+ }
+ }
+
+ public boolean compareWithOther(FeedMedia other) {
+ if (super.compareWithOther(other)) {
+ return true;
+ }
+ if (other.mime_type != null) {
+ if (mime_type == null || !mime_type.equals(other.mime_type)) {
+ return true;
+ }
+ }
+ if (other.size > 0 && other.size != size) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Reads playback preferences to determine whether this FeedMedia object is
+ * currently being played.
+ */
+ public boolean isPlaying() {
+ return PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
+ && PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id;
+ }
+
+ @Override
+ public int getTypeAsInt() {
+ return FEEDFILETYPE_FEEDMEDIA;
+ }
+
+ public int getDuration() {
+ return duration;
+ }
+
+ public void setDuration(int duration) {
+ this.duration = duration;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public void setPosition(int position) {
+ this.position = position;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ public String getMime_type() {
+ return mime_type;
+ }
+
+ public void setMime_type(String mime_type) {
+ this.mime_type = mime_type;
+ }
+
+ public FeedItem getItem() {
+ return item;
+ }
+
+ /**
+ * Sets the item object of this FeedMedia. If the given
+ * FeedItem object is not null, it's 'media'-attribute value
+ * will also be set to this media object.
+ * */
+ public void setItem(FeedItem item) {
+ this.item = item;
+ if (item != null && item.getMedia() != this) {
+ item.setMedia(this);
+ }
+ }
+
+ public Date getPlaybackCompletionDate() {
+ return playbackCompletionDate == null
+ ? null : (Date) playbackCompletionDate.clone(); }
+
+ public void setPlaybackCompletionDate(Date playbackCompletionDate) {
+ this.playbackCompletionDate = playbackCompletionDate == null
+ ? null : (Date) playbackCompletionDate.clone();
+ }
+
+ public boolean isInProgress() {
+ return (this.position > 0);
+ }
+
+ public FeedImage getImage() {
+ if (item != null && item.getFeed() != null) {
+ return item.getFeed().getImage();
+ }
+ return null;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(id);
+ dest.writeLong(item.getId());
+
+ dest.writeInt(duration);
+ dest.writeInt(position);
+ dest.writeLong(size);
+ dest.writeString(mime_type);
+ dest.writeString(file_url);
+ dest.writeString(download_url);
+ dest.writeByte((byte) ((downloaded) ? 1 : 0));
+ dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0);
+ }
+
+ @Override
+ public void writeToPreferences(Editor prefEditor) {
+ prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
+ prefEditor.putLong(PREF_MEDIA_ID, id);
+ }
+
+ @Override
+ public void loadMetadata() throws PlayableException {
+ if (item == null && itemID != 0) {
+ item = DBReader.getFeedItem(PodcastApp.getInstance(), itemID);
+ }
+ }
+
+ @Override
+ public void loadChapterMarks() {
+ if (getChapters() == null && !localFileAvailable()) {
+ ChapterUtils.loadChaptersFromStreamUrl(this);
+ if (getChapters() != null && item != null) {
+ DBWriter.setFeedItem(PodcastApp.getInstance(),
+ item);
+ }
+ }
+
+ }
+
+ @Override
+ public String getEpisodeTitle() {
+ if (item == null) {
+ return null;
+ }
+ if (getItem().getTitle() != null) {
+ return getItem().getTitle();
+ } else {
+ return getItem().getIdentifyingValue();
+ }
+ }
+
+ @Override
+ public List<Chapter> getChapters() {
+ if (item == null) {
+ return null;
+ }
+ return getItem().getChapters();
+ }
+
+ @Override
+ public String getWebsiteLink() {
+ if (item == null) {
+ return null;
+ }
+ return getItem().getLink();
+ }
+
+ @Override
+ public String getFeedTitle() {
+ if (item == null) {
+ return null;
+ }
+ return getItem().getFeed().getTitle();
+ }
+
+ @Override
+ public Object getIdentifier() {
+ return id;
+ }
+
+ @Override
+ public String getLocalMediaUrl() {
+ return file_url;
+ }
+
+ @Override
+ public String getStreamUrl() {
+ return download_url;
+ }
+
+ @Override
+ public String getPaymentLink() {
+ if (item == null) {
+ return null;
+ }
+ return getItem().getPaymentLink();
+ }
+
+ @Override
+ public boolean localFileAvailable() {
+ return isDownloaded() && file_url != null;
+ }
+
+ @Override
+ public boolean streamAvailable() {
+ return download_url != null;
+ }
+
+ @Override
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
+ position = newPosition;
+ DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this);
+ }
+
+ @Override
+ public void onPlaybackStart() {
+ }
+
+ @Override
+ public void onPlaybackCompleted() {
+
+ }
+
+ @Override
+ public int getPlayableType() {
+ return PLAYABLE_TYPE_FEEDMEDIA;
+ }
+
+ @Override
+ public void setChapters(List<Chapter> chapters) {
+ getItem().setChapters(chapters);
+ }
+
+ @Override
+ public Callable<String> loadShownotes() {
+ return new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ if (item == null) {
+ item = DBReader.getFeedItem(PodcastApp.getInstance(), itemID);
+ }
+ if (item.getContentEncoded() == null || item.getDescription() == null) {
+ DBReader.loadExtraInformationOfFeedItem(PodcastApp.getInstance(), item);
+
+ }
+ return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
+ }
+ };
+ }
+
+ public static final Parcelable.Creator<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() {
+ public FeedMedia createFromParcel(Parcel in) {
+ final long id = in.readLong();
+ final long itemID = in.readLong();
+ FeedMedia result = new FeedMedia(id, null, in.readInt(), in.readInt(), in.readLong(), in.readString(), in.readString(),
+ in.readString(), in.readByte() != 0, new Date(in.readLong()));
+ result.itemID = itemID;
+ return result;
+ }
+
+ public FeedMedia[] newArray(int size) {
+ return new FeedMedia[size];
+ }
+ };
+
+ @Override
+ public InputStream openImageInputStream() {
+ InputStream out = new Playable.DefaultPlayableImageLoader(this)
+ .openImageInputStream();
+ if (out == null) {
+ if (item.getFeed().getImage() != null) {
+ return item.getFeed().getImage().openImageInputStream();
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public String getImageLoaderCacheKey() {
+ String out = new Playable.DefaultPlayableImageLoader(this)
+ .getImageLoaderCacheKey();
+ if (out == null) {
+ if (item.getFeed().getImage() != null) {
+ return item.getFeed().getImage().getImageLoaderCacheKey();
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public InputStream reopenImageInputStream(InputStream input) {
+ if (input instanceof FileInputStream) {
+ return item.getFeed().getImage().reopenImageInputStream(input);
+ } else {
+ return new Playable.DefaultPlayableImageLoader(this)
+ .reopenImageInputStream(input);
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/feed/FeedSearcher.java b/src/de/danoeh/antennapod/feed/FeedSearcher.java
deleted file mode 100644
index ab7c174bc..000000000
--- a/src/de/danoeh/antennapod/feed/FeedSearcher.java
+++ /dev/null
@@ -1,253 +0,0 @@
-package de.danoeh.antennapod.feed;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Looper;
-import android.util.Log;
-import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.PodcastApp;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.storage.PodDBAdapter;
-import de.danoeh.antennapod.util.comparator.SearchResultValueComparator;
-
-/** Performs search on Feeds and FeedItems */
-public class FeedSearcher {
- private static final String TAG = "FeedSearcher";
-
- // Search result values
- private static final int VALUE_FEED_TITLE = 3;
- private static final int VALUE_ITEM_TITLE = 2;
- private static final int VALUE_ITEM_CHAPTER = 1;
- private static final int VALUE_ITEM_DESCRIPTION = 0;
- private static final int VALUE_WORD_MATCH = 4;
-
- /** Performs a search in all feeds or one specific feed. */
- public static ArrayList<SearchResult> performSearch(final Context context,
- final String query, final Feed selectedFeed) {
- final String lcQuery = query.toLowerCase();
- final ArrayList<SearchResult> result = new ArrayList<SearchResult>();
- if (selectedFeed == null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Performing global search");
- if (AppConfig.DEBUG)
- Log.d(TAG, "Searching Feed titles");
- searchFeedtitles(lcQuery, result);
- } else if (AppConfig.DEBUG) {
- Log.d(TAG, "Performing search on specific feed");
- }
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "Searching Feeditem titles");
- searchFeedItemTitles(lcQuery, result, selectedFeed);
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "Searching item-chaptertitles");
- searchFeedItemChapters(lcQuery, result, selectedFeed);
-
- final FeedManager manager = FeedManager.getInstance();
- Looper.prepare();
- manager.searchFeedItemDescription(context, selectedFeed, lcQuery,
- new FeedManager.QueryTaskCallback() {
-
- @Override
- public void handleResult(Cursor cResult) {
- searchFeedItemContentEncodedCursor(lcQuery, result,
- selectedFeed, cResult);
-
- }
-
- @Override
- public void onCompletion() {
- manager.searchFeedItemContentEncoded(context,
- selectedFeed, lcQuery,
- new FeedManager.QueryTaskCallback() {
-
- @Override
- public void handleResult(Cursor cResult) {
- searchFeedItemDescriptionCursor(
- lcQuery, result, selectedFeed,
- cResult);
- }
-
- @Override
- public void onCompletion() {
- Looper.myLooper().quit();
- }
- });
- }
- });
-
- Looper.loop();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Sorting results");
- Collections.sort(result, new SearchResultValueComparator());
-
- return result;
- }
-
- private static void searchFeedtitles(String query,
- ArrayList<SearchResult> destination) {
- FeedManager manager = FeedManager.getInstance();
- for (Feed feed : manager.getFeeds()) {
- SearchResult result = createSearchResult(feed, query, feed
- .getTitle().toLowerCase(), VALUE_FEED_TITLE);
- if (result != null) {
- destination.add(result);
- }
- }
- }
-
- private static void searchFeedItemTitles(String query,
- ArrayList<SearchResult> destination, Feed selectedFeed) {
- FeedManager manager = FeedManager.getInstance();
- if (selectedFeed == null) {
- for (Feed feed : manager.getFeeds()) {
- searchFeedItemTitlesSingleFeed(query, destination, feed);
- }
- } else {
- searchFeedItemTitlesSingleFeed(query, destination, selectedFeed);
- }
- }
-
- private static void searchFeedItemTitlesSingleFeed(String query,
- ArrayList<SearchResult> destination, Feed feed) {
- for (FeedItem item : feed.getItems()) {
- SearchResult result = createSearchResult(item, query, item
- .getTitle().toLowerCase(), VALUE_ITEM_TITLE);
- if (result != null) {
- result.setSubtitle(PodcastApp.getInstance().getString(
- R.string.found_in_title_label));
- destination.add(result);
- }
-
- }
- }
-
- private static void searchFeedItemChapters(String query,
- ArrayList<SearchResult> destination, Feed selectedFeed) {
- FeedManager manager = FeedManager.getInstance();
- if (selectedFeed == null) {
- for (Feed feed : manager.getFeeds()) {
- searchFeedItemChaptersSingleFeed(query, destination, feed);
- }
- } else {
- searchFeedItemChaptersSingleFeed(query, destination, selectedFeed);
- }
- }
-
- private static void searchFeedItemChaptersSingleFeed(String query,
- ArrayList<SearchResult> destination, Feed feed) {
- for (FeedItem item : feed.getItems()) {
- if (item.getChapters() != null) {
- for (Chapter sc : item.getChapters()) {
- SearchResult result = createSearchResult(item, query, sc
- .getTitle().toLowerCase(), VALUE_ITEM_CHAPTER);
- if (result != null) {
- result.setSubtitle(PodcastApp.getInstance().getString(
- R.string.found_in_chapters_label));
- destination.add(result);
- }
- }
- }
- }
- }
-
- private static void searchFeedItemDescriptionCursor(String query,
- ArrayList<SearchResult> destination, Feed feed, Cursor cursor) {
- FeedManager manager = FeedManager.getInstance();
- if (cursor.moveToFirst()) {
- do {
- final long itemId = cursor
- .getLong(PodDBAdapter.IDX_FI_EXTRA_ID);
- String content = cursor
- .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
- if (content != null) {
- content = content.toLowerCase();
- final long feedId = cursor
- .getLong(PodDBAdapter.IDX_FI_EXTRA_FEED);
- FeedItem item = null;
- if (feed == null) {
- item = manager.getFeedItem(itemId, feedId);
- } else {
- item = manager.getFeedItem(itemId, feed);
- }
- if (item != null) {
- SearchResult searchResult = createSearchResult(item,
- query, content, VALUE_ITEM_DESCRIPTION);
- if (searchResult != null) {
- searchResult.setSubtitle(PodcastApp.getInstance()
- .getString(
- R.string.found_in_shownotes_label));
- destination.add(searchResult);
-
- }
- }
- }
-
- } while (cursor.moveToNext());
- }
- }
-
- private static void searchFeedItemContentEncodedCursor(String query,
- ArrayList<SearchResult> destination, Feed feed, Cursor cursor) {
- FeedManager manager = FeedManager.getInstance();
- if (cursor.moveToFirst()) {
- do {
- final long itemId = cursor
- .getLong(PodDBAdapter.IDX_FI_EXTRA_ID);
- String content = cursor
- .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
- if (content != null) {
- content = content.toLowerCase();
-
- final long feedId = cursor
- .getLong(PodDBAdapter.IDX_FI_EXTRA_FEED);
- FeedItem item = null;
- if (feed == null) {
- item = manager.getFeedItem(itemId, feedId);
- } else {
- item = manager.getFeedItem(itemId, feed);
- }
- if (item != null) {
- SearchResult searchResult = createSearchResult(item,
- query, content, VALUE_ITEM_DESCRIPTION);
- if (searchResult != null) {
- searchResult.setSubtitle(PodcastApp.getInstance()
- .getString(
- R.string.found_in_shownotes_label));
- destination.add(searchResult);
- }
- }
- }
- } while (cursor.moveToNext());
- }
- }
-
- private static SearchResult createSearchResult(FeedComponent component,
- String query, String text, int baseValue) {
- int bonus = 0;
- boolean found = false;
- // try word search
- Pattern word = Pattern.compile("\b" + query + "\b");
- Matcher matcher = word.matcher(text);
- found = matcher.find();
- if (found) {
- bonus = VALUE_WORD_MATCH;
- } else {
- // search for other occurence
- found = text.contains(query);
- }
-
- if (found) {
- return new SearchResult(component, baseValue + bonus);
- } else {
- return null;
- }
- }
-
-}
diff --git a/src/de/danoeh/antennapod/feed/SearchResult.java b/src/de/danoeh/antennapod/feed/SearchResult.java
index b4016f2e8..1cba389ec 100644
--- a/src/de/danoeh/antennapod/feed/SearchResult.java
+++ b/src/de/danoeh/antennapod/feed/SearchResult.java
@@ -7,10 +7,11 @@ public class SearchResult {
/** Higher value means more importance */
private int value;
- public SearchResult(FeedComponent component, int value) {
+ public SearchResult(FeedComponent component, int value, String subtitle) {
super();
this.component = component;
this.value = value;
+ this.subtitle = subtitle;
}
public FeedComponent getComponent() {
diff --git a/src/de/danoeh/antennapod/fragment/CoverFragment.java b/src/de/danoeh/antennapod/fragment/CoverFragment.java
index 6be76f515..791315719 100644
--- a/src/de/danoeh/antennapod/fragment/CoverFragment.java
+++ b/src/de/danoeh/antennapod/fragment/CoverFragment.java
@@ -1,14 +1,13 @@
package de.danoeh.antennapod.fragment;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
-import com.actionbarsherlock.app.SherlockFragment;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment;
@@ -16,7 +15,7 @@ import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.util.playback.Playable;
/** Displays the cover and the title of a FeedItem. */
-public class CoverFragment extends SherlockFragment implements
+public class CoverFragment extends Fragment implements
AudioplayerContentFragment {
private static final String TAG = "CoverFragment";
private static final String ARG_PLAYABLE = "arg.playable";
diff --git a/src/de/danoeh/antennapod/fragment/EpisodesFragment.java b/src/de/danoeh/antennapod/fragment/EpisodesFragment.java
index 4039235e0..a99056c9a 100644
--- a/src/de/danoeh/antennapod/fragment/EpisodesFragment.java
+++ b/src/de/danoeh/antennapod/fragment/EpisodesFragment.java
@@ -1,20 +1,16 @@
package de.danoeh.antennapod.fragment;
+import android.content.Context;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.util.Log;
-import android.view.ContextMenu;
+import android.view.*;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
-import com.actionbarsherlock.app.SherlockFragment;
-import com.actionbarsherlock.view.Menu;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.ItemviewActivity;
@@ -24,11 +20,16 @@ import de.danoeh.antennapod.adapter.ExternalEpisodesListAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
-public class EpisodesFragment extends SherlockFragment {
+import java.util.List;
+
+public class EpisodesFragment extends Fragment {
private static final String TAG = "EpisodesFragment";
private static final int EVENTS = EventDistributor.QUEUE_UPDATE
@@ -40,6 +41,9 @@ public class EpisodesFragment extends SherlockFragment {
private ExpandableListView listView;
private ExternalEpisodesListAdapter adapter;
+ private List<FeedItem> queue;
+ private List<FeedItem> unreadItems;
+
protected FeedItem selectedItem = null;
protected long selectedGroupId = -1;
protected boolean contextMenuClosed = true;
@@ -92,7 +96,7 @@ public class EpisodesFragment extends SherlockFragment {
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
adapter = new ExternalEpisodesListAdapter(getActivity(),
- adapterCallback, groupActionCallback);
+ adapterCallback, groupActionCallback, itemAccess);
listView.setAdapter(adapter);
listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_QUEUE);
listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_UNREAD);
@@ -117,9 +121,73 @@ public class EpisodesFragment extends SherlockFragment {
return true;
}
});
+ loadData();
registerForContextMenu(listView);
+
}
+ ExternalEpisodesListAdapter.ItemAccess itemAccess = new ExternalEpisodesListAdapter.ItemAccess() {
+
+ @Override
+ public int getQueueSize() {
+ return (queue != null) ? queue.size() : 0;
+ }
+
+ @Override
+ public int getUnreadItemsSize() {
+ return (unreadItems != null) ? unreadItems.size() : 0;
+ }
+
+ @Override
+ public FeedItem getQueueItemAt(int position) {
+ return (queue != null) ? queue.get(position) : null;
+ }
+
+ @Override
+ public FeedItem getUnreadItemAt(int position) {
+ return (unreadItems != null) ? unreadItems.get(position) : null;
+ }
+ };
+
+ private void loadData() {
+ AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() {
+ private volatile List<FeedItem> queueRef;
+ private volatile List<FeedItem> unreadItemsRef;
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Starting to load list data");
+ Context context = EpisodesFragment.this.getActivity();
+ if (context != null) {
+ queueRef = DBReader.getQueue(context);
+ unreadItemsRef = DBReader.getUnreadItemsList(context);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ if (queueRef != null && unreadItemsRef != null) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Done loading list data");
+ queue = queueRef;
+ unreadItems = unreadItemsRef;
+ if (adapter != null) {
+ adapter.notifyDataSetChanged();
+ }
+ } else {
+ if (queueRef == null) {
+ Log.e(TAG, "Could not load queue");
+ }
+ if (unreadItemsRef == null) {
+ Log.e(TAG, "Could not load unread items");
+ }
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
@@ -127,7 +195,7 @@ public class EpisodesFragment extends SherlockFragment {
if ((EVENTS & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received contentUpdate Intent.");
- adapter.notifyDataSetChanged();
+ loadData();
}
}
};
@@ -146,13 +214,13 @@ public class EpisodesFragment extends SherlockFragment {
menu.setHeaderTitle(selectedItem.getTitle());
FeedItemMenuHandler.onPrepareMenu(
- new FeedItemMenuHandler.MenuInterface() {
+ new FeedItemMenuHandler.MenuInterface() {
- @Override
- public void setItemVisibility(int id, boolean visible) {
- menu.findItem(id).setVisible(visible);
- }
- }, selectedItem, false);
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ menu.findItem(id).setVisible(visible);
+ }
+ }, selectedItem, false, QueueAccess.ItemListAccess(queue));
} else if (selectedGroupId == ExternalEpisodesListAdapter.GROUP_POS_QUEUE) {
menu.add(Menu.NONE, R.id.organize_queue_item, Menu.NONE,
@@ -172,11 +240,10 @@ public class EpisodesFragment extends SherlockFragment {
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
boolean handled = false;
- FeedManager manager = FeedManager.getInstance();
if (selectedItem != null) {
try {
handled = FeedItemMenuHandler.onMenuItemClicked(
- getSherlockActivity(), item.getItemId(), selectedItem);
+ getActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
@@ -191,10 +258,10 @@ public class EpisodesFragment extends SherlockFragment {
OrganizeQueueActivity.class));
break;
case R.id.clear_queue_item:
- manager.clearQueue(getActivity());
+ DBWriter.clearQueue(getActivity());
break;
case R.id.download_all_item:
- manager.downloadAllItemsInQueue(getActivity());
+ DBTasks.downloadAllItemsInQueue(getActivity());
break;
default:
handled = false;
@@ -203,10 +270,10 @@ public class EpisodesFragment extends SherlockFragment {
handled = true;
switch (item.getItemId()) {
case R.id.mark_all_read_item:
- manager.markAllItemsRead(getActivity());
+ DBWriter.markAllItemsRead(getActivity());
break;
case R.id.enqueue_all_item:
- manager.enqueueAllNewItems(getActivity());
+ DBTasks.enqueueAllNewItems(getActivity());
break;
default:
handled = false;
diff --git a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
index a50e820b6..3f967bbbe 100644
--- a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.fragment;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -10,8 +11,6 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockFragment;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
@@ -24,7 +23,7 @@ import de.danoeh.antennapod.util.playback.PlaybackController;
* Fragment which is supposed to be displayed outside of the MediaplayerActivity
* if the PlaybackService is running
*/
-public class ExternalPlayerFragment extends SherlockFragment {
+public class ExternalPlayerFragment extends Fragment {
private static final String TAG = "ExternalPlayerFragment";
private ViewGroup fragmentLayout;
@@ -139,7 +138,10 @@ public class ExternalPlayerFragment extends SherlockFragment {
@Override
public void loadMediaInfo() {
- ExternalPlayerFragment.this.loadMediaInfo();
+ ExternalPlayerFragment fragment = ExternalPlayerFragment.this;
+ if (fragment != null) {
+ fragment.loadMediaInfo();
+ }
}
@Override
@@ -174,6 +176,12 @@ public class ExternalPlayerFragment extends SherlockFragment {
.newOnPlayButtonClickListener());
}
}
+
+ @Override
+ public void onPlaybackSpeedChange() {
+ // TODO Auto-generated method stub
+
+ }
};
}
diff --git a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
index c3034c2af..ed607b279 100644
--- a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
+++ b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
@@ -1,24 +1,18 @@
package de.danoeh.antennapod.fragment;
import android.annotation.SuppressLint;
-import android.app.Activity;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.view.ActionMode;
import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.GridView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.actionbarsherlock.app.SherlockFragment;
-import com.actionbarsherlock.view.ActionMode;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.*;
+import android.widget.*;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.FeedItemlistActivity;
@@ -28,201 +22,280 @@ import de.danoeh.antennapod.dialog.ConfirmationDialog;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
+import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
-public class FeedlistFragment extends SherlockFragment implements
- ActionMode.Callback, AdapterView.OnItemClickListener,
- AdapterView.OnItemLongClickListener {
- private static final String TAG = "FeedlistFragment";
-
- private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
- | EventDistributor.DOWNLOAD_QUEUED
- | EventDistributor.FEED_LIST_UPDATE
- | EventDistributor.UNREAD_ITEMS_UPDATE;
-
- public static final String EXTRA_SELECTED_FEED = "extra.de.danoeh.antennapod.activity.selected_feed";
-
- private FeedManager manager;
- private FeedlistAdapter fla;
-
- private Feed selectedFeed;
- private ActionMode mActionMode;
-
- private GridView gridView;
- private ListView listView;
- private TextView txtvEmpty;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating");
- manager = FeedManager.getInstance();
- fla = new FeedlistAdapter(getActivity());
-
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View result = inflater.inflate(R.layout.feedlist, container, false);
- listView = (ListView) result.findViewById(android.R.id.list);
- gridView = (GridView) result.findViewById(R.id.grid);
- txtvEmpty = (TextView) result.findViewById(android.R.id.empty);
-
- return result;
-
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- if (listView != null) {
- listView.setOnItemClickListener(this);
- listView.setOnItemLongClickListener(this);
- listView.setAdapter(fla);
- listView.setEmptyView(txtvEmpty);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Using ListView");
- } else {
- gridView.setOnItemClickListener(this);
- gridView.setOnItemLongClickListener(this);
- gridView.setAdapter(fla);
- gridView.setEmptyView(txtvEmpty);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Using GridView");
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Resuming");
- EventDistributor.getInstance().register(contentUpdate);
- fla.notifyDataSetChanged();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- EventDistributor.getInstance().unregister(contentUpdate);
- if (mActionMode != null) {
- mActionMode.finish();
- }
- }
-
- private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
-
- @Override
- public void update(EventDistributor eventDistributor, Integer arg) {
- if ((EVENTS & arg) != 0) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received contentUpdate Intent.");
- fla.notifyDataSetChanged();
- }
- }
- };
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- FeedMenuHandler.onCreateOptionsMenu(mode.getMenuInflater(), menu);
- mode.setTitle(selectedFeed.getTitle());
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return FeedMenuHandler.onPrepareOptionsMenu(menu, selectedFeed);
- }
-
- @SuppressLint("NewApi")
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- try {
- if (FeedMenuHandler.onOptionsItemClicked(getSherlockActivity(),
- item, selectedFeed)) {
- fla.notifyDataSetChanged();
- } else {
- switch (item.getItemId()) {
- case R.id.remove_item:
- final FeedRemover remover = new FeedRemover(
- getSherlockActivity(), selectedFeed) {
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- fla.notifyDataSetChanged();
- }
- };
- ConfirmationDialog conDialog = new ConfirmationDialog(
- getActivity(), R.string.remove_feed_label,
- R.string.feed_delete_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(
- DialogInterface dialog) {
- dialog.dismiss();
- remover.executeAsync();
- }
- };
- conDialog.createNewDialog().show();
- break;
- }
- }
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(
- getActivity(), e.getMessage());
- }
- mode.finish();
- return true;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- mActionMode = null;
- selectedFeed = null;
- fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
- }
-
- @Override
- public void onItemClick(AdapterView<?> arg0, View arg1, int position,
- long id) {
- Feed selection = fla.getItem(position);
- Intent showFeed = new Intent(getActivity(), FeedItemlistActivity.class);
- showFeed.putExtra(EXTRA_SELECTED_FEED, selection.getId());
-
- getActivity().startActivity(showFeed);
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view,
- int position, long id) {
- Feed selection = fla.getItem(position);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Selected Feed with title " + selection.getTitle());
- if (selection != null) {
- if (mActionMode != null) {
- mActionMode.finish();
- }
- fla.setSelectedItemIndex(position);
- selectedFeed = selection;
- mActionMode = getSherlockActivity().startActionMode(
- FeedlistFragment.this);
-
- }
- return true;
- }
+import java.util.List;
+
+public class FeedlistFragment extends Fragment implements
+ ActionMode.Callback, AdapterView.OnItemClickListener,
+ AdapterView.OnItemLongClickListener {
+ private static final String TAG = "FeedlistFragment";
+
+ private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
+ | EventDistributor.DOWNLOAD_QUEUED
+ | EventDistributor.FEED_LIST_UPDATE
+ | EventDistributor.UNREAD_ITEMS_UPDATE;
+
+ public static final String EXTRA_SELECTED_FEED = "extra.de.danoeh.antennapod.activity.selected_feed";
+
+ private FeedlistAdapter fla;
+ private List<Feed> feeds;
+ private List<FeedItemStatistics> feedItemStatistics;
+
+ private Feed selectedFeed;
+ private ActionMode mActionMode;
+
+ private GridView gridView;
+ private ListView listView;
+ private TextView emptyView;
+
+ private FeedlistAdapter.ItemAccess itemAccess = new FeedlistAdapter.ItemAccess() {
+
+ @Override
+ public Feed getItem(int position) {
+ if (feeds != null) {
+ return feeds.get(position);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public FeedItemStatistics getFeedItemStatistics(int position) {
+ if (feedItemStatistics != null && position < feedItemStatistics.size()) {
+ return feedItemStatistics.get(position);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if (feeds != null) {
+ return feeds.size();
+ } else {
+ return 0;
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating");
+ fla = new FeedlistAdapter(getActivity(), itemAccess);
+ loadFeeds();
+ }
+
+ private void loadFeeds() {
+ AsyncTask<Void, Void, List[]> loadTask = new AsyncTask<Void, Void, List[]>() {
+ @Override
+ protected List[] doInBackground(Void... params) {
+ Context context = getActivity();
+ if (context != null) {
+ return new List[]{DBReader.getFeedList(context),
+ DBReader.getFeedStatisticsList(context)};
+ } else {
+ return null;
+ }
+ }
+
+
+ @Override
+ protected void onPostExecute(List[] result) {
+ super.onPostExecute(result);
+ if (result != null) {
+ feeds = result[0];
+ feedItemStatistics = result[1];
+ setEmptyViewIfListIsEmpty();
+ if (fla != null) {
+ fla.notifyDataSetChanged();
+ }
+ } else {
+ Log.e(TAG, "Failed to load feeds");
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View result = inflater.inflate(R.layout.feedlist, container, false);
+ listView = (ListView) result.findViewById(android.R.id.list);
+ gridView = (GridView) result.findViewById(R.id.grid);
+ emptyView = (TextView) result.findViewById(android.R.id.empty);
+
+ return result;
+
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (listView != null) {
+ listView.setOnItemClickListener(this);
+ listView.setOnItemLongClickListener(this);
+ listView.setAdapter(fla);
+ listView.setEmptyView(emptyView);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Using ListView");
+ } else {
+ gridView.setOnItemClickListener(this);
+ gridView.setOnItemLongClickListener(this);
+ gridView.setAdapter(fla);
+ gridView.setEmptyView(emptyView);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Using GridView");
+ }
+ setEmptyViewIfListIsEmpty();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Resuming");
+ EventDistributor.getInstance().register(contentUpdate);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ EventDistributor.getInstance().unregister(contentUpdate);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
+ }
+
+ private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
+
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EVENTS & arg) != 0) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received contentUpdate Intent.");
+ loadFeeds();
+ }
+ }
+ };
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ FeedMenuHandler.onCreateOptionsMenu(mode.getMenuInflater(), menu);
+ mode.setTitle(selectedFeed.getTitle());
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return FeedMenuHandler.onPrepareOptionsMenu(menu, selectedFeed);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ try {
+ if (FeedMenuHandler.onOptionsItemClicked(getActivity(),
+ item, selectedFeed)) {
+ loadFeeds();
+ } else {
+ switch (item.getItemId()) {
+ case R.id.remove_item:
+ final FeedRemover remover = new FeedRemover(
+ getActivity(), selectedFeed) {
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ loadFeeds();
+ }
+ };
+ ConfirmationDialog conDialog = new ConfirmationDialog(
+ getActivity(), R.string.remove_feed_label,
+ R.string.feed_delete_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(
+ DialogInterface dialog) {
+ dialog.dismiss();
+ remover.executeAsync();
+ }
+ };
+ conDialog.createNewDialog().show();
+ break;
+ }
+ }
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(
+ getActivity(), e.getMessage());
+ }
+ mode.finish();
+ return true;
+ }
+
+ private boolean actionModeDestroyWorkaround = false; // TODO remove this workaround
+ private boolean skipWorkAround = Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ if (skipWorkAround || actionModeDestroyWorkaround) {
+ mActionMode = null;
+ selectedFeed = null;
+ fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
+ actionModeDestroyWorkaround = false;
+ } else {
+ actionModeDestroyWorkaround = true;
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> arg0, View arg1, int position,
+ long id) {
+ Feed selection = fla.getItem(position);
+ Intent showFeed = new Intent(getActivity(), FeedItemlistActivity.class);
+ showFeed.putExtra(EXTRA_SELECTED_FEED, selection.getId());
+
+ getActivity().startActivity(showFeed);
+ }
+
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ Feed selection = fla.getItem(position);
+ if (selection != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Selected Feed with title " + selection.getTitle());
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
+ fla.setSelectedItemIndex(position);
+ selectedFeed = selection;
+ mActionMode = ((ActionBarActivity) getActivity()).startSupportActionMode(FeedlistFragment.this);
+
+ }
+ return true;
+ }
+
+ private AbsListView getMainView() {
+ return (listView != null) ? listView : gridView;
+ }
+
+ private void setEmptyViewIfListIsEmpty() {
+ if (getMainView() != null && emptyView != null && feeds != null) {
+ if (feeds.isEmpty()) {
+ emptyView.setText(R.string.no_feeds_label);
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
index 10f43718f..c996f497e 100644
--- a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
@@ -1,13 +1,15 @@
package de.danoeh.antennapod.fragment;
+import android.content.*;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.ActionBarActivity;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.util.ShownotesProvider;
import org.apache.commons.lang3.StringEscapeUtils;
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.AsyncTask;
@@ -27,442 +29,429 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
-import com.actionbarsherlock.app.SherlockFragment;
-
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.ShareUtils;
import de.danoeh.antennapod.util.playback.Playable;
+import java.util.concurrent.Callable;
+
/** Displays the description of a Playable object in a Webview. */
-public class ItemDescriptionFragment extends SherlockFragment {
-
- private static final String TAG = "ItemDescriptionFragment";
-
- private static final String PREF = "ItemDescriptionFragmentPrefs";
- private static final String PREF_SCROLL_Y = "prefScrollY";
- private static final String PREF_PLAYABLE_ID = "prefPlayableId";
-
- private static final String ARG_PLAYABLE = "arg.playable";
-
- private static final String ARG_FEED_ID = "arg.feedId";
- private static final String ARG_FEED_ITEM_ID = "arg.feeditemId";
- private static final String ARG_SAVE_STATE = "arg.saveState";
-
- private WebView webvDescription;
- private Playable media;
-
- private FeedItem item;
-
- private AsyncTask<Void, Void, Void> webViewLoader;
-
- private String shownotes;
-
- /** URL that was selected via long-press. */
- private String selectedURL;
-
- /**
- * True if Fragment should save its state (e.g. scrolling position) in a
- * shared preference.
- */
- private boolean saveState;
-
- public static ItemDescriptionFragment newInstance(Playable media,
- boolean saveState) {
- ItemDescriptionFragment f = new ItemDescriptionFragment();
- Bundle args = new Bundle();
- args.putParcelable(ARG_PLAYABLE, media);
- args.putBoolean(ARG_SAVE_STATE, saveState);
- f.setArguments(args);
- return f;
- }
-
- public static ItemDescriptionFragment newInstance(FeedItem item,
- boolean saveState) {
- ItemDescriptionFragment f = new ItemDescriptionFragment();
- Bundle args = new Bundle();
- args.putLong(ARG_FEED_ID, item.getFeed().getId());
- args.putLong(ARG_FEED_ITEM_ID, item.getId());
- args.putBoolean(ARG_SAVE_STATE, saveState);
- f.setArguments(args);
- return f;
- }
-
- @SuppressLint("NewApi")
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating view");
- webvDescription = new WebView(getActivity());
- if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
- if (Build.VERSION.SDK_INT >= 11
- && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
- webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
- }
- webvDescription.setBackgroundColor(getResources().getColor(
- R.color.black));
- }
- webvDescription.getSettings().setUseWideViewPort(false);
- webvDescription.getSettings().setLayoutAlgorithm(
- LayoutAlgorithm.NARROW_COLUMNS);
- webvDescription.getSettings().setLoadWithOverviewMode(true);
- webvDescription.setOnLongClickListener(webViewLongClickListener);
- webvDescription.setWebViewClient(new WebViewClient() {
-
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
- startActivity(intent);
- return true;
- }
-
- @Override
- public void onPageFinished(WebView view, String url) {
- super.onPageFinished(view, url);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Page finished");
- // Restoring the scroll position might not always work
- view.postDelayed(new Runnable() {
-
- @Override
- public void run() {
- restoreFromPreference();
- }
-
- }, 50);
- }
-
- });
- registerForContextMenu(webvDescription);
- return webvDescription;
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Fragment attached");
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Fragment detached");
- if (webViewLoader != null) {
- webViewLoader.cancel(true);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Fragment destroyed");
- if (webViewLoader != null) {
- webViewLoader.cancel(true);
- }
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating fragment");
- Bundle args = getArguments();
- saveState = args.getBoolean(ARG_SAVE_STATE, false);
- if (args.containsKey(ARG_PLAYABLE)) {
- media = args.getParcelable(ARG_PLAYABLE);
- } else if (args.containsKey(ARG_FEED_ID)
- && args.containsKey(ARG_FEED_ITEM_ID)) {
- long feedId = args.getLong(ARG_FEED_ID);
- long itemId = args.getLong(ARG_FEED_ITEM_ID);
- FeedItem f = FeedManager.getInstance().getFeedItem(itemId, feedId);
- if (f != null) {
- item = f;
- }
- }
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- if (media != null) {
- media.loadShownotes(new Playable.ShownoteLoaderCallback() {
-
- @Override
- public void onShownotesLoaded(String shownotes) {
- ItemDescriptionFragment.this.shownotes = shownotes;
- if (ItemDescriptionFragment.this.shownotes != null) {
- startLoader();
- }
- }
- });
- } else if (item != null) {
- if (item.getDescription() == null
- || item.getContentEncoded() == null) {
- FeedManager.getInstance().loadExtraInformationOfItem(
- PodcastApp.getInstance(), item,
- new FeedManager.TaskCallback<String[]>() {
- @Override
- public void onCompletion(String[] result) {
- if (result[1] != null) {
- shownotes = result[1];
- } else {
- shownotes = result[0];
- }
- if (shownotes != null) {
- startLoader();
- }
-
- }
- });
- } else {
- shownotes = item.getContentEncoded();
- startLoader();
- }
- } else {
- Log.e(TAG, "Error in onViewCreated: Item and media were null");
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- }
-
- @SuppressLint("NewApi")
- private void startLoader() {
- webViewLoader = createLoader();
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- webViewLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- webViewLoader.execute();
- }
- }
-
- /**
- * Return the CSS style of the Webview.
- *
- * @param textColor
- * the default color to use for the text in the webview. This
- * value is inserted directly into the CSS String.
- * */
- private String applyWebviewStyle(String textColor, String data) {
- final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>";
- final int pageMargin = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, 8, getResources()
- .getDisplayMetrics());
- return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin,
- pageMargin, pageMargin, pageMargin, data);
- }
-
- private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() {
-
- @Override
- public boolean onLongClick(View v) {
- WebView.HitTestResult r = webvDescription.getHitTestResult();
- if (r != null
- && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Link of webview was long-pressed. Extra: "
- + r.getExtra());
- selectedURL = r.getExtra();
- webvDescription.showContextMenu();
- return true;
- }
- selectedURL = null;
- return false;
- }
- };
-
- @SuppressWarnings("deprecation")
- @SuppressLint("NewApi")
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- boolean handled = selectedURL != null;
- if (selectedURL != null) {
- switch (item.getItemId()) {
- case R.id.open_in_browser_item:
- Uri uri = Uri.parse(selectedURL);
- getActivity()
- .startActivity(new Intent(Intent.ACTION_VIEW, uri));
- break;
- case R.id.share_url_item:
- ShareUtils.shareLink(getActivity(), selectedURL);
- break;
- case R.id.copy_url_item:
- if (android.os.Build.VERSION.SDK_INT >= 11) {
- ClipData clipData = ClipData.newPlainText(selectedURL,
- selectedURL);
- android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- cm.setPrimaryClip(clipData);
- } else {
- android.text.ClipboardManager cm = (android.text.ClipboardManager) getActivity()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- cm.setText(selectedURL);
- }
- Toast t = Toast.makeText(getActivity(),
- R.string.copied_url_msg, Toast.LENGTH_SHORT);
- t.show();
- break;
- default:
- handled = false;
- break;
-
- }
- selectedURL = null;
- }
- return handled;
-
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- if (selectedURL != null) {
- super.onCreateContextMenu(menu, v, menuInfo);
- menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE,
- R.string.open_in_browser_label);
- menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE,
- R.string.copy_url_label);
- menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE,
- R.string.share_url_label);
- menu.setHeaderTitle(selectedURL);
- }
- }
-
- private AsyncTask<Void, Void, Void> createLoader() {
- return new AsyncTask<Void, Void, Void>() {
- @Override
- protected void onCancelled() {
- super.onCancelled();
- if (getSherlockActivity() != null) {
- getSherlockActivity()
- .setSupportProgressBarIndeterminateVisibility(false);
- }
- webViewLoader = null;
- }
-
- String data;
-
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- // /webvDescription.loadData(url, "text/html", "utf-8");
- webvDescription.loadDataWithBaseURL(null, data, "text/html",
- "utf-8", "about:blank");
- if (getSherlockActivity() != null) {
- getSherlockActivity()
- .setSupportProgressBarIndeterminateVisibility(false);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Webview loaded");
- webViewLoader = null;
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- if (getSherlockActivity() != null) {
- getSherlockActivity()
- .setSupportProgressBarIndeterminateVisibility(true);
- }
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Loading Webview");
- data = "";
- data = StringEscapeUtils.unescapeHtml4(shownotes);
- Activity activity = getActivity();
- if (activity != null) {
- TypedArray res = getActivity()
- .getTheme()
- .obtainStyledAttributes(
- new int[] { android.R.attr.textColorPrimary });
- int colorResource = res.getColor(0, 0);
- String colorString = String.format("#%06X",
- 0xFFFFFF & colorResource);
- Log.i(TAG, "text color: " + colorString);
- res.recycle();
- data = applyWebviewStyle(colorString, data);
- } else {
- cancel(true);
- }
- return null;
- }
-
- };
- }
-
- @Override
- public void onPause() {
- super.onPause();
- savePreference();
- }
-
- private void savePreference() {
- if (saveState) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Saving preferences");
- SharedPreferences prefs = getActivity().getSharedPreferences(PREF,
- Activity.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- if (media != null && webvDescription != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Saving scroll position: "
- + webvDescription.getScrollY());
- editor.putInt(PREF_SCROLL_Y, webvDescription.getScrollY());
- editor.putString(PREF_PLAYABLE_ID, media.getIdentifier()
- .toString());
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "savePreferences was called while media or webview was null");
- editor.putInt(PREF_SCROLL_Y, -1);
- editor.putString(PREF_PLAYABLE_ID, "");
- }
- editor.commit();
- }
- }
-
- private boolean restoreFromPreference() {
- if (saveState) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Restoring from preferences");
- Activity activity = getActivity();
- if (activity != null) {
- SharedPreferences prefs = activity.getSharedPreferences(
- PREF, Activity.MODE_PRIVATE);
- String id = prefs.getString(PREF_PLAYABLE_ID, "");
- int scrollY = prefs.getInt(PREF_SCROLL_Y, -1);
- if (scrollY != -1 && media != null
- && id.equals(media.getIdentifier().toString())
- && webvDescription != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Restored scroll Position: " + scrollY);
- webvDescription.scrollTo(webvDescription.getScrollX(),
- scrollY);
- return true;
- }
- }
- }
- return false;
- }
+public class ItemDescriptionFragment extends Fragment {
+
+ private static final String TAG = "ItemDescriptionFragment";
+
+ private static final String PREF = "ItemDescriptionFragmentPrefs";
+ private static final String PREF_SCROLL_Y = "prefScrollY";
+ private static final String PREF_PLAYABLE_ID = "prefPlayableId";
+
+ private static final String ARG_PLAYABLE = "arg.playable";
+ private static final String ARG_FEEDITEM_ID = "arg.feeditem";
+
+ private static final String ARG_SAVE_STATE = "arg.saveState";
+
+ private WebView webvDescription;
+
+ private ShownotesProvider shownotesProvider;
+ private Playable media;
+
+
+ private AsyncTask<Void, Void, Void> webViewLoader;
+
+ /**
+ * URL that was selected via long-press.
+ */
+ private String selectedURL;
+
+ /**
+ * True if Fragment should save its state (e.g. scrolling position) in a
+ * shared preference.
+ */
+ private boolean saveState;
+
+ public static ItemDescriptionFragment newInstance(Playable media,
+ boolean saveState) {
+ ItemDescriptionFragment f = new ItemDescriptionFragment();
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_PLAYABLE, media);
+ args.putBoolean(ARG_SAVE_STATE, saveState);
+ f.setArguments(args);
+ return f;
+ }
+
+ public static ItemDescriptionFragment newInstance(FeedItem item, boolean saveState) {
+ ItemDescriptionFragment f = new ItemDescriptionFragment();
+ Bundle args = new Bundle();
+ args.putLong(ARG_FEEDITEM_ID, item.getId());
+ args.putBoolean(ARG_SAVE_STATE, saveState);
+ f.setArguments(args);
+ return f;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating view");
+ webvDescription = new WebView(getActivity());
+ if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
+ if (Build.VERSION.SDK_INT >= 11
+ && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+ webvDescription.setBackgroundColor(getResources().getColor(
+ R.color.black));
+ }
+ webvDescription.getSettings().setUseWideViewPort(false);
+ webvDescription.getSettings().setLayoutAlgorithm(
+ LayoutAlgorithm.NARROW_COLUMNS);
+ webvDescription.getSettings().setLoadWithOverviewMode(true);
+ webvDescription.setOnLongClickListener(webViewLongClickListener);
+ webvDescription.setWebViewClient(new WebViewClient() {
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Page finished");
+ // Restoring the scroll position might not always work
+ view.postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ restoreFromPreference();
+ }
+
+ }, 50);
+ }
+
+ });
+
+ registerForContextMenu(webvDescription);
+ return webvDescription;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Fragment attached");
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Fragment detached");
+ if (webViewLoader != null) {
+ webViewLoader.cancel(true);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Fragment destroyed");
+ if (webViewLoader != null) {
+ webViewLoader.cancel(true);
+ }
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating fragment");
+ Bundle args = getArguments();
+ saveState = args.getBoolean(ARG_SAVE_STATE, false);
+
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ Bundle args = getArguments();
+ if (args.containsKey(ARG_PLAYABLE)) {
+ media = args.getParcelable(ARG_PLAYABLE);
+ shownotesProvider = media;
+ startLoader();
+ } else if (args.containsKey(ARG_FEEDITEM_ID)) {
+ AsyncTask<Void, Void, FeedItem> itemLoadTask = new AsyncTask<Void, Void, FeedItem>() {
+
+ @Override
+ protected FeedItem doInBackground(Void... voids) {
+ return DBReader.getFeedItem(getActivity(), getArguments().getLong(ARG_FEEDITEM_ID));
+ }
+
+ @Override
+ protected void onPostExecute(FeedItem feedItem) {
+ super.onPostExecute(feedItem);
+ shownotesProvider = feedItem;
+ startLoader();
+ }
+ };
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ itemLoadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ itemLoadTask.execute();
+ }
+ }
+
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @SuppressLint("NewApi")
+ private void startLoader() {
+ webViewLoader = createLoader();
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ webViewLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ webViewLoader.execute();
+ }
+ }
+
+ /**
+ * Return the CSS style of the Webview.
+ *
+ * @param textColor the default color to use for the text in the webview. This
+ * value is inserted directly into the CSS String.
+ */
+ private String applyWebviewStyle(String textColor, String data) {
+ final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>";
+ final int pageMargin = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, 8, getResources()
+ .getDisplayMetrics());
+ return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin,
+ pageMargin, pageMargin, pageMargin, data);
+ }
+
+ private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ WebView.HitTestResult r = webvDescription.getHitTestResult();
+ if (r != null
+ && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Link of webview was long-pressed. Extra: "
+ + r.getExtra());
+ selectedURL = r.getExtra();
+ webvDescription.showContextMenu();
+ return true;
+ }
+ selectedURL = null;
+ return false;
+ }
+ };
+
+ @SuppressWarnings("deprecation")
+ @SuppressLint("NewApi")
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ boolean handled = selectedURL != null;
+ if (selectedURL != null) {
+ switch (item.getItemId()) {
+ case R.id.open_in_browser_item:
+ Uri uri = Uri.parse(selectedURL);
+ getActivity()
+ .startActivity(new Intent(Intent.ACTION_VIEW, uri));
+ break;
+ case R.id.share_url_item:
+ ShareUtils.shareLink(getActivity(), selectedURL);
+ break;
+ case R.id.copy_url_item:
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ ClipData clipData = ClipData.newPlainText(selectedURL,
+ selectedURL);
+ android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(clipData);
+ } else {
+ android.text.ClipboardManager cm = (android.text.ClipboardManager) getActivity()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setText(selectedURL);
+ }
+ Toast t = Toast.makeText(getActivity(),
+ R.string.copied_url_msg, Toast.LENGTH_SHORT);
+ t.show();
+ break;
+ default:
+ handled = false;
+ break;
+
+ }
+ selectedURL = null;
+ }
+ return handled;
+
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ if (selectedURL != null) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE,
+ R.string.open_in_browser_label);
+ menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE,
+ R.string.copy_url_label);
+ menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE,
+ R.string.share_url_label);
+ menu.setHeaderTitle(selectedURL);
+ }
+ }
+
+ private AsyncTask<Void, Void, Void> createLoader() {
+ return new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected void onCancelled() {
+ super.onCancelled();
+ if (getActivity() != null) {
+ ((ActionBarActivity) getActivity())
+ .setSupportProgressBarIndeterminateVisibility(false);
+ }
+ webViewLoader = null;
+ }
+
+ String data;
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ // /webvDescription.loadData(url, "text/html", "utf-8");
+ webvDescription.loadDataWithBaseURL(null, data, "text/html",
+ "utf-8", "about:blank");
+ if (getActivity() != null) {
+ ((ActionBarActivity) getActivity())
+ .setSupportProgressBarIndeterminateVisibility(false);
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Webview loaded");
+ webViewLoader = null;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ if (getActivity() != null) {
+ ((ActionBarActivity) getActivity())
+ .setSupportProgressBarIndeterminateVisibility(false);
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading Webview");
+ try {
+ Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes();
+ final String shownotes = shownotesLoadTask.call();
+
+ data = StringEscapeUtils.unescapeHtml4(shownotes);
+ Activity activity = getActivity();
+ if (activity != null) {
+ TypedArray res = activity
+ .getTheme()
+ .obtainStyledAttributes(
+ new int[]{android.R.attr.textColorPrimary});
+ int colorResource = res.getColor(0, 0);
+ String colorString = String.format("#%06X",
+ 0xFFFFFF & colorResource);
+ Log.i(TAG, "text color: " + colorString);
+ res.recycle();
+ data = applyWebviewStyle(colorString, data);
+ } else {
+ cancel(true);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ };
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ savePreference();
+ }
+
+ private void savePreference() {
+ if (saveState) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Saving preferences");
+ SharedPreferences prefs = getActivity().getSharedPreferences(PREF,
+ Activity.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ if (media != null && webvDescription != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Saving scroll position: "
+ + webvDescription.getScrollY());
+ editor.putInt(PREF_SCROLL_Y, webvDescription.getScrollY());
+ editor.putString(PREF_PLAYABLE_ID, media.getIdentifier()
+ .toString());
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "savePreferences was called while media or webview was null");
+ editor.putInt(PREF_SCROLL_Y, -1);
+ editor.putString(PREF_PLAYABLE_ID, "");
+ }
+ editor.commit();
+ }
+ }
+
+ private boolean restoreFromPreference() {
+ if (saveState) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Restoring from preferences");
+ Activity activity = getActivity();
+ if (activity != null) {
+ SharedPreferences prefs = activity.getSharedPreferences(
+ PREF, Activity.MODE_PRIVATE);
+ String id = prefs.getString(PREF_PLAYABLE_ID, "");
+ int scrollY = prefs.getInt(PREF_SCROLL_Y, -1);
+ if (scrollY != -1 && media != null
+ && id.equals(media.getIdentifier().toString())
+ && webvDescription != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Restored scroll Position: " + scrollY);
+ webvDescription.scrollTo(webvDescription.getScrollX(),
+ scrollY);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
index 6265694f6..282bb4d5c 100644
--- a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
@@ -1,8 +1,12 @@
package de.danoeh.antennapod.fragment;
import android.annotation.SuppressLint;
+import android.content.Context;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@@ -12,27 +16,30 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
-import com.actionbarsherlock.app.SherlockListFragment;
+import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.ItemviewActivity;
import de.danoeh.antennapod.adapter.ActionButtonCallback;
-import de.danoeh.antennapod.adapter.DefaultFeedItemlistAdapter;
import de.danoeh.antennapod.adapter.InternalFeedItemlistAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.download.DownloadService;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
+import java.util.Iterator;
+import java.util.List;
+
/** Displays a list of FeedItems. */
@SuppressLint("ValidFragment")
-public class ItemlistFragment extends SherlockListFragment {
+public class ItemlistFragment extends ListFragment {
private static final String TAG = "ItemlistFragment";
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
@@ -43,12 +50,9 @@ public class ItemlistFragment extends SherlockListFragment {
public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem";
public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
protected InternalFeedItemlistAdapter fila;
- protected FeedManager manager = FeedManager.getInstance();
- protected DownloadRequester requester = DownloadRequester.getInstance();
-
- private DefaultFeedItemlistAdapter.ItemAccess itemAccess;
private Feed feed;
+ protected List<Long> queue;
protected FeedItem selectedItem = null;
protected boolean contextMenuClosed = true;
@@ -56,10 +60,10 @@ public class ItemlistFragment extends SherlockListFragment {
/** Argument for FeeditemlistAdapter */
protected boolean showFeedtitle;
- public ItemlistFragment(DefaultFeedItemlistAdapter.ItemAccess itemAccess,
- boolean showFeedtitle) {
+ private AsyncTask<Long, Void, Feed> currentLoadTask;
+
+ public ItemlistFragment(boolean showFeedtitle) {
super();
- this.itemAccess = itemAccess;
this.showFeedtitle = showFeedtitle;
}
@@ -83,51 +87,117 @@ public class ItemlistFragment extends SherlockListFragment {
return i;
}
+ private InternalFeedItemlistAdapter.ItemAccess itemAccessRef;
+ protected InternalFeedItemlistAdapter.ItemAccess itemAccess() {
+ if (itemAccessRef == null) {
+ itemAccessRef = new InternalFeedItemlistAdapter.ItemAccess() {
+
+ @Override
+ public FeedItem getItem(int position) {
+ return (feed != null) ? feed.getItemAtIndex(true, position) : null;
+ }
+
+ @Override
+ public int getCount() {
+ return (feed != null) ? feed.getNumOfItems(true) : 0;
+ }
+
+ @Override
+ public boolean isInQueue(FeedItem item) {
+ return (queue != null) && queue.contains(item.getId());
+ }
+ };
+ }
+ return itemAccessRef;
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.feeditemlist, container, false);
}
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (itemAccess == null) {
- long feedId = getArguments().getLong(ARGUMENT_FEED_ID);
- final Feed feed = FeedManager.getInstance().getFeed(feedId);
- this.feed = feed;
- itemAccess = new DefaultFeedItemlistAdapter.ItemAccess() {
-
- @Override
- public FeedItem getItem(int position) {
- return feed.getItemAtIndex(true, position);
- }
-
- @Override
- public int getCount() {
- return feed.getNumOfItems(true);
- }
- };
- }
- }
+ @Override
+ public void onStart() {
+ super.onStart();
+ EventDistributor.getInstance().register(contentUpdate);
+ loadData();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ EventDistributor.getInstance().unregister(contentUpdate);
+ if (currentLoadTask != null) {
+ currentLoadTask.cancel(true);
+ }
+ }
+
+ protected void loadData() {
+ final long feedId;
+ if (feed == null) {
+ feedId = getArguments().getLong(ARGUMENT_FEED_ID);
+ } else {
+ feedId = feed.getId();
+ }
+ AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>(){
+ private volatile List<Long> queueRef;
+
+ @Override
+ protected Feed doInBackground(Long... longs) {
+ Context context = ItemlistFragment.this.getActivity();
+ if (context != null) {
+ Feed result = DBReader.getFeed(context, longs[0]);
+ if (result != null) {
+ result.setItems(DBReader.getFeedItemList(context, result));
+ queueRef = DBReader.getQueueIDList(context);
+ return result;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Feed result) {
+ super.onPostExecute(result);
+ if (result != null && result.getItems() != null) {
+ feed = result;
+ if (queueRef != null) {
+ queue = queueRef;
+ } else {
+ Log.e(TAG, "Could not load queue");
+ }
+ setEmptyViewIfListIsEmpty();
+ if (fila != null) {
+ fila.notifyDataSetChanged();
+ }
+ } else {
+ if (result == null) {
+ Log.e(TAG, "Could not load feed with id " + feedId);
+ } else if (result.getItems() == null) {
+ Log.e(TAG, "Could not load feed items");
+ }
+ }
+ }
+ };
+ currentLoadTask = loadTask;
+ loadTask.execute(feedId);
+ }
+
+ private void setEmptyViewIfListIsEmpty() {
+ if (getListView() != null && feed != null && feed.getItems() != null) {
+ if (feed.getItems().isEmpty()) {
+ ((TextView) getActivity().findViewById(android.R.id.empty)).setText(R.string.no_items_label);
+ }
+ }
+ }
protected InternalFeedItemlistAdapter createListAdapter() {
- return new InternalFeedItemlistAdapter(getActivity(), itemAccess,
+ return new InternalFeedItemlistAdapter(getActivity(), itemAccess(),
adapterCallback, showFeedtitle);
}
@Override
- public void onPause() {
- super.onPause();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- EventDistributor.getInstance().unregister(contentUpdate);
- }
-
- @Override
public void onResume() {
super.onResume();
getActivity().runOnUiThread(new Runnable() {
@@ -138,7 +208,6 @@ public class ItemlistFragment extends SherlockListFragment {
}
});
updateProgressBarVisibility();
- EventDistributor.getInstance().register(contentUpdate);
}
@Override
@@ -162,7 +231,9 @@ public class ItemlistFragment extends SherlockListFragment {
if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) {
updateProgressBarVisibility();
} else {
- fila.notifyDataSetChanged();
+ if (feed != null) {
+ loadData();
+ }
updateProgressBarVisibility();
}
}
@@ -173,13 +244,13 @@ public class ItemlistFragment extends SherlockListFragment {
if (feed != null) {
if (DownloadService.isRunning
&& DownloadRequester.getInstance().isDownloadingFile(feed)) {
- getSherlockActivity()
+ ((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(true);
} else {
- getSherlockActivity()
+ ((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(false);
}
- getSherlockActivity().supportInvalidateOptionsMenu();
+ getActivity().supportInvalidateOptionsMenu();
}
}
@@ -218,13 +289,13 @@ public class ItemlistFragment extends SherlockListFragment {
menu.setHeaderTitle(selectedItem.getTitle());
FeedItemMenuHandler.onPrepareMenu(
- new FeedItemMenuHandler.MenuInterface() {
+ new FeedItemMenuHandler.MenuInterface() {
- @Override
- public void setItemVisibility(int id, boolean visible) {
- menu.findItem(id).setVisible(visible);
- }
- }, selectedItem, false);
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ menu.findItem(id).setVisible(visible);
+ }
+ }, selectedItem, false, QueueAccess.IDListAccess(queue));
}
}
@@ -237,7 +308,7 @@ public class ItemlistFragment extends SherlockListFragment {
try {
handled = FeedItemMenuHandler.onMenuItemClicked(
- getSherlockActivity(), item.getItemId(), selectedItem);
+ getActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
diff --git a/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java b/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java
index c378c0acd..c6901ad17 100644
--- a/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java
+++ b/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java
@@ -10,6 +10,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -17,8 +18,6 @@ import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;
-import com.actionbarsherlock.app.SherlockListFragment;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MiroGuideChannelViewActivity;
@@ -33,7 +32,7 @@ import de.danoeh.antennapod.miroguide.model.MiroGuideChannel;
* entries will be loaded until all entries have been loaded or the maximum
* number of channels has been reached.
* */
-public class MiroGuideChannellistFragment extends SherlockListFragment {
+public class MiroGuideChannellistFragment extends ListFragment {
private static final String TAG = "MiroGuideChannellistFragment";
private static final String ARG_FILTER = "filter";
diff --git a/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
index b471d5303..d20cb63c4 100644
--- a/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
+++ b/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
@@ -1,33 +1,53 @@
package de.danoeh.antennapod.fragment;
+import android.content.Context;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.adapter.DefaultFeedItemlistAdapter;
+import de.danoeh.antennapod.adapter.InternalFeedItemlistAdapter;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBReader;
+
+import java.util.Iterator;
+import java.util.List;
public class PlaybackHistoryFragment extends ItemlistFragment {
private static final String TAG = "PlaybackHistoryFragment";
+ private List<FeedItem> playbackHistory;
+
public PlaybackHistoryFragment() {
- super(new DefaultFeedItemlistAdapter.ItemAccess() {
+ super(true);
+ }
- @Override
- public FeedItem getItem(int position) {
- return FeedManager.getInstance().getPlaybackHistoryItemIndex(
- position);
- }
+ InternalFeedItemlistAdapter.ItemAccess itemAccessRef;
+ @Override
+ protected InternalFeedItemlistAdapter.ItemAccess itemAccess() {
+ if (itemAccessRef == null) {
+ itemAccessRef = new InternalFeedItemlistAdapter.ItemAccess() {
- @Override
- public int getCount() {
- return FeedManager.getInstance().getPlaybackHistorySize();
- }
- }, true);
- }
+ @Override
+ public FeedItem getItem(int position) {
+ return (playbackHistory != null) ? playbackHistory.get(position) : null;
+ }
- @Override
+ @Override
+ public int getCount() {
+ return (playbackHistory != null) ? playbackHistory.size() : 0;
+ }
+
+ @Override
+ public boolean isInQueue(FeedItem item) {
+ return (queue != null) ? queue.contains(item.getId()) : false;
+ }
+ };
+ }
+ return itemAccessRef;
+ }
+
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventDistributor.getInstance().register(historyUpdate);
@@ -46,10 +66,48 @@ public class PlaybackHistoryFragment extends ItemlistFragment {
if ((EventDistributor.PLAYBACK_HISTORY_UPDATE & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received content update");
- fila.notifyDataSetChanged();
+ loadData();
}
}
};
+ @Override
+ protected void loadData() {
+ AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() {
+ private volatile List<FeedItem> phRef;
+ private volatile List<Long> queueRef;
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ Context context = PlaybackHistoryFragment.this.getActivity();
+ if (context != null) {
+ queueRef = DBReader.getQueueIDList(context);
+ phRef = DBReader.getPlaybackHistory(context);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ if (queueRef != null && phRef != null) {
+ queue = queueRef;
+ playbackHistory = phRef;
+ Log.i(TAG, "Number of items in playback history: " + playbackHistory.size());
+ if (fila != null) {
+ fila.notifyDataSetChanged();
+ }
+ } else {
+ if (queueRef == null) {
+ Log.e(TAG, "Could not load queue");
+ }
+ if (phRef == null) {
+ Log.e(TAG, "Could not load playback history");
+ }
+ }
+ }
+ };
+ loadTask.execute();
+ }
}
diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
index 99bef4bd8..ee1a3ea21 100644
--- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
+++ b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
@@ -16,6 +16,8 @@ import org.json.JSONObject;
import android.net.Uri;
+import de.danoeh.antennapod.util.LangUtils;
+
/** Executes HTTP requests and returns the results. */
public class MiroGuideConnector {
private HttpClient httpClient;
@@ -73,12 +75,14 @@ public class MiroGuideConnector {
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
if (entity != null) {
- InputStream in = entity.getContent();
-
BufferedReader reader = new BufferedReader(
- new InputStreamReader(in));
- result = reader.readLine();
- in.close();
+ new InputStreamReader(entity.getContent(),
+ LangUtils.UTF_8));
+ try {
+ result = reader.readLine();
+ } finally {
+ reader.close();
+ }
}
} else {
throw new MiroGuideException(response.getStatusLine()
diff --git a/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java b/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java
index 89a2250df..cb5b15c56 100644
--- a/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java
+++ b/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java
@@ -12,7 +12,7 @@ public class MiroGuideItem {
super();
this.name = name;
this.description = description;
- this.date = date;
+ this.date = (Date) date.clone();
this.url = url;
}
@@ -30,7 +30,7 @@ public class MiroGuideItem {
}
public Date getDate() {
- return date;
+ return (Date) date.clone();
}
public String getUrl() {
diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java
index f2f35f41d..0d07a7178 100644
--- a/src/de/danoeh/antennapod/preferences/UserPreferences.java
+++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java
@@ -2,9 +2,13 @@ package de.danoeh.antennapod.preferences;
import java.io.File;
import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -41,11 +45,13 @@ public class UserPreferences implements
public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
+ private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
+ private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
private static UserPreferences instance;
- private Context context;
+ private final Context context;
// Preferences
private boolean pauseOnHeadsetDisconnect;
@@ -60,6 +66,8 @@ public class UserPreferences implements
private boolean enableAutodownloadWifiFilter;
private String[] autodownloadSelectedNetworks;
private int episodeCacheSize;
+ private String playbackSpeed;
+ private String[] playbackSpeedArray;
private UserPreferences(Context context) {
this.context = context;
@@ -83,6 +91,7 @@ public class UserPreferences implements
createNoMediaFile();
PreferenceManager.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(instance);
+
}
private void loadPreferences() {
@@ -108,6 +117,9 @@ public class UserPreferences implements
episodeCacheSize = readEpisodeCacheSize(sp.getString(
PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
+ playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
+ playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
+ PREF_PLAYBACK_SPEED_ARRAY, null));
}
private int readThemeValue(String valueFromPrefs) {
@@ -135,6 +147,36 @@ public class UserPreferences implements
}
}
+ private String[] readPlaybackSpeedArray(String valueFromPrefs) {
+ String[] selectedSpeeds = null;
+ // If this preference hasn't been set yet, return the default options
+ if (valueFromPrefs == null) {
+ String[] allSpeeds = context.getResources().getStringArray(
+ R.array.playback_speed_values);
+ List<String> speedList = new LinkedList<String>();
+ for (String speedStr : allSpeeds) {
+ float speed = Float.parseFloat(speedStr);
+ if (speed < 2.0001 && speed * 10 % 1 == 0) {
+ speedList.add(speedStr);
+ }
+ }
+ selectedSpeeds = speedList.toArray(new String[speedList.size()]);
+ } else {
+ try {
+ JSONArray jsonArray = new JSONArray(valueFromPrefs);
+ selectedSpeeds = new String[jsonArray.length()];
+ for (int i = 0; i < jsonArray.length(); i++) {
+ selectedSpeeds[i] = jsonArray.getString(i);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG,
+ "Got JSON error when trying to get speeds from JSONArray");
+ e.printStackTrace();
+ }
+ }
+ return selectedSpeeds;
+ }
+
private static void instanceAvailable() {
if (instance == null) {
throw new IllegalStateException(
@@ -169,7 +211,8 @@ public class UserPreferences implements
public static boolean isDisplayOnlyEpisodes() {
instanceAvailable();
- return instance.displayOnlyEpisodes;
+ //return instance.displayOnlyEpisodes;
+ return false;
}
public static boolean isAutoDelete() {
@@ -196,6 +239,16 @@ public class UserPreferences implements
return EPISODE_CACHE_SIZE_UNLIMITED;
}
+ public static String getPlaybackSpeed() {
+ instanceAvailable();
+ return instance.playbackSpeed;
+ }
+
+ public static String[] getPlaybackSpeedArray() {
+ instanceAvailable();
+ return instance.playbackSpeedArray;
+ }
+
/**
* Returns the capacity of the episode cache. This method will return the
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
@@ -250,7 +303,27 @@ public class UserPreferences implements
PREF_EPISODE_CACHE_SIZE, "20"));
} else if (key.equals(PREF_ENABLE_AUTODL)) {
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
+ } else if (key.equals(PREF_PLAYBACK_SPEED)) {
+ playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
+ } else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) {
+ playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
+ PREF_PLAYBACK_SPEED_ARRAY, null));
+ }
+ }
+
+ public static void setPlaybackSpeed(String speed) {
+ PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
+ .putString(PREF_PLAYBACK_SPEED, speed).apply();
+ }
+
+ public static void setPlaybackSpeedArray(String[] speeds) {
+ JSONArray jsonArray = new JSONArray();
+ for (String speed : speeds) {
+ jsonArray.put(speed);
}
+ PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
+ .putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString())
+ .apply();
}
public static void setAutodownloadSelectedNetworks(Context context,
diff --git a/src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java b/src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java
index b58527130..aebe5a681 100644
--- a/src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java
+++ b/src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java
@@ -7,7 +7,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.NetworkUtils;
@@ -27,7 +27,7 @@ public class ConnectivityActionReceiver extends BroadcastReceiver {
new Thread() {
@Override
public void run() {
- FeedManager.getInstance()
+ DBTasks
.autodownloadUndownloadedItems(context);
}
}.start();
diff --git a/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java b/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java
index 821ade4b0..fdbaa97f0 100644
--- a/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java
+++ b/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java
@@ -7,8 +7,8 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBTasks;
/** Refreshes all feeds when it receives an intent */
public class FeedUpdateReceiver extends BroadcastReceiver {
@@ -22,7 +22,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
Log.d(TAG, "Received intent");
boolean mobileUpdate = UserPreferences.isAllowMobileUpdate();
if (mobileUpdate || connectedToWifi(context)) {
- FeedManager.getInstance().refreshExpiredFeeds(context);
+ DBTasks.refreshExpiredFeeds(context);
} else {
if (AppConfig.DEBUG)
Log.d(TAG,
diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java
index 409ac6b48..025bf7dc4 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,366 +35,434 @@ 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.DuckType;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
+import de.danoeh.antennapod.util.playback.AudioPlayer;
+import de.danoeh.antennapod.util.playback.IPlayer;
import de.danoeh.antennapod.util.playback.Playable;
import de.danoeh.antennapod.util.playback.Playable.PlayableException;
+import de.danoeh.antennapod.util.playback.VideoPlayer;
+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;
+ /**
+ * 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;
+
+ /**
+ * Playback speed has changed
+ * */
+ public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8;
+
+ /**
+ * 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 volatile IPlayer 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);
+ private AudioManager audioManager;
+ private ComponentName mediaButtonReceiver;
+
+ private volatile 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();
+
+ 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 IPlayer createMediaPlayer() {
+ IPlayer player;
+ if (media == null || media.getMediaType() == MediaType.VIDEO) {
+ player = new VideoPlayer();
+ } else {
+ player = new AudioPlayer(this);
+ }
+ return createMediaPlayer(player);
+ }
+
+ private IPlayer createMediaPlayer(IPlayer mp) {
+ if (mp != null && media != null) {
+ if (media.getMediaType() == MediaType.AUDIO) {
+ ((AudioPlayer) mp).setOnPreparedListener(audioPreparedListener);
+ ((AudioPlayer) mp)
+ .setOnCompletionListener(audioCompletionListener);
+ ((AudioPlayer) mp)
+ .setOnSeekCompleteListener(audioSeekCompleteListener);
+ ((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
+ ((AudioPlayer) mp)
+ .setOnBufferingUpdateListener(audioBufferingUpdateListener);
+ ((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
} else {
- return new Intent(context, AudioplayerActivity.class);
+ ((VideoPlayer) mp).setOnPreparedListener(videoPreparedListener);
+ ((VideoPlayer) mp)
+ .setOnCompletionListener(videoCompletionListener);
+ ((VideoPlayer) mp)
+ .setOnSeekCompleteListener(videoSeekCompleteListener);
+ ((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
+ ((VideoPlayer) mp)
+ .setOnBufferingUpdateListener(videoBufferingUpdateListener);
+ ((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
}
}
- }
-
- /**
- * 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;
- }
+ @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) {
@@ -432,166 +495,181 @@ public class PlaybackService extends Service {
pause(true, true);
}
break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ seekDelta(PlaybackController.DEFAULT_SEEK_DELTA);
+ break;
}
- }
-
- /**
- * 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();
- }
+ case KeyEvent.KEYCODE_MEDIA_REWIND: {
+ seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA);
+ break;
+ }
}
-
}
- /** 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();
+ player = createMediaPlayer();
+ 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
@@ -613,181 +691,245 @@ public class PlaybackService extends Service {
}
}
- private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(MediaPlayer mp) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Resource prepared");
- mp.seekTo(media.getPosition());
- if (media.getDuration() == 0) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting duration of media");
- media.setDuration(mp.getDuration());
- }
- setStatus(PlayerStatus.PREPARED);
- if (chapterLoader != null) {
- chapterLoader.interrupt();
- }
- chapterLoader = new Thread() {
- @Override
- public void run() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Chapter loader started");
- if (media != null && media.getChapters() == null) {
- media.loadChapterMarks();
- if (!isInterrupted() && media.getChapters() != null) {
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
- 0);
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Chapter loader stopped");
- }
- };
- chapterLoader.start();
-
- if (startWhenPrepared) {
- play();
- }
- }
- };
-
- private MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() {
-
- @Override
- public void onSeekComplete(MediaPlayer mp) {
- if (status == PlayerStatus.SEEKING) {
- setStatus(statusBeforeSeek);
- }
-
- }
- };
-
- private MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() {
-
- @Override
- public boolean onInfo(MediaPlayer mp, int what, int extra) {
- switch (what) {
- case MediaPlayer.MEDIA_INFO_BUFFERING_START:
- sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0);
- return true;
- case MediaPlayer.MEDIA_INFO_BUFFERING_END:
- sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0);
- return true;
- default:
- return false;
- }
- }
- };
-
- private MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() {
- private static final String TAG = "PlaybackService.onErrorListener";
-
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- Log.w(TAG, "An error has occured: " + what);
- if (mp.isPlaying()) {
- pause(true, true);
- }
- sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
- setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
- stopSelf();
- return true;
- }
- };
-
- private MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() {
-
- @Override
- public void onCompletion(MediaPlayer mp) {
- endPlayback(true);
- }
- };
-
- private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {
-
- @Override
- public void onBufferingUpdate(MediaPlayer mp, int percent) {
- sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
-
- }
- };
-
- private void endPlayback(boolean playNextEpisode) {
- if (AppConfig.DEBUG)
- 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 final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(com.aocate.media.MediaPlayer mp) {
+ genericOnPrepared(mp);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(android.media.MediaPlayer mp) {
+ genericOnPrepared(mp);
+ }
+ };
+
+ private final void genericOnPrepared(Object inObj) {
+ IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Resource prepared");
+ mp.seekTo(media.getPosition());
+ if (media.getDuration() == 0) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting duration of media");
+ media.setDuration(mp.getDuration());
+ }
+ setStatus(PlayerStatus.PREPARED);
+ if (chapterLoader != null) {
+ chapterLoader.interrupt();
+ }
+ chapterLoader = new Thread() {
+ @Override
+ public void run() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Chapter loader started");
+ if (media != null && media.getChapters() == null) {
+ media.loadChapterMarks();
+ if (!isInterrupted() && media.getChapters() != null) {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
+ 0);
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Chapter loader stopped");
+ }
+ };
+ chapterLoader.start();
+
+ if (startWhenPrepared) {
+ play();
+ }
+ }
+
+ private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
+ @Override
+ public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
+ genericSeekCompleteListener();
+ }
+ };
+
+ private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
+ @Override
+ public void onSeekComplete(android.media.MediaPlayer mp) {
+ genericSeekCompleteListener();
+ }
+ };
+
+ private final void genericSeekCompleteListener() {
+ if (status == PlayerStatus.SEEKING) {
+ setStatus(statusBeforeSeek);
+ }
+ }
+
+ private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
+ @Override
+ public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
+ int extra) {
+ return genericInfoListener(what);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
+ @Override
+ public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
+ return genericInfoListener(what);
+ }
+ };
+
+ private boolean genericInfoListener(int what) {
+ switch (what) {
+ case MediaPlayer.MEDIA_INFO_BUFFERING_START:
+ sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0);
+ return true;
+ case MediaPlayer.MEDIA_INFO_BUFFERING_END:
+ sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(com.aocate.media.MediaPlayer mp, int what,
+ int extra) {
+ return genericOnError(mp, what, extra);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
+ return genericOnError(mp, what, extra);
+ }
+ };
+
+ private boolean genericOnError(Object inObj, int what, int extra) {
+ final String TAG = "PlaybackService.onErrorListener";
+ Log.w(TAG, "An error has occured: " + what + " " + extra);
+ IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
+ if (mp.isPlaying()) {
+ pause(true, true);
+ }
+ sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
+ setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
+ stopSelf();
+ return true;
+ }
+
+ private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(com.aocate.media.MediaPlayer mp) {
+ genericOnCompletion();
+ }
+ };
+
+ private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(android.media.MediaPlayer mp) {
+ genericOnCompletion();
+ }
+ };
+
+ private void genericOnCompletion() {
+ endPlayback(true);
+ }
+
+ private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
+ @Override
+ public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
+ int percent) {
+ genericOnBufferingUpdate(percent);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
+ @Override
+ public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
+ genericOnBufferingUpdate(percent);
+ }
+ };
+
+ private void genericOnBufferingUpdate(int percent) {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
+ }
+
+ 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();
+ 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, (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)
@@ -813,10 +955,10 @@ public class PlaybackService extends Service {
/**
* Saves the current position and pauses playback. Note that, if audiofocus
* is abandoned, the lockscreen controls will also disapear.
- *
+ *
* @param abandonFocus
* is true if the service should release audio focus
- * @param reset
+ * @param reinit
* is true if service should reinit after pausing if the media
* file is being streamed
*/
@@ -825,14 +967,14 @@ public class PlaybackService extends Service {
if (AppConfig.DEBUG)
Log.d(TAG, "Pausing playback.");
player.pause();
+ cancelPositionSaver();
+ saveCurrentPosition();
+ setStatus(PlayerStatus.PAUSED);
if (abandonFocus) {
audioManager.abandonAudioFocus(audioFocusChangeListener);
pausedBecauseOfTransientAudiofocusLoss = false;
disableSleepTimer();
}
- cancelPositionSaver();
- saveCurrentPosition();
- setStatus(PlayerStatus.PAUSED);
stopWidgetUpdater();
stopForeground(true);
if (shouldStream && reinit) {
@@ -871,8 +1013,7 @@ public class PlaybackService extends Service {
public void reinit() {
player.reset();
player = createMediaPlayer(player);
- prepareImmediately = false;
- initMediaplayer();
+ initMediaplayer(false);
}
@SuppressLint("NewApi")
@@ -890,6 +1031,7 @@ public class PlaybackService extends Service {
Log.d(TAG, "Resuming/Starting playback");
writePlaybackPreferences();
+ setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
player.start();
if (status != PlayerStatus.PAUSED) {
player.seekTo((int) media.getPosition());
@@ -1075,7 +1217,7 @@ public class PlaybackService extends Service {
/**
* Seek a specific position from the current position
- *
+ *
* @param delta
* offset from current position (positive or negative)
* */
@@ -1233,16 +1375,20 @@ public class PlaybackService extends Service {
isPlaying = true;
}
- Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED);
- i.putExtra("id", 1);
- i.putExtra("artist", "");
- i.putExtra("album", media.getFeedTitle());
- i.putExtra("track", media.getEpisodeTitle());
- i.putExtra("playing", isPlaying);
- i.putExtra("ListSize", manager.getQueueSize(false));
- i.putExtra("duration", media.getDuration());
- i.putExtra("position", media.getPosition());
- sendBroadcast(i);
+ if (media != null) {
+ Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED);
+ i.putExtra("id", 1);
+ i.putExtra("artist", "");
+ i.putExtra("album", media.getFeedTitle());
+ i.putExtra("track", media.getEpisodeTitle());
+ i.putExtra("playing", isPlaying);
+ if (queue != null) {
+ i.putExtra("ListSize", queue.size());
+ }
+ i.putExtra("duration", media.getDuration());
+ i.putExtra("position", media.getPosition());
+ sendBroadcast(i);
+ }
}
/**
@@ -1314,12 +1460,11 @@ public class PlaybackService extends Service {
Log.d(TAG, "Received SKIP_CURRENT_EPISODE intent");
if (media != null) {
setStatus(PlayerStatus.STOPPED);
- player.reset();
- endPlayback(false);
+ endPlayback(true);
}
}
}
- };
+ };
/** Periodically saves the position of the media file */
class PositionSaver implements Runnable {
@@ -1421,7 +1566,7 @@ public class PlaybackService extends Service {
return media;
}
- public MediaPlayer getPlayer() {
+ public IPlayer getPlayer() {
return player;
}
@@ -1434,6 +1579,53 @@ public class PlaybackService extends Service {
postStatusUpdateIntent();
}
+ public boolean canSetSpeed() {
+ if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) {
+ return ((AudioPlayer) player).canSetSpeed();
+ }
+ return false;
+ }
+
+ public boolean canSetPitch() {
+ if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) {
+ return ((AudioPlayer) player).canSetPitch();
+ }
+ return false;
+ }
+
+ public void setSpeed(float speed) {
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ AudioPlayer audioPlayer = (AudioPlayer) player;
+ if (audioPlayer.canSetSpeed()) {
+ audioPlayer.setPlaybackSpeed((float) speed);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Playback speed was set to " + speed);
+ sendNotificationBroadcast(
+ NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0);
+ }
+ }
+ }
+
+ public void setPitch(float pitch) {
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ AudioPlayer audioPlayer = (AudioPlayer) player;
+ if (audioPlayer.canSetPitch()) {
+ audioPlayer.setPlaybackPitch((float) pitch);
+ }
+ }
+ }
+
+ public float getCurrentPlaybackSpeed() {
+ if (media.getMediaType() == MediaType.AUDIO
+ && player instanceof AudioPlayer) {
+ AudioPlayer audioPlayer = (AudioPlayer) player;
+ if (audioPlayer.canSetSpeed()) {
+ return audioPlayer.getCurrentSpeedMultiplier();
+ }
+ }
+ return -1;
+ }
+
/**
* call getDuration() on mediaplayer or return INVALID_TIME if player is in
* an invalid state. This method should be used instead of calling
@@ -1520,4 +1712,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 e1230e170..4040c85a8 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,859 @@ 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 int reason;
- private boolean successful;
-
- public FeedSyncThread(DownloadStatus status) {
- this.feed = (Feed) status.getFeedFile();
- this.status = status;
- }
-
- public void run() {
- Feed savedFeed = null;
- reason = 0;
- 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.get() == 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;
+ }
+
+ /**
+ * Remove download from the DownloadRequester list and from the
+ * DownloadService list.
+ */
+ private void removeDownload(final Downloader d) {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ 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 && DownloadRequester.getInstance().hasNoDownloads()) {
+ 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;
+ 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.getItems()) {
+ 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 = null;
+ try {
+ mediaplayer = new MediaPlayer();
+ 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();
+ } catch (RuntimeException e) {
+ // Thrown by MediaPlayer initialization on some devices
+ e.printStackTrace();
+ } finally {
+ if (mediaplayer != null) {
+ 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..487c3b3de
--- /dev/null
+++ b/src/de/danoeh/antennapod/service/download/DownloadStatus.java
@@ -0,0 +1,182 @@
+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 = (Date) completionDate.clone();
+ 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 (Date) completionDate.clone();
+ }
+
+ 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;
+ this.done = true;
+ }
+
+ 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 = (Date) completionDate.clone();
+ }
+
+ 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 f8f26f6fd..582fb9575 100644
--- a/src/de/danoeh/antennapod/service/download/HttpDownloader.java
+++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java
@@ -6,12 +6,12 @@ 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.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.commons.io.IOUtils;
+import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
@@ -26,178 +26,190 @@ 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;
public class HttpDownloader extends Downloader {
- private static final String TAG = "HttpDownloader";
-
- private static final int MAX_REDIRECTS = 5;
-
- private static final int BUFFER_SIZE = 8 * 1024;
- private static final int CONNECTION_TIMEOUT = 30000;
- private static final int SOCKET_TIMEOUT = 30000;
-
- public HttpDownloader(DownloaderCallback downloaderCallback,
- DownloadStatus status) {
- super(downloaderCallback, status);
- }
-
- private DefaultHttpClient createHttpClient() {
- DefaultHttpClient httpClient = new DefaultHttpClient();
- HttpParams params = httpClient.getParams();
- params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
- params.setBooleanParameter("http.protocol.reject-relative-redirect",
- false);
- HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
- HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
- HttpClientParams.setRedirecting(params, true);
-
- // Workaround for broken URLs in redirection
- ((AbstractHttpClient) httpClient)
- .setRedirectHandler(new APRedirectHandler());
- return httpClient;
- }
-
- @Override
- protected void download() {
- DefaultHttpClient httpClient = null;
- OutputStream out = null;
- InputStream connection = null;
- try {
- HttpGet httpGet = new HttpGet(status.getFeedFile()
- .getDownload_url());
- httpClient = createHttpClient();
- HttpResponse response = httpClient.execute(httpGet);
- HttpEntity httpEntity = response.getEntity();
- int responseCode = response.getStatusLine().getStatusCode();
- if (AppConfig.DEBUG)
- 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());
- if (!destination.exists()) {
- connection = AndroidHttpClient
- .getUngzippedContent(httpEntity);
- InputStream in = new BufferedInputStream(connection);
- 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(httpEntity.getContentLength());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Size is " + status.getSize());
- if (status.getSize() < 0) {
- status.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 (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));
- }
- if (cancelled) {
- onCancelled();
- } else {
- onSuccess();
- }
- } else {
- onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
- }
- } else {
- Log.w(TAG, "File already exists");
- onFail(DownloadError.ERROR_FILE_EXISTS, null);
- }
- } else {
- onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
- }
- } else {
- onFail(DownloadError.ERROR_HTTP_DATA_ERROR,
- String.valueOf(responseCode));
- }
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage());
- } catch (SocketTimeoutException e) {
- e.printStackTrace();
- onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage());
- } catch (UnknownHostException e) {
- e.printStackTrace();
- onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage());
- } catch (IOException e) {
- e.printStackTrace();
- onFail(DownloadError.ERROR_IO_ERROR, e.getMessage());
- } catch (NullPointerException e) {
- // might be thrown by connection.getInputStream()
- e.printStackTrace();
- onFail(DownloadError.ERROR_CONNECTION_ERROR, status.getFeedFile()
- .getDownload_url());
- } finally {
- IOUtils.closeQuietly(connection);
- IOUtils.closeQuietly(out);
- if (httpClient != null) {
- httpClient.getConnectionManager().shutdown();
- }
- }
- }
-
- private void onSuccess() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Download was successful");
- status.setSuccessful(true);
- status.setDone(true);
- }
-
- private void onFail(int reason, String reasonDetailed) {
- if (AppConfig.DEBUG) {
- Log.d(TAG, "Download failed");
- }
- status.setReason(reason);
- status.setReasonDetailed(reasonDetailed);
- status.setDone(true);
- status.setSuccessful(false);
- cleanup();
- }
-
- private void onCancelled() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Download was cancelled");
- status.setReason(DownloadError.ERROR_DOWNLOAD_CANCELLED);
- status.setDone(true);
- status.setSuccessful(false);
- status.setCancelled(true);
- 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 (dest.exists()) {
- boolean rc = dest.delete();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
- + rc);
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "cleanup() didn't delete file: does not exist.");
- }
- }
- }
+ private static final String TAG = "HttpDownloader";
+
+ private static final int MAX_REDIRECTS = 5;
+
+ private static final int BUFFER_SIZE = 8 * 1024;
+ private static final int CONNECTION_TIMEOUT = 30000;
+ private static final int SOCKET_TIMEOUT = 30000;
+
+ public HttpDownloader(DownloadRequest request) {
+ super(request);
+ }
+
+ private DefaultHttpClient createHttpClient() {
+ DefaultHttpClient httpClient = new DefaultHttpClient();
+ HttpParams params = httpClient.getParams();
+ params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
+ params.setBooleanParameter("http.protocol.reject-relative-redirect",
+ false);
+ HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
+ HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
+ HttpClientParams.setRedirecting(params, true);
+
+ // Workaround for broken URLs in redirection
+ ((AbstractHttpClient) httpClient)
+ .setRedirectHandler(new APRedirectHandler());
+ return httpClient;
+ }
+
+ @Override
+ protected void download() {
+ DefaultHttpClient httpClient = null;
+ BufferedOutputStream out = null;
+ InputStream connection = null;
+ try {
+ HttpGet httpGet = new HttpGet(request.getSource());
+ httpClient = createHttpClient();
+ HttpResponse response = httpClient.execute(httpGet);
+ HttpEntity httpEntity = response.getEntity();
+ int responseCode = response.getStatusLine().getStatusCode();
+ Header contentEncodingHeader = response.getFirstHeader("Content-Encoding");
+
+ final boolean isGzip = contentEncodingHeader != null &&
+ contentEncodingHeader.getValue().equalsIgnoreCase("gzip");
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Response code is " + responseCode);
+
+ if (responseCode != HttpURLConnection.HTTP_OK || httpEntity == null) {
+ onFail(DownloadError.ERROR_HTTP_DATA_ERROR,
+ String.valueOf(responseCode));
+ return;
+ }
+
+ if (!StorageUtils.storageAvailable(PodcastApp.getInstance())) {
+ onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
+ return;
+ }
+
+ File destination = new File(request.getDestination());
+ if (destination.exists()) {
+ Log.w(TAG, "File already exists");
+ onFail(DownloadError.ERROR_FILE_EXISTS, null);
+ return;
+ }
+
+ connection = new BufferedInputStream(AndroidHttpClient
+ .getUngzippedContent(httpEntity));
+ out = new BufferedOutputStream(new FileOutputStream(
+ destination));
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int count = 0;
+ request.setStatusMsg(R.string.download_running);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Getting size of download");
+ request.setSize(httpEntity.getContentLength());
+ if (AppConfig.DEBUG)
+ 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 (request.getSize() != DownloadStatus.SIZE_UNKNOWN
+ && request.getSize() > freeSpace) {
+ onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
+ return;
+ }
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Starting download");
+ while (!cancelled
+ && (count = connection.read(buffer)) != -1) {
+ out.write(buffer, 0, count);
+ request.setSoFar(request.getSoFar() + count);
+ request.setProgressPercent((int) (((double) request
+ .getSoFar() / (double) request
+ .getSize()) * 100));
+ }
+ if (cancelled) {
+ onCancelled();
+ } else {
+ out.flush();
+ // check if size specified in the response header is the same as the size of the
+ // written file. This check cannot be made if compression was used
+ if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN &&
+ request.getSoFar() != request.getSize()) {
+ onFail(DownloadError.ERROR_IO_ERROR,
+ "Download completed but size: " +
+ request.getSoFar() +
+ " does not equal expected size " +
+ request.getSize());
+ return;
+ }
+ onSuccess();
+ }
+
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage());
+ } catch (SocketTimeoutException e) {
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage());
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage());
+ } catch (IOException e) {
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_IO_ERROR, e.getMessage());
+ } catch (NullPointerException e) {
+ // might be thrown by connection.getInputStream()
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
+ } finally {
+ IOUtils.closeQuietly(out);
+ if (httpClient != null) {
+ httpClient.getConnectionManager().shutdown();
+ }
+ }
+ }
+
+ private void onSuccess() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Download was successful");
+ result.setSuccessful();
+ }
+
+ private void onFail(DownloadError reason, String reasonDetailed) {
+ if (AppConfig.DEBUG) {
+ Log.d(TAG, "Download failed");
+ }
+ result.setFailed(reason, reasonDetailed);
+ cleanup();
+ }
+
+ private void onCancelled() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Download was cancelled");
+ result.setCancelled();
+ cleanup();
+ }
+
+ /**
+ * Deletes unfinished downloads.
+ */
+ private void cleanup() {
+ if (request.getDestination() != null) {
+ File dest = new File(request.getDestination());
+ if (dest.exists()) {
+ boolean rc = dest.delete();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ + rc);
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "cleanup() didn't delete file: does not exist.");
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java
new file mode 100644
index 000000000..28ab3d939
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/DBReader.java
@@ -0,0 +1,757 @@
+package de.danoeh.antennapod.storage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.util.Log;
+import de.danoeh.antennapod.AppConfig;
+import de.danoeh.antennapod.feed.Chapter;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedImage;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.feed.ID3Chapter;
+import de.danoeh.antennapod.feed.SimpleChapter;
+import de.danoeh.antennapod.feed.VorbisCommentChapter;
+import de.danoeh.antennapod.service.download.*;
+import de.danoeh.antennapod.util.DownloadError;
+import de.danoeh.antennapod.util.comparator.DownloadStatusComparator;
+import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
+
+/**
+ * Provides methods for reading data from the AntennaPod database.
+ * In general, all database calls in DBReader-methods are executed on the caller's thread.
+ * This means that the caller should make sure that DBReader-methods are not executed on the GUI-thread.
+ * This class will use the {@link de.danoeh.antennapod.feed.EventDistributor} to notify listeners about changes in the database.
+
+ */
+public final class DBReader {
+ private static final String TAG = "DBReader";
+
+ /**
+ * Maximum size of the list returned by {@link #getPlaybackHistory(android.content.Context)}.
+ */
+ public static final int PLAYBACK_HISTORY_SIZE = 50;
+
+ /**
+ * Maximum size of the list returned by {@link #getDownloadLog(android.content.Context)}.
+ */
+ public static final int DOWNLOAD_LOG_SIZE = 200;
+
+
+ private DBReader() {
+ }
+
+ /**
+ * Returns a list of Feeds, sorted alphabetically by their title.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of Feeds, sorted alphabetically by their title. A Feed-object
+ * of the returned list does NOT have its list of FeedItems yet. The FeedItem-list
+ * can be loaded separately with {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)}.
+ */
+ public static List<Feed> getFeedList(final Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting Feedlist");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor feedlistCursor = adapter.getAllFeedsCursor();
+ List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
+
+ if (feedlistCursor.moveToFirst()) {
+ do {
+ Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor);
+ feeds.add(feed);
+ } while (feedlistCursor.moveToNext());
+ }
+ feedlistCursor.close();
+ return feeds;
+ }
+
+ /**
+ * Returns a list of 'expired Feeds', i.e. Feeds that have not been updated for a certain amount of time.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param expirationTime Time that is used for determining whether a feed is outdated or not.
+ * A Feed is considered expired if 'lastUpdate < (currentTime - expirationTime)' evaluates to true.
+ * @return A list of Feeds, sorted alphabetically by their title. A Feed-object
+ * of the returned list does NOT have its list of FeedItems yet. The FeedItem-list
+ * can be loaded separately with {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)}.
+ */
+ static List<Feed> getExpiredFeedsList(final Context context, final long expirationTime) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime));
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime);
+ List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
+
+ if (feedlistCursor.moveToFirst()) {
+ do {
+ Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor);
+ feeds.add(feed);
+ } while (feedlistCursor.moveToNext());
+ }
+ feedlistCursor.close();
+ return feeds;
+ }
+
+ /**
+ * Takes a list of FeedItems and loads their corresponding Feed-objects from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param items The FeedItems whose Feed-objects should be loaded.
+ */
+ public static void loadFeedDataOfFeedItemlist(Context context,
+ List<FeedItem> items) {
+ List<Feed> feeds = getFeedList(context);
+ for (FeedItem item : items) {
+ for (Feed feed : feeds) {
+ if (feed.getId() == item.getFeedId()) {
+ item.setFeed(feed);
+ break;
+ }
+ }
+ if (item.getFeed() == null) {
+ Log.w(TAG, "No match found for item with ID " + item.getId() + ". Feed ID was " + item.getFeedId());
+ }
+ }
+ }
+
+ /**
+ * Loads the list of FeedItems for a certain Feed-object. This method should NOT be used if the FeedItems are not
+ * used. In order to get information ABOUT the list of FeedItems, consider using {@link #getFeedStatisticsList(android.content.Context)} instead.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feed The Feed whose items should be loaded
+ * @return A list with the FeedItems of the Feed. The Feed-attribute of the FeedItems will already be set correctly.
+ * The method does NOT change the items-attribute of the feed.
+ */
+ public static List<FeedItem> getFeedItemList(Context context,
+ final Feed feed) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed);
+ List<FeedItem> items = extractItemlistFromCursor(adapter,
+ itemlistCursor);
+ itemlistCursor.close();
+
+ Collections.sort(items, new FeedItemPubdateComparator());
+
+ adapter.close();
+
+ for (FeedItem item : items) {
+ item.setFeed(feed);
+ }
+
+ return items;
+ }
+
+ static List<FeedItem> extractItemlistFromCursor(Context context, Cursor itemlistCursor) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ List<FeedItem> result = extractItemlistFromCursor(adapter, itemlistCursor);
+ adapter.close();
+ return result;
+ }
+
+ private static List<FeedItem> extractItemlistFromCursor(
+ PodDBAdapter adapter, Cursor itemlistCursor) {
+ ArrayList<String> itemIds = new ArrayList<String>();
+ List<FeedItem> items = new ArrayList<FeedItem>(
+ itemlistCursor.getCount());
+
+ if (itemlistCursor.moveToFirst()) {
+ do {
+ FeedItem item = new FeedItem();
+
+ item.setId(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID));
+ item.setTitle(itemlistCursor
+ .getString(PodDBAdapter.IDX_FI_SMALL_TITLE));
+ item.setLink(itemlistCursor
+ .getString(PodDBAdapter.IDX_FI_SMALL_LINK));
+ item.setPubDate(new Date(itemlistCursor
+ .getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE)));
+ item.setPaymentLink(itemlistCursor
+ .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK));
+ item.setFeedId(itemlistCursor
+ .getLong(PodDBAdapter.IDX_FI_SMALL_FEED));
+ itemIds.add(String.valueOf(item.getId()));
+
+ item.setRead((itemlistCursor
+ .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0));
+ item.setItemIdentifier(itemlistCursor
+ .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
+
+ // extract chapters
+ boolean hasSimpleChapters = itemlistCursor
+ .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0;
+ if (hasSimpleChapters) {
+ Cursor chapterCursor = adapter
+ .getSimpleChaptersOfFeedItemCursor(item);
+ if (chapterCursor.moveToFirst()) {
+ item.setChapters(new ArrayList<Chapter>());
+ do {
+ int chapterType = chapterCursor
+ .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
+ Chapter chapter = null;
+ long start = chapterCursor
+ .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
+ String title = chapterCursor
+ .getString(PodDBAdapter.KEY_TITLE_INDEX);
+ String link = chapterCursor
+ .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
+
+ switch (chapterType) {
+ case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
+ chapter = new SimpleChapter(start, title, item,
+ link);
+ break;
+ case ID3Chapter.CHAPTERTYPE_ID3CHAPTER:
+ chapter = new ID3Chapter(start, title, item,
+ link);
+ break;
+ case VorbisCommentChapter.CHAPTERTYPE_VORBISCOMMENT_CHAPTER:
+ chapter = new VorbisCommentChapter(start,
+ title, item, link);
+ break;
+ }
+ if (chapter != null) {
+ chapter.setId(chapterCursor
+ .getLong(PodDBAdapter.KEY_ID_INDEX));
+ item.getChapters().add(chapter);
+ }
+ } while (chapterCursor.moveToNext());
+ }
+ chapterCursor.close();
+ }
+ items.add(item);
+ } while (itemlistCursor.moveToNext());
+ }
+
+ extractMediafromItemlist(adapter, items, itemIds);
+ return items;
+ }
+
+ private static void extractMediafromItemlist(PodDBAdapter adapter,
+ List<FeedItem> items, ArrayList<String> itemIds) {
+
+ List<FeedItem> itemsCopy = new ArrayList<FeedItem>(items);
+ Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds
+ .toArray(new String[itemIds.size()]));
+ if (cursor.moveToFirst()) {
+ do {
+ long itemId = cursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX);
+ // find matching feed item
+ FeedItem item = getMatchingItemForMedia(itemId, itemsCopy);
+ if (item != null) {
+ item.setMedia(extractFeedMediaFromCursorRow(cursor));
+ item.getMedia().setItem(item);
+ }
+ } while (cursor.moveToNext());
+ cursor.close();
+ }
+ }
+
+ private static FeedMedia extractFeedMediaFromCursorRow(final Cursor cursor) {
+ long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ Date playbackCompletionDate = null;
+ long playbackCompletionTime = cursor
+ .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX);
+ if (playbackCompletionTime > 0) {
+ playbackCompletionDate = new Date(
+ playbackCompletionTime);
+ }
+
+ return new FeedMedia(
+ mediaId,
+ null,
+ cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX),
+ cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX),
+ cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
+ cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
+ cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0,
+ playbackCompletionDate);
+ }
+
+ private static Feed extractFeedFromCursorRow(PodDBAdapter adapter,
+ Cursor cursor) {
+ Date lastUpdate = new Date(
+ cursor.getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX));
+
+ final FeedImage image;
+ long imageIndex = cursor.getLong(PodDBAdapter.KEY_IMAGE_INDEX);
+ if (imageIndex != 0) {
+ image = getFeedImage(adapter, imageIndex);
+ } else {
+ image = null;
+ }
+ Feed feed = new Feed(cursor.getLong(PodDBAdapter.KEY_ID_INDEX),
+ lastUpdate,
+ cursor.getString(PodDBAdapter.KEY_TITLE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_LINK_INDEX),
+ cursor.getString(PodDBAdapter.KEY_DESCRIPTION_INDEX),
+ cursor.getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX),
+ cursor.getString(PodDBAdapter.KEY_AUTHOR_INDEX),
+ cursor.getString(PodDBAdapter.KEY_LANGUAGE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_TYPE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX),
+ image,
+ cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
+ cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
+ cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0);
+
+ if (image != null) {
+ image.setFeed(feed);
+ }
+ return feed;
+ }
+
+ private static FeedItem getMatchingItemForMedia(long itemId,
+ List<FeedItem> items) {
+ for (FeedItem item : items) {
+ if (item.getId() == itemId) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ static List<FeedItem> getQueue(Context context, PodDBAdapter adapter) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting queue");
+
+ Cursor itemlistCursor = adapter.getQueueCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter,
+ itemlistCursor);
+ itemlistCursor.close();
+ loadFeedDataOfFeedItemlist(context, items);
+
+ return items;
+ }
+
+ /**
+ * Loads the IDs of the FeedItems in the queue. This method should be preferred over
+ * {@link #getQueue(android.content.Context)} if the FeedItems of the queue are not needed.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of IDs sorted by the same order as the queue. The caller can wrap the returned
+ * list in a {@link de.danoeh.antennapod.util.QueueAccess} object for easier access to the queue's properties.
+ */
+ public static List<Long> getQueueIDList(Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+
+ adapter.open();
+ List<Long> result = getQueueIDList(adapter);
+ adapter.close();
+
+ return result;
+ }
+
+ static List<Long> getQueueIDList(PodDBAdapter adapter) {
+ adapter.open();
+ Cursor queueCursor = adapter.getQueueIDCursor();
+
+ List<Long> queueIds = new ArrayList<Long>(queueCursor.getCount());
+ if (queueCursor.moveToFirst()) {
+ do {
+ queueIds.add(queueCursor.getLong(0));
+ } while (queueCursor.moveToNext());
+ }
+ return queueIds;
+ }
+
+
+ /**
+ * Loads a list of the FeedItems in the queue. If the FeedItems of the queue are not used directly, consider using
+ * {@link #getQueueIDList(android.content.Context)} instead.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems sorted by the same order as the queue. The caller can wrap the returned
+ * list in a {@link de.danoeh.antennapod.util.QueueAccess} object for easier access to the queue's properties.
+ */
+ public static List<FeedItem> getQueue(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting queue");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ List<FeedItem> items = getQueue(context, adapter);
+ adapter.close();
+ return items;
+ }
+
+ /**
+ * Loads a list of FeedItems whose episode has been downloaded.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems whose episdoe has been downloaded.
+ */
+ public static List<FeedItem> getDownloadedItems(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting downloaded items");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getDownloadedItemsCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter,
+ itemlistCursor);
+ itemlistCursor.close();
+ loadFeedDataOfFeedItemlist(context, items);
+ Collections.sort(items, new FeedItemPubdateComparator());
+
+ adapter.close();
+ return items;
+
+ }
+
+ /**
+ * Loads a list of FeedItems whose 'read'-attribute is set to false.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems whose 'read'-attribute it set to false. If the FeedItems in the list are not used,
+ * consider using {@link #getUnreadItemIds(android.content.Context)} instead.
+ */
+ public static List<FeedItem> getUnreadItemsList(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting unread items list");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getUnreadItemsCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter,
+ itemlistCursor);
+ itemlistCursor.close();
+
+ loadFeedDataOfFeedItemlist(context, items);
+
+ adapter.close();
+
+ return items;
+ }
+
+ /**
+ * Loads the IDs of the FeedItems whose 'read'-attribute is set to false.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred
+ * over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used.
+ */
+ public static long[] getUnreadItemIds(Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getUnreadItemIdsCursor();
+ long[] itemIds = new long[cursor.getCount()];
+ int i = 0;
+ if (cursor.moveToFirst()) {
+ do {
+ itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ i++;
+ } while (cursor.moveToNext());
+ }
+ return itemIds;
+ }
+
+ /**
+ * Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode
+ * has been completed at least once.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
+ * The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}.
+ */
+ public static List<FeedItem> getPlaybackHistory(final Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading playback history");
+ final int PLAYBACK_HISTORY_SIZE = 50;
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE);
+ String[] itemIds = new String[mediaCursor.getCount()];
+ for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) {
+ itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX));
+ }
+ mediaCursor.close();
+ Cursor itemCursor = adapter.getFeedItemCursor(itemIds);
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemCursor);
+ loadFeedDataOfFeedItemlist(context, items);
+ itemCursor.close();
+
+ adapter.close();
+ return items;
+ }
+
+ /**
+ * Loads the download log from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list with DownloadStatus objects that represent the download log.
+ * The size of the returned list is limited by {@link #DOWNLOAD_LOG_SIZE}.
+ */
+ public static List<DownloadStatus> getDownloadLog(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting DownloadLog");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor logCursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE);
+ List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
+ logCursor.getCount());
+
+ if (logCursor.moveToFirst()) {
+ do {
+ long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+
+ long feedfileId = logCursor
+ .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
+ int feedfileType = logCursor
+ .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
+ boolean successful = logCursor
+ .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
+ int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
+ String reasonDetailed = logCursor
+ .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
+ String title = logCursor
+ .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
+ Date completionDate = new Date(
+ logCursor
+ .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
+ downloadLog.add(new DownloadStatus(id, title, feedfileId,
+ feedfileType, successful, DownloadError.fromCode(reason), completionDate,
+ reasonDetailed));
+
+ } while (logCursor.moveToNext());
+ }
+ logCursor.close();
+ Collections.sort(downloadLog, new DownloadStatusComparator());
+ return downloadLog;
+ }
+
+ /**
+ * Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over
+ * {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)} if only metadata about
+ * the FeedItems is needed.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItemStatistics objects sorted alphabetically by their Feed's title.
+ */
+ public static List<FeedItemStatistics> getFeedStatisticsList(final Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ List<FeedItemStatistics> result = new ArrayList<FeedItemStatistics>();
+ Cursor cursor = adapter.getFeedStatisticsCursor();
+ if (cursor.moveToFirst()) {
+ do {
+ result.add(new FeedItemStatistics(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_FEED),
+ cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NUM_ITEMS),
+ cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NEW_ITEMS),
+ cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES),
+ new Date(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_LATEST_EPISODE))));
+ } while (cursor.moveToNext());
+ }
+
+ cursor.close();
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Loads a specific Feed from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feedId The ID of the Feed
+ * @return The Feed or null if the Feed could not be found. The Feeds FeedItems will also be loaded from the
+ * database and the items-attribute will be set correctly.
+ */
+ public static Feed getFeed(final Context context, final long feedId) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading feed with id " + feedId);
+ Feed feed = null;
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor feedCursor = adapter.getFeedCursor(feedId);
+ if (feedCursor.moveToFirst()) {
+ feed = extractFeedFromCursorRow(adapter, feedCursor);
+ feed.setItems(getFeedItemList(context, feed));
+ } else {
+ Log.e(TAG, "getFeed could not find feed with id " + feedId);
+ }
+ feedCursor.close();
+ adapter.close();
+ return feed;
+ }
+
+ static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading feeditem with id " + itemId);
+ FeedItem item = null;
+
+ Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId));
+ if (itemCursor.moveToFirst()) {
+ List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
+ if (list.size() > 0) {
+ item = list.get(0);
+ loadFeedDataOfFeedItemlist(context, list);
+ }
+ }
+ return item;
+
+ }
+
+ /**
+ * Loads a specific FeedItem from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemId The ID of the FeedItem
+ * @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes of the FeedItem will
+ * also be loaded from the database.
+ */
+ public static FeedItem getFeedItem(final Context context, final long itemId) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading feeditem with id " + itemId);
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ FeedItem item = getFeedItem(context, itemId, adapter);
+ adapter.close();
+ return item;
+
+ }
+
+ /**
+ * Loads additional information about a FeedItem, e.g. shownotes
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param item The FeedItem
+ */
+ public static void loadExtraInformationOfFeedItem(final Context context, final FeedItem item) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor extraCursor = adapter.getExtraInformationOfItem(item);
+ if (extraCursor.moveToFirst()) {
+ String description = extraCursor
+ .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
+ String contentEncoded = extraCursor
+ .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
+ item.setDescription(description);
+ item.setContentEncoded(contentEncoded);
+ }
+ adapter.close();
+ }
+
+ /**
+ * Returns the number of downloaded episodes.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return The number of downloaded episodes.
+ */
+ public static int getNumberOfDownloadedEpisodes(final Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final int result = adapter.getNumberOfDownloadedEpisodes();
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Returns the number of unread items.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return The number of unread items.
+ */
+ public static int getNumberOfUnreadItems(final Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final int result = adapter.getNumberOfUnreadItems();
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Searches the DB for a FeedImage of the given id.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param imageId The id of the object
+ * @return The found object
+ */
+ public static FeedImage getFeedImage(final Context context, final long imageId) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ FeedImage result = getFeedImage(adapter, imageId);
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Searches the DB for a FeedImage of the given id.
+ *
+ * @param id The id of the object
+ * @return The found object
+ */
+ static FeedImage getFeedImage(PodDBAdapter adapter, final long id) {
+ Cursor cursor = adapter.getImageOfFeedCursor(id);
+ if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
+ throw new SQLException("No FeedImage found at index: " + id);
+ }
+ FeedImage image = new FeedImage(id, cursor.getString(cursor
+ .getColumnIndex(PodDBAdapter.KEY_TITLE)),
+ cursor.getString(cursor
+ .getColumnIndex(PodDBAdapter.KEY_FILE_URL)),
+ cursor.getString(cursor
+ .getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL)),
+ cursor.getInt(cursor
+ .getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0);
+ cursor.close();
+ return image;
+ }
+
+ /**
+ * Searches the DB for a FeedMedia of the given id.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param mediaId The id of the object
+ * @return The found object
+ */
+ public static FeedMedia getFeedMedia(final Context context, final long mediaId) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+
+ adapter.open();
+ Cursor mediaCursor = adapter.getSingleFeedMediaCursor(mediaId);
+
+ FeedMedia media = null;
+ if (mediaCursor.moveToFirst()) {
+ final long itemId = mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX);
+ media = extractFeedMediaFromCursorRow(mediaCursor);
+ FeedItem item = getFeedItem(context, itemId);
+ if (media != null && item != null) {
+ media.setItem(item);
+ item.setMedia(media);
+ }
+ }
+
+ mediaCursor.close();
+ adapter.close();
+
+ return media;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java
new file mode 100644
index 000000000..ba2e743a8
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/DBTasks.java
@@ -0,0 +1,740 @@
+package de.danoeh.antennapod.storage;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.util.Log;
+import de.danoeh.antennapod.AppConfig;
+import de.danoeh.antennapod.feed.EventDistributor;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedImage;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.PlaybackService;
+import de.danoeh.antennapod.service.download.DownloadStatus;
+import de.danoeh.antennapod.util.DownloadError;
+import de.danoeh.antennapod.util.NetworkUtils;
+import de.danoeh.antennapod.util.QueueAccess;
+import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
+import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
+
+/**
+ * Provides methods for doing common tasks that use DBReader and DBWriter.
+ */
+public final class DBTasks {
+ private static final String TAG = "DBTasks";
+
+ private DBTasks() {
+ }
+
+ /**
+ * Starts playback of a FeedMedia object's file. This method will build an Intent based on the given parameters to
+ * start the {@link PlaybackService}.
+ *
+ * @param context Used for sending starting Services and Activities.
+ * @param media The FeedMedia object.
+ * @param showPlayer If true, starts the appropriate player activity ({@link de.danoeh.antennapod.activity.AudioplayerActivity}
+ * or {@link de.danoeh.antennapod.activity.VideoplayerActivity}
+ * @param startWhenPrepared Parameter for the {@link PlaybackService} start intent. If true, playback will start as
+ * soon as the PlaybackService has finished loading the FeedMedia object's file.
+ * @param shouldStream Parameter for the {@link PlaybackService} start intent. If true, the FeedMedia object's file
+ * will be streamed, otherwise the downloaded file will be used. If the downloaded file cannot be
+ * found, the PlaybackService will shutdown and the database entry of the FeedMedia object will be
+ * corrected.
+ */
+ public static void playMedia(final Context context, final FeedMedia media,
+ boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) {
+ try {
+ if (!shouldStream) {
+ if (media.fileExists() == false) {
+ throw new MediaFileNotFoundException(
+ "No episode was found at " + media.getFile_url(),
+ media);
+ }
+ }
+ // Start playback Service
+ Intent launchIntent = new Intent(context, PlaybackService.class);
+ launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
+ launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
+ startWhenPrepared);
+ launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
+ shouldStream);
+ launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
+ true);
+ context.startService(launchIntent);
+ if (showPlayer) {
+ // Launch media player
+ context.startActivity(PlaybackService.getPlayerActivityIntent(
+ context, media));
+ }
+ DBWriter.addQueueItemAt(context, media.getItem().getId(), 0, false);
+ } catch (MediaFileNotFoundException e) {
+ e.printStackTrace();
+ if (media.isPlaying()) {
+ context.sendBroadcast(new Intent(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ notifyMissingFeedMediaFile(context, media);
+ }
+ }
+
+ private static AtomicBoolean isRefreshing = new AtomicBoolean(false);
+
+ /**
+ * Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
+ * enqueuing Feeds for download from a previous call
+ *
+ * @param context Might be used for accessing the database
+ * @param feeds List of Feeds that should be refreshed.
+ */
+ public static void refreshAllFeeds(final Context context,
+ final List<Feed> feeds) {
+ if (isRefreshing.compareAndSet(false, true)) {
+ new Thread() {
+ public void run() {
+ if (feeds != null) {
+ refreshFeeds(context, feeds);
+ } else {
+ refreshFeeds(context, DBReader.getFeedList(context));
+ }
+ isRefreshing.set(false);
+ }
+ }.start();
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Ignoring request to refresh all feeds: Refresh lock is locked");
+ }
+ }
+
+ /**
+ * Refreshes expired Feeds in the list returned by the getExpiredFeedsList(Context, long) method in DBReader.
+ * The expiration date parameter is determined by the update interval specified in {@link UserPreferences}.
+ *
+ * @param context Used for DB access.
+ */
+ public static void refreshExpiredFeeds(final Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Refreshing expired feeds");
+
+ new Thread() {
+ public void run() {
+ long millis = UserPreferences.getUpdateInterval();
+
+ if (millis > 0) {
+ long now = Calendar.getInstance().getTime().getTime();
+
+ // Allow a 10 minute window
+ millis -= 10 * 60 * 1000;
+ List<Feed> feedList = DBReader.getExpiredFeedsList(context,
+ now - millis);
+ if (feedList.size() > 0) {
+ refreshFeeds(context, feedList);
+ }
+ }
+ }
+ }.start();
+ }
+
+ private static void refreshFeeds(final Context context,
+ final List<Feed> feedList) {
+
+ for (Feed feed : feedList) {
+ try {
+ refreshFeed(context, feed);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DBWriter.addDownloadStatus(
+ context,
+ new DownloadStatus(feed, feed
+ .getHumanReadableIdentifier(),
+ DownloadError.ERROR_REQUEST_ERROR, false, e
+ .getMessage()));
+ }
+ }
+
+ }
+
+ /**
+ * Updates a specific Feed.
+ *
+ * @param context Used for requesting the download.
+ * @param feed The Feed object.
+ */
+ public static void refreshFeed(Context context, Feed feed)
+ throws DownloadRequestException {
+ DownloadRequester.getInstance().downloadFeed(context,
+ new Feed(feed.getDownload_url(), new Date(), feed.getTitle()));
+ }
+
+ /**
+ * Notifies the database about a missing FeedImage file. This method will attempt to re-download the file.
+ *
+ * @param context Used for requesting the download.
+ * @param image The FeedImage object.
+ */
+ public static void notifyInvalidImageFile(final Context context,
+ final FeedImage image) {
+ Log.i(TAG,
+ "The DB was notified about an invalid image download. It will now try to re-download the image file");
+ try {
+ DownloadRequester.getInstance().downloadImage(context, image);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ Log.w(TAG, "Failed to download invalid feed image");
+ }
+ }
+
+ /**
+ * Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's values in the
+ * DB and send a FeedUpdateBroadcast.
+ */
+ public static void notifyMissingFeedMediaFile(final Context context,
+ final FeedMedia media) {
+ Log.i(TAG,
+ "The feedmanager was notified about a missing episode. It will update its database now.");
+ media.setDownloaded(false);
+ media.setFile_url(null);
+ DBWriter.setFeedMedia(context, media);
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+
+ /**
+ * Request the download of all objects in the queue. from a separate Thread.
+ *
+ * @param context Used for requesting the download an accessing the database.
+ */
+ public static void downloadAllItemsInQueue(final Context context) {
+ new Thread() {
+ public void run() {
+ List<FeedItem> queue = DBReader.getQueue(context);
+ if (!queue.isEmpty()) {
+ try {
+ downloadFeedItems(context,
+ queue.toArray(new FeedItem[queue.size()]));
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Requests the download of a list of FeedItem objects.
+ *
+ * @param context Used for requesting the download and accessing the DB.
+ * @param items The FeedItem objects.
+ */
+ public static void downloadFeedItems(final Context context,
+ FeedItem... items) throws DownloadRequestException {
+ downloadFeedItems(true, context, items);
+ }
+
+ private static void downloadFeedItems(boolean performAutoCleanup,
+ final Context context, final FeedItem... items)
+ throws DownloadRequestException {
+ final DownloadRequester requester = DownloadRequester.getInstance();
+
+ if (performAutoCleanup) {
+ new Thread() {
+
+ @Override
+ public void run() {
+ performAutoCleanup(context,
+ getPerformAutoCleanupArgs(context, items.length));
+ }
+
+ }.start();
+ }
+ for (FeedItem item : items) {
+ if (item.getMedia() != null
+ && !requester.isDownloadingFile(item.getMedia())
+ && !item.getMedia().isDownloaded()) {
+ if (items.length > 1) {
+ try {
+ requester.downloadMedia(context, item.getMedia());
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DBWriter.addDownloadStatus(context,
+ new DownloadStatus(item.getMedia(), item
+ .getMedia()
+ .getHumanReadableIdentifier(),
+ DownloadError.ERROR_REQUEST_ERROR,
+ false, e.getMessage()));
+ }
+ } else {
+ requester.downloadMedia(context, item.getMedia());
+ }
+ }
+ }
+ }
+
+ private static int getNumberOfUndownloadedEpisodes(
+ final List<FeedItem> queue, final List<FeedItem> unreadItems) {
+ int counter = 0;
+ for (FeedItem item : queue) {
+ if (item.hasMedia() && !item.getMedia().isDownloaded()
+ && !item.getMedia().isPlaying()) {
+ counter++;
+ }
+ }
+ for (FeedItem item : unreadItems) {
+ if (item.hasMedia() && !item.getMedia().isDownloaded()) {
+ counter++;
+ }
+ }
+ return counter;
+ }
+
+ /**
+ * Looks for undownloaded episodes in the queue or list of unread items and request a download if
+ * 1. Network is available
+ * 2. There is free space in the episode cache
+ * This method should NOT be executed on the GUI thread.
+ *
+ * @param context Used for accessing the DB.
+ */
+ public static void autodownloadUndownloadedItems(final Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Performing auto-dl of undownloaded episodes");
+ if (NetworkUtils.autodownloadNetworkAvailable(context)
+ && UserPreferences.isEnableAutodownload()) {
+ final List<FeedItem> queue = DBReader.getQueue(context);
+ final List<FeedItem> unreadItems = DBReader
+ .getUnreadItemsList(context);
+
+ int undownloadedEpisodes = getNumberOfUndownloadedEpisodes(queue,
+ unreadItems);
+ int downloadedEpisodes = DBReader
+ .getNumberOfDownloadedEpisodes(context);
+ int deletedEpisodes = performAutoCleanup(context,
+ getPerformAutoCleanupArgs(context, undownloadedEpisodes));
+ int episodeSpaceLeft = undownloadedEpisodes;
+ boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
+ .getEpisodeCacheSizeUnlimited();
+
+ if (!cacheIsUnlimited
+ && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
+ + undownloadedEpisodes) {
+ episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
+ - (downloadedEpisodes - deletedEpisodes);
+ }
+
+ List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
+ if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
+ for (int i = 0; i < queue.size(); i++) { // ignore playing item
+ FeedItem item = queue.get(i);
+ if (item.hasMedia() && !item.getMedia().isDownloaded()
+ && !item.getMedia().isPlaying()) {
+ itemsToDownload.add(item);
+ episodeSpaceLeft--;
+ undownloadedEpisodes--;
+ if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
+ break;
+ }
+ }
+ }
+ }
+ if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
+ for (FeedItem item : unreadItems) {
+ if (item.hasMedia() && !item.getMedia().isDownloaded()) {
+ itemsToDownload.add(item);
+ episodeSpaceLeft--;
+ undownloadedEpisodes--;
+ if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
+ break;
+ }
+ }
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Enqueueing " + itemsToDownload.size()
+ + " items for download");
+
+ try {
+ downloadFeedItems(false, context,
+ itemsToDownload.toArray(new FeedItem[itemsToDownload
+ .size()]));
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+
+ private static int getPerformAutoCleanupArgs(Context context,
+ final int episodeNumber) {
+ if (episodeNumber >= 0
+ && UserPreferences.getEpisodeCacheSize() != UserPreferences
+ .getEpisodeCacheSizeUnlimited()) {
+ int downloadedEpisodes = DBReader
+ .getNumberOfDownloadedEpisodes(context);
+ if (downloadedEpisodes + episodeNumber >= UserPreferences
+ .getEpisodeCacheSize()) {
+
+ return downloadedEpisodes + episodeNumber
+ - UserPreferences.getEpisodeCacheSize();
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller
+ * 'playbackCompletionDate'-value will be deleted first.
+ * <p/>
+ * This method should NOT be executed on the GUI thread.
+ *
+ * @param context Used for accessing the DB.
+ */
+ public static void performAutoCleanup(final Context context) {
+ performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0));
+ }
+
+ private static int performAutoCleanup(final Context context,
+ final int episodeNumber) {
+ List<FeedItem> candidates = new ArrayList<FeedItem>();
+ List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context);
+ QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context));
+ List<FeedItem> delete;
+ for (FeedItem item : downloadedItems) {
+ if (item.hasMedia() && item.getMedia().isDownloaded()
+ && !queue.contains(item.getId()) && item.isRead()) {
+ candidates.add(item);
+ }
+
+ }
+
+ Collections.sort(candidates, new Comparator<FeedItem>() {
+ @Override
+ public int compare(FeedItem lhs, FeedItem rhs) {
+ Date l = lhs.getMedia().getPlaybackCompletionDate();
+ Date r = rhs.getMedia().getPlaybackCompletionDate();
+
+ if (l == null) {
+ l = new Date(0);
+ }
+ if (r == null) {
+ r = new Date(0);
+ }
+ return l.compareTo(r);
+ }
+ });
+
+ if (candidates.size() > episodeNumber) {
+ delete = candidates.subList(0, episodeNumber);
+ } else {
+ delete = candidates;
+ }
+
+ for (FeedItem item : delete) {
+ try {
+ DBWriter.deleteFeedMediaOfItem(context, item.getId()).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ int counter = delete.size();
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, String.format(
+ "Auto-delete deleted %d episodes (%d requested)", counter,
+ episodeNumber));
+
+ return counter;
+ }
+
+ /**
+ * Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread.
+ */
+ public static void enqueueAllNewItems(final Context context) {
+ long[] unreadItems = DBReader.getUnreadItemIds(context);
+ DBWriter.addQueueItem(context, unreadItems);
+ }
+
+ /**
+ * Returns the successor of a FeedItem in the queue.
+ *
+ * @param context Used for accessing the DB.
+ * @param itemId ID of the FeedItem
+ * @param queue Used for determining the successor of the item. If this parameter is null, the method will load
+ * the queue from the database in the same thread.
+ * @return Successor of the FeedItem or null if the FeedItem is not in the queue or has no successor.
+ */
+ public static FeedItem getQueueSuccessorOfItem(Context context,
+ final long itemId, List<FeedItem> queue) {
+ FeedItem result = null;
+ if (queue == null) {
+ queue = DBReader.getQueue(context);
+ }
+ if (queue != null) {
+ Iterator<FeedItem> iterator = queue.iterator();
+ while (iterator.hasNext()) {
+ FeedItem item = iterator.next();
+ if (item.getId() == itemId) {
+ if (iterator.hasNext()) {
+ result = iterator.next();
+ }
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Loads the queue from the database and checks if the specified FeedItem is in the queue.
+ * This method should NOT be executed in the GUI thread.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedItemId ID of the FeedItem
+ */
+ public static boolean isInQueue(Context context, final long feedItemId) {
+ List<Long> queue = DBReader.getQueueIDList(context);
+ return QueueAccess.IDListAccess(queue).contains(feedItemId);
+ }
+
+ private static Feed searchFeedByIdentifyingValue(Context context,
+ String identifier) {
+ List<Feed> feeds = DBReader.getFeedList(context);
+ for (Feed feed : feeds) {
+ if (feed.getIdentifyingValue().equals(identifier)) {
+ return feed;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get a FeedItem by its identifying value.
+ */
+ private static FeedItem searchFeedItemByIdentifyingValue(Feed feed,
+ String identifier) {
+ for (FeedItem item : feed.getItems()) {
+ if (item.getIdentifyingValue().equals(identifier)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a new Feed to the database or updates the old version if it already exists. If another Feed with the same
+ * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed.
+ * These FeedItems will be marked as unread.
+ * This method should NOT be executed on the GUI thread.
+ *
+ * @param context Used for accessing the DB.
+ * @param newFeed The new Feed object.
+ * @return The updated Feed from the database if it already existed, or the new Feed from the parameters otherwise.
+ */
+ public static synchronized Feed updateFeed(final Context context,
+ final Feed newFeed) {
+ // Look up feed in the feedslist
+ final Feed savedFeed = searchFeedByIdentifyingValue(context,
+ newFeed.getIdentifyingValue());
+ if (savedFeed == null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Found no existing Feed with title "
+ + newFeed.getTitle() + ". Adding as new one.");
+ // Add a new Feed
+ try {
+ DBWriter.addNewFeed(context, newFeed).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ return newFeed;
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ + " already exists. Syncing new with existing one.");
+
+ Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
+ savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed));
+ if (savedFeed.compareWithOther(newFeed)) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Feed has updated attribute values. Updating old feed's attributes");
+ savedFeed.updateFromOther(newFeed);
+ }
+ // Look for new or updated Items
+ for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
+ final FeedItem item = newFeed.getItems().get(idx);
+ FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed,
+ item.getIdentifyingValue());
+ if (oldItem == null) {
+ // item is new
+ final int i = idx;
+ item.setFeed(savedFeed);
+ savedFeed.getItems().add(i, item);
+ item.setRead(false);
+ } else {
+ oldItem.updateFromOther(item);
+ }
+ }
+ // update attributes
+ savedFeed.setLastUpdate(newFeed.getLastUpdate());
+ savedFeed.setType(newFeed.getType());
+ try {
+ DBWriter.setCompleteFeed(context, savedFeed).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ new Thread() {
+ @Override
+ public void run() {
+ autodownloadUndownloadedItems(context);
+ }
+ }.start();
+ return savedFeed;
+ }
+ }
+
+ /**
+ * Searches the titles of FeedItems of a specific Feed for a given
+ * string.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedID The id of the feed whose items should be searched.
+ * @param query The search string.
+ * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ */
+ public static FutureTask<List<FeedItem>> searchFeedItemTitle(final Context context,
+ final long feedID, final String query) {
+ return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ @Override
+ public void execute(PodDBAdapter adapter) {
+ Cursor searchResult = adapter.searchItemTitles(feedID,
+ query);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
+ DBReader.loadFeedDataOfFeedItemlist(context, items);
+ setResult(items);
+ searchResult.close();
+ }
+ });
+ }
+
+ /**
+ * Searches the descriptions of FeedItems of a specific Feed for a given
+ * string.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedID The id of the feed whose items should be searched.
+ * @param query The search string
+ * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ */
+ public static FutureTask<List<FeedItem>> searchFeedItemDescription(final Context context,
+ final long feedID, final String query) {
+ return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ @Override
+ public void execute(PodDBAdapter adapter) {
+ Cursor searchResult = adapter.searchItemDescriptions(feedID,
+ query);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
+ DBReader.loadFeedDataOfFeedItemlist(context, items);
+ setResult(items);
+ searchResult.close();
+ }
+ });
+ }
+
+ /**
+ * Searches the contentEncoded-value of FeedItems of a specific Feed for a given
+ * string.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedID The id of the feed whose items should be searched.
+ * @param query The search string
+ * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ */
+ public static FutureTask<List<FeedItem>> searchFeedItemContentEncoded(final Context context,
+ final long feedID, final String query) {
+ return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ @Override
+ public void execute(PodDBAdapter adapter) {
+ Cursor searchResult = adapter.searchItemContentEncoded(feedID,
+ query);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
+ DBReader.loadFeedDataOfFeedItemlist(context, items);
+ setResult(items);
+ searchResult.close();
+ }
+ });
+ }
+
+ /**
+ * Searches chapters of the FeedItems of a specific Feed for a given string.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedID The id of the feed whose items should be searched.
+ * @param query The search string
+ * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ */
+ public static FutureTask<List<FeedItem>> searchFeedItemChapters(final Context context,
+ final long feedID, final String query) {
+ return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ @Override
+ public void execute(PodDBAdapter adapter) {
+ Cursor searchResult = adapter.searchItemChapters(feedID,
+ query);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
+ DBReader.loadFeedDataOfFeedItemlist(context, items);
+ setResult(items);
+ searchResult.close();
+ }
+ });
+ }
+
+ /**
+ * A runnable which should be used for database queries. The onCompletion
+ * method is executed on the database executor to handle Cursors correctly.
+ * This class automatically creates a PodDBAdapter object and closes it when
+ * it is no longer in use.
+ */
+ static abstract class QueryTask<T> implements Callable<T> {
+ private T result;
+ private Context context;
+
+ public QueryTask(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public T call() throws Exception {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ execute(adapter);
+ adapter.close();
+ return result;
+ }
+
+ public abstract void execute(PodDBAdapter adapter);
+
+ protected void setResult(T result) {
+ this.result = result;
+ }
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java
new file mode 100644
index 000000000..569411ae2
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/DBWriter.java
@@ -0,0 +1,731 @@
+package de.danoeh.antennapod.storage;
+
+import java.io.File;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import de.danoeh.antennapod.AppConfig;
+import de.danoeh.antennapod.feed.*;
+import de.danoeh.antennapod.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.service.PlaybackService;
+import de.danoeh.antennapod.service.download.DownloadStatus;
+import de.danoeh.antennapod.util.QueueAccess;
+
+/**
+ * Provides methods for writing data to AntennaPod's database.
+ * In general, DBWriter-methods will be executed on an internal ExecutorService.
+ * Some methods return a Future-object which the caller can use for waiting for the method's completion. The returned Future's
+ * will NOT contain any results.
+ * The caller can also use the {@link EventDistributor} in order to be notified about the method's completion asynchronously.
+ * This class will use the {@link EventDistributor} to notify listeners about changes in the database.
+ */
+public class DBWriter {
+ private static final String TAG = "DBWriter";
+
+ private static final ExecutorService dbExec;
+
+ static {
+ dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ });
+ }
+
+ private DBWriter() {
+ }
+
+ /**
+ * Deletes a downloaded FeedMedia file from the storage device.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param mediaId ID of the FeedMedia object whose downloaded file should be deleted.
+ */
+ public static Future<?> deleteFeedMediaOfItem(final Context context,
+ final long mediaId) {
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+
+ final FeedMedia media = DBReader.getFeedMedia(context, mediaId);
+ if (media != null) {
+ boolean result = false;
+ if (media.isDownloaded()) {
+ // delete downloaded media file
+ File mediaFile = new File(media.getFile_url());
+ if (mediaFile.exists()) {
+ result = mediaFile.delete();
+ }
+ media.setDownloaded(false);
+ media.setFile_url(null);
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setMedia(media);
+ adapter.close();
+
+ // If media is currently being played, change playback
+ // type to 'stream' and shutdown playback service
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context);
+ if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
+ if (media.getId() == PlaybackPreferences
+ .getCurrentlyPlayingFeedMediaId()) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(
+ PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM,
+ true);
+ editor.commit();
+ }
+ if (PlaybackPreferences
+ .getCurrentlyPlayingFeedMediaId() == media
+ .getId()) {
+ context.sendBroadcast(new Intent(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Deleting File. Result: " + result);
+ EventDistributor.getInstance().sendQueueUpdateBroadcast();
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ }
+ });
+ }
+
+ /**
+ * Deletes a Feed and all downloaded files of its components like images and downloaded episodes.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feedId ID of the Feed that should be deleted.
+ */
+ public static Future<?> deleteFeed(final Context context, final long feedId) {
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+ DownloadRequester requester = DownloadRequester.getInstance();
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context
+ .getApplicationContext());
+ final Feed feed = DBReader.getFeed(context, feedId);
+ if (feed != null) {
+ if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
+ && PlaybackPreferences.getLastPlayedFeedId() == feed
+ .getId()) {
+ context.sendBroadcast(new Intent(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putLong(
+ PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID,
+ -1);
+ editor.commit();
+ }
+
+ // delete image file
+ if (feed.getImage() != null) {
+ if (feed.getImage().isDownloaded()
+ && feed.getImage().getFile_url() != null) {
+ File imageFile = new File(feed.getImage()
+ .getFile_url());
+ imageFile.delete();
+ } else if (requester.isDownloadingFile(feed.getImage())) {
+ requester.cancelDownload(context, feed.getImage());
+ }
+ }
+ // delete stored media files and mark them as read
+ List<FeedItem> queue = DBReader.getQueue(context);
+ boolean queueWasModified = false;
+ if (feed.getItems() == null) {
+ DBReader.getFeedItemList(context, feed);
+ }
+
+ for (FeedItem item : feed.getItems()) {
+ queueWasModified |= queue.remove(item);
+ if (item.getMedia() != null
+ && item.getMedia().isDownloaded()) {
+ File mediaFile = new File(item.getMedia()
+ .getFile_url());
+ mediaFile.delete();
+ } else if (item.getMedia() != null
+ && requester.isDownloadingFile(item.getMedia())) {
+ requester.cancelDownload(context, item.getMedia());
+ }
+ }
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ if (queueWasModified) {
+ adapter.setQueue(queue);
+ }
+ adapter.removeFeed(feed);
+ adapter.close();
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+ }
+ });
+ }
+
+ /**
+ * Deletes the entire playback history.
+ *
+ * @param context A context that is used for opening a database connection.
+ */
+ public static Future<?> clearPlaybackHistory(final Context context) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.clearPlaybackHistory();
+ adapter.close();
+ EventDistributor.getInstance()
+ .sendPlaybackHistoryUpdateBroadcast();
+ }
+ });
+ }
+
+ /**
+ * Adds a FeedMedia object to the playback history. A FeedMedia object is in the playback history if
+ * its playback completion date is set to a non-null value. This method will set the playback completion date to the
+ * current date regardless of the current value.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param media FeedMedia that should be added to the playback history.
+ */
+ public static Future<?> addItemToPlaybackHistory(final Context context,
+ final FeedMedia media) {
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Adding new item to playback history");
+ media.setPlaybackCompletionDate(new Date());
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedMediaPlaybackCompletionDate(media);
+ adapter.close();
+ EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast();
+
+ }
+ });
+ }
+
+ private static void cleanupDownloadLog(final PodDBAdapter adapter) {
+ final long logSize = adapter.getDownloadLogSize();
+ if (logSize > DBReader.DOWNLOAD_LOG_SIZE) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Cleaning up download log");
+ adapter.removeDownloadLogItems(logSize - DBReader.DOWNLOAD_LOG_SIZE);
+ }
+ }
+
+ /**
+ * Adds a Download status object to the download log.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param status The DownloadStatus object.
+ */
+ public static Future<?> addDownloadStatus(final Context context,
+ final DownloadStatus status) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ adapter.setDownloadStatus(status);
+ cleanupDownloadLog(adapter);
+ adapter.close();
+ EventDistributor.getInstance().sendDownloadLogUpdateBroadcast();
+ }
+ });
+
+ }
+
+ /**
+ * Inserts a FeedItem in the queue at the specified index. The 'read'-attribute of the FeedItem will be set to
+ * true. If the FeedItem is already in the queue, the queue will not be modified.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemId ID of the FeedItem that should be added to the queue.
+ * @param index Destination index. Must be in range 0..queue.size()
+ * @param performAutoDownload True if an auto-download process should be started after the operation
+ * @throws IndexOutOfBoundsException if index < 0 || index >= queue.size()
+ */
+ public static Future<?> addQueueItemAt(final Context context, final long itemId,
+ final int index, final boolean performAutoDownload) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final List<FeedItem> queue = DBReader
+ .getQueue(context, adapter);
+ FeedItem item = null;
+
+ if (queue != null) {
+ boolean queueModified = false;
+ boolean unreadItemsModified = false;
+
+ if (!itemListContains(queue, itemId)) {
+ item = DBReader.getFeedItem(context, itemId);
+ if (item != null) {
+ queue.add(index, item);
+ queueModified = true;
+ if (!item.isRead()) {
+ item.setRead(true);
+ unreadItemsModified = true;
+ }
+ }
+ }
+ if (queueModified) {
+ adapter.setQueue(queue);
+ EventDistributor.getInstance()
+ .sendQueueUpdateBroadcast();
+ }
+ if (unreadItemsModified && item != null) {
+ adapter.setSingleFeedItem(item);
+ EventDistributor.getInstance()
+ .sendUnreadItemsUpdateBroadcast();
+ }
+ }
+ adapter.close();
+ if (performAutoDownload) {
+
+ new Thread() {
+ @Override
+ public void run() {
+ DBTasks.autodownloadUndownloadedItems(context);
+
+ }
+ }.start();
+ }
+
+ }
+ });
+
+ }
+
+ /**
+ * Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
+ * If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemIds IDs of the FeedItem objects that should be added to the queue.
+ */
+ public static Future<?> addQueueItem(final Context context,
+ final long... itemIds) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ if (itemIds.length > 0) {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(context,
+ adapter);
+
+ if (queue != null) {
+ boolean queueModified = false;
+ boolean unreadItemsModified = false;
+ List<FeedItem> itemsToSave = new LinkedList<FeedItem>();
+ for (int i = 0; i < itemIds.length; i++) {
+ if (!itemListContains(queue, itemIds[i])) {
+ final FeedItem item = DBReader.getFeedItem(
+ context, itemIds[i]);
+
+ if (item != null) {
+ queue.add(item);
+ queueModified = true;
+ if (!item.isRead()) {
+ item.setRead(true);
+ itemsToSave.add(item);
+ unreadItemsModified = true;
+ }
+ }
+ }
+ }
+ if (queueModified) {
+ adapter.setQueue(queue);
+ EventDistributor.getInstance()
+ .sendQueueUpdateBroadcast();
+ }
+ if (unreadItemsModified) {
+ adapter.setFeedItemlist(itemsToSave);
+ EventDistributor.getInstance()
+ .sendUnreadItemsUpdateBroadcast();
+ }
+ }
+ adapter.close();
+ new Thread() {
+ @Override
+ public void run() {
+ DBTasks.autodownloadUndownloadedItems(context);
+
+ }
+ }.start();
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Removes all FeedItem objects from the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ */
+ public static Future<?> clearQueue(final Context context) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.clearQueue();
+ adapter.close();
+
+ EventDistributor.getInstance().sendQueueUpdateBroadcast();
+ }
+ });
+ }
+
+ /**
+ * Removes a FeedItem object from the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemId ID of the FeedItem that should be removed.
+ * @param performAutoDownload true if an auto-download process should be started after the operation.
+ */
+ public static Future<?> removeQueueItem(final Context context,
+ final long itemId, final boolean performAutoDownload) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final List<FeedItem> queue = DBReader
+ .getQueue(context, adapter);
+ FeedItem item = null;
+
+ if (queue != null) {
+ boolean queueModified = false;
+ QueueAccess queueAccess = QueueAccess.ItemListAccess(queue);
+ if (queueAccess.contains(itemId)) {
+ item = DBReader.getFeedItem(context, itemId);
+ if (item != null) {
+ queueModified = queueAccess.remove(itemId);
+ }
+ }
+ if (queueModified) {
+ adapter.setQueue(queue);
+ EventDistributor.getInstance()
+ .sendQueueUpdateBroadcast();
+ } else {
+ Log.w(TAG, "Queue was not modified by call to removeQueueItem");
+ }
+ } else {
+ Log.e(TAG, "removeQueueItem: Could not load queue");
+ }
+ adapter.close();
+ if (performAutoDownload) {
+
+ new Thread() {
+ @Override
+ public void run() {
+ DBTasks.autodownloadUndownloadedItems(context);
+
+ }
+ }.start();
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Changes the position of a FeedItem in the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param from Source index. Must be in range 0..queue.size()-1.
+ * @param to Destination index. Must be in range 0..queue.size()-1.
+ * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
+ * false if the caller wants to avoid unexpected updates of the GUI.
+ * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
+ */
+ public static Future<?> moveQueueItem(final Context context, final int from,
+ final int to, final boolean broadcastUpdate) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final List<FeedItem> queue = DBReader
+ .getQueue(context, adapter);
+
+ if (queue != null) {
+ if (from >= 0 && from < queue.size() && to >= 0
+ && to < queue.size()) {
+
+ final FeedItem item = queue.remove(from);
+ queue.add(to, item);
+
+ adapter.setQueue(queue);
+ if (broadcastUpdate) {
+ EventDistributor.getInstance()
+ .sendQueueUpdateBroadcast();
+ }
+
+ }
+ } else {
+ Log.e(TAG, "moveQueueItem: Could not load queue");
+ }
+ adapter.close();
+ }
+ });
+ }
+
+ /**
+ * Sets the 'read'-attribute of a FeedItem to the specified value.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param item The FeedItem object
+ * @param read New value of the 'read'-attribute
+ * @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object.
+ * If the FeedItem has no FeedMedia object, this parameter will be ignored.
+ */
+ public static Future<?> markItemRead(Context context, FeedItem item, boolean read, boolean resetMediaPosition) {
+ long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0;
+ return markItemRead(context, item.getId(), read, mediaId, resetMediaPosition);
+ }
+
+ /**
+ * Sets the 'read'-attribute of a FeedItem to the specified value.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemId ID of the FeedItem
+ * @param read New value of the 'read'-attribute
+ */
+ public static Future<?> markItemRead(final Context context, final long itemId,
+ final boolean read) {
+ return markItemRead(context, itemId, read, 0, false);
+ }
+
+ private static Future<?> markItemRead(final Context context, final long itemId,
+ final boolean read, final long mediaId,
+ final boolean resetMediaPosition) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedItemRead(read, itemId, mediaId,
+ resetMediaPosition);
+ adapter.close();
+
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ });
+ }
+
+ /**
+ * Sets the 'read'-attribute of all FeedItems of a specific Feed to true.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feedId ID of the Feed.
+ */
+ public static Future<?> markFeedRead(final Context context, final long feedId) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor itemCursor = adapter.getAllItemsOfFeedCursor(feedId);
+ long[] itemIds = new long[itemCursor.getCount()];
+ itemCursor.moveToFirst();
+ for (int i = 0; i < itemIds.length; i++) {
+ itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ itemCursor.moveToNext();
+ }
+ itemCursor.close();
+ adapter.setFeedItemRead(true, itemIds);
+ adapter.close();
+
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ });
+
+ }
+
+ /**
+ * Sets the 'read'-attribute of all FeedItems to true.
+ *
+ * @param context A context that is used for opening a database connection.
+ */
+ public static Future<?> markAllItemsRead(final Context context) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor itemCursor = adapter.getUnreadItemsCursor();
+ long[] itemIds = new long[itemCursor.getCount()];
+ itemCursor.moveToFirst();
+ for (int i = 0; i < itemIds.length; i++) {
+ itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ itemCursor.moveToNext();
+ }
+ itemCursor.close();
+ adapter.setFeedItemRead(true, itemIds);
+ adapter.close();
+
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ });
+
+ }
+
+ static Future<?> addNewFeed(final Context context, final Feed feed) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+ });
+ }
+
+ static Future<?> setCompleteFeed(final Context context, final Feed feed) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+ });
+
+ }
+
+ /**
+ * Saves a FeedMedia object in the database. This method will save all attributes of the FeedMedia object. The
+ * contents of FeedComponent-attributes (e.g. the FeedMedia's 'item'-attribute) will not be saved.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param media The FeedMedia object.
+ */
+ public static Future<?> setFeedMedia(final Context context,
+ final FeedMedia media) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setMedia(media);
+ adapter.close();
+ }
+ });
+ }
+
+ /**
+ * Saves the 'position' and 'duration' attributes of a FeedMedia object
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param media The FeedMedia object.
+ */
+ public static Future<?> setFeedMediaPlaybackInformation(final Context context, final FeedMedia media) {
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedMediaPlaybackInformation(media);
+ adapter.close();
+ }
+ });
+ }
+
+ /**
+ * Saves a FeedItem object in the database. This method will save all attributes of the FeedItem object including
+ * the content of FeedComponent-attributes.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param item The FeedItem object.
+ */
+ public static Future<?> setFeedItem(final Context context,
+ final FeedItem item) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setSingleFeedItem(item);
+ adapter.close();
+ }
+ });
+ }
+
+ /**
+ * Saves a FeedImage object in the database. This method will save all attributes of the FeedImage object. The
+ * contents of FeedComponent-attributes (e.g. the FeedImages's 'feed'-attribute) will not be saved.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param image The FeedImage object.
+ */
+ public static Future<?> setFeedImage(final Context context,
+ final FeedImage image) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setImage(image);
+ adapter.close();
+ }
+ });
+ }
+
+ private static boolean itemListContains(List<FeedItem> items, long itemId) {
+ for (FeedItem item : items) {
+ if (item.getId() == itemId) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java
index 29bd764dd..013162f0c 100644
--- a/src/de/danoeh/antennapod/storage/DownloadRequester.java
+++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java
@@ -5,6 +5,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
import android.content.Context;
import android.content.Intent;
@@ -17,280 +18,303 @@ import de.danoeh.antennapod.feed.FeedFile;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.util.FileNameGenerator;
import de.danoeh.antennapod.util.URLChecker;
public class DownloadRequester {
- private static final String TAG = "DownloadRequester";
-
- public static String IMAGE_DOWNLOADPATH = "images/";
- public static String FEED_DOWNLOADPATH = "cache/";
- public static String MEDIA_DOWNLOADPATH = "media/";
-
- private static DownloadRequester downloader;
-
- Map<String, FeedFile> downloads;
-
- private DownloadRequester() {
- downloads = new ConcurrentHashMap<String, FeedFile>();
- }
-
- public static DownloadRequester getInstance() {
- if (downloader == null) {
- downloader = new DownloadRequester();
- }
- return downloader;
- }
-
- private void download(Context context, FeedFile item, File dest,
- boolean overwriteIfExists) {
- if (!isDownloadingFile(item)) {
- if (!isFilenameAvailable(dest.toString()) || dest.exists()) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Filename already used.");
- if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
- boolean result = dest.delete();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Deleting file. Result: " + result);
- } else {
- // find different name
- File newDest = null;
- for (int i = 1; i < Integer.MAX_VALUE; i++) {
- String newName = FilenameUtils.getBaseName(dest
- .getName())
- + "-"
- + i
- + "."
- + FilenameUtils.getExtension(dest.getName());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Testing filename " + newName);
- newDest = new File(dest.getParent(), newName);
- if (!newDest.exists()
- && isFilenameAvailable(newDest.toString())) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "File doesn't exist yet. Using "
- + newName);
- break;
- }
- }
- if (newDest != null) {
- dest = newDest;
- }
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Requesting download of url " + item.getDownload_url());
- item.setDownload_url(URLChecker.prepareURL(item.getDownload_url()));
- 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 {
- Intent queueIntent = new Intent(
- DownloadService.ACTION_ENQUEUE_DOWNLOAD);
- queueIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
- context.sendBroadcast(queueIntent);
- }
- EventDistributor.getInstance().sendDownloadQueuedBroadcast();
- } else {
- Log.e(TAG, "URL " + item.getDownload_url()
- + " is already being downloaded");
- }
- }
-
- /**
- * Returns true if a filename is available and false if it has already been
- * taken by another requested download.
- */
- private boolean isFilenameAvailable(String path) {
- for (String key : downloads.keySet()) {
- FeedFile f = downloads.get(key);
- if (f.getFile_url() != null && f.getFile_url().equals(path)) {
- if (AppConfig.DEBUG)
- Log.d(TAG, path
- + " is already used by another requested download");
- return false;
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, path + " is available as a download destination");
- return true;
- }
-
- public void downloadFeed(Context context, Feed feed)
- throws DownloadRequestException {
- if (feedFileValid(feed)) {
- download(context, feed, new File(getFeedfilePath(context),
- getFeedfileName(feed)), true);
- }
- }
-
- public void downloadImage(Context context, FeedImage image)
- throws DownloadRequestException {
- if (feedFileValid(image)) {
- download(context, image, new File(getImagefilePath(context),
- getImagefileName(image)), true);
- }
- }
-
- public void downloadMedia(Context context, FeedMedia feedmedia)
- throws DownloadRequestException {
- if (feedFileValid(feedmedia)) {
- download(context, feedmedia,
- new File(getMediafilePath(context, feedmedia),
- getMediafilename(feedmedia)), false);
- }
- }
-
- /**
- * Throws a DownloadRequestException if the feedfile or the download url of
- * the feedfile is null.
- *
- * @throws DownloadRequestException
- */
- private boolean feedFileValid(FeedFile f) throws DownloadRequestException {
- if (f == null) {
- throw new DownloadRequestException("Feedfile was null");
- } else if (f.getDownload_url() == null) {
- throw new DownloadRequestException("File has no download URL");
- } else {
- return true;
- }
- }
-
- /**
- * Cancels a running download.
- * */
- 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 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");
- 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.values()) {
- if (f.getClass() == Feed.class) {
- return true;
- }
- }
- return false;
- }
-
- /** Checks if feedfile is in the downloads list */
- public boolean isDownloadingFile(FeedFile item) {
- 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) {
- return downloads.get(downloadUrl) != null;
- }
-
- public boolean hasNoDownloads() {
- return downloads.isEmpty();
- }
-
- public FeedFile getDownloadAt(int index) {
- return downloads.get(index);
- }
-
- /** Remove an object from the downloads-list of the requester. */
- public void removeDownload(FeedFile f) {
- 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 */
- public int getNumberOfDownloads() {
- return downloads.size();
- }
-
- public String getFeedfilePath(Context context)
- throws DownloadRequestException {
- return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
- .toString() + "/";
- }
-
- public String getFeedfileName(Feed feed) {
- String filename = feed.getDownload_url();
- if (feed.getTitle() != null && !feed.getTitle().isEmpty()) {
- filename = feed.getTitle();
- }
- return "feed-" + FileNameGenerator.generateFileName(filename);
- }
-
- public String getImagefilePath(Context context)
- throws DownloadRequestException {
- return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH)
- .toString() + "/";
- }
-
- public String getImagefileName(FeedImage image) {
- String filename = image.getDownload_url();
- if (image.getFeed() != null && image.getFeed().getTitle() != null) {
- filename = image.getFeed().getTitle();
- }
- return "image-" + FileNameGenerator.generateFileName(filename);
- }
-
- public String getMediafilePath(Context context, FeedMedia media)
- throws DownloadRequestException {
- File externalStorage = getExternalFilesDirOrThrowException(
- context,
- MEDIA_DOWNLOADPATH
- + FileNameGenerator.generateFileName(media.getItem()
- .getFeed().getTitle()) + "/");
- return externalStorage.toString();
- }
-
- private File getExternalFilesDirOrThrowException(Context context,
- String type) throws DownloadRequestException {
- File result = UserPreferences.getDataFolder(context, type);
- if (result == null) {
- throw new DownloadRequestException(
- "Failed to access external storage");
- }
- return result;
- }
-
- public String getMediafilename(FeedMedia media) {
- return URLUtil.guessFileName(media.getDownload_url(), null,
- media.getMime_type());
- }
-
+ private static final String TAG = "DownloadRequester";
+
+ public static final String IMAGE_DOWNLOADPATH = "images/";
+ public static final String FEED_DOWNLOADPATH = "cache/";
+ public static final String MEDIA_DOWNLOADPATH = "media/";
+
+ private static DownloadRequester downloader;
+
+ Map<String, DownloadRequest> downloads;
+
+ private DownloadRequester() {
+ downloads = new ConcurrentHashMap<String, DownloadRequest>();
+ }
+
+ public static synchronized DownloadRequester getInstance() {
+ if (downloader == null) {
+ downloader = new DownloadRequester();
+ }
+ return downloader;
+ }
+
+ private void download(Context context, FeedFile item, File dest,
+ boolean overwriteIfExists) {
+ if (!isDownloadingFile(item)) {
+ if (!isFilenameAvailable(dest.toString()) || dest.exists()) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Filename already used.");
+ if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
+ boolean result = dest.delete();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Deleting file. Result: " + result);
+ } else {
+ // find different name
+ File newDest = null;
+ for (int i = 1; i < Integer.MAX_VALUE; i++) {
+ String newName = FilenameUtils.getBaseName(dest
+ .getName())
+ + "-"
+ + i
+ + FilenameUtils.EXTENSION_SEPARATOR
+ + FilenameUtils.getExtension(dest.getName());
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Testing filename " + newName);
+ newDest = new File(dest.getParent(), newName);
+ if (!newDest.exists()
+ && isFilenameAvailable(newDest.toString())) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "File doesn't exist yet. Using "
+ + newName);
+ break;
+ }
+ }
+ if (newDest != null) {
+ dest = newDest;
+ }
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Requesting download of url " + item.getDownload_url());
+ item.setDownload_url(URLChecker.prepareURL(item.getDownload_url()));
+
+ DownloadRequest request = new DownloadRequest(dest.toString(),
+ item.getDownload_url(), item.getHumanReadableIdentifier(),
+ item.getId(), item.getTypeAsInt());
+
+ downloads.put(request.getSource(), request);
+
+ Intent launchIntent = new Intent(context, DownloadService.class);
+ launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
+ context.startService(launchIntent);
+ EventDistributor.getInstance().sendDownloadQueuedBroadcast();
+ } else {
+ Log.e(TAG, "URL " + item.getDownload_url()
+ + " is already being downloaded");
+ }
+ }
+
+ /**
+ * Returns true if a filename is available and false if it has already been
+ * taken by another requested download.
+ */
+ private boolean isFilenameAvailable(String path) {
+ for (String key : downloads.keySet()) {
+ DownloadRequest r = downloads.get(key);
+ if (StringUtils.equals(r.getDestination(), path)) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, path
+ + " is already used by another requested download");
+ return false;
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, path + " is available as a download destination");
+ return true;
+ }
+
+ public void downloadFeed(Context context, Feed feed)
+ throws DownloadRequestException {
+ if (feedFileValid(feed)) {
+ download(context, feed, new File(getFeedfilePath(context),
+ getFeedfileName(feed)), true);
+ }
+ }
+
+ public void downloadImage(Context context, FeedImage image)
+ throws DownloadRequestException {
+ if (feedFileValid(image)) {
+ download(context, image, new File(getImagefilePath(context),
+ getImagefileName(image)), true);
+ }
+ }
+
+ public void downloadMedia(Context context, FeedMedia feedmedia)
+ throws DownloadRequestException {
+ if (feedFileValid(feedmedia)) {
+ download(context, feedmedia,
+ new File(getMediafilePath(context, feedmedia),
+ getMediafilename(feedmedia)), false);
+ }
+ }
+
+ /**
+ * Throws a DownloadRequestException if the feedfile or the download url of
+ * the feedfile is null.
+ *
+ * @throws DownloadRequestException
+ */
+ private boolean feedFileValid(FeedFile f) throws DownloadRequestException {
+ if (f == null) {
+ throw new DownloadRequestException("Feedfile was null");
+ } else if (f.getDownload_url() == null) {
+ throw new DownloadRequestException("File has no download URL");
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Cancels a running download.
+ */
+ 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 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");
+ 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 (DownloadRequest r : downloads.values()) {
+ if (r.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if feedfile is in the downloads list
+ */
+ public boolean isDownloadingFile(FeedFile item) {
+ if (item.getDownload_url() != null) {
+ return downloads.containsKey(item.getDownload_url());
+ }
+ return false;
+ }
+
+ public DownloadRequest 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) {
+ return downloads.get(downloadUrl) != null;
+ }
+
+ public boolean hasNoDownloads() {
+ return downloads.isEmpty();
+ }
+
+ /**
+ * Remove an object from the downloads-list of the requester.
+ */
+ public void removeDownload(DownloadRequest r) {
+ if (downloads.remove(r.getSource()) == null) {
+ Log.e(TAG,
+ "Could not remove object with url " + r.getSource());
+ }
+ }
+
+ /**
+ * Get the number of uncompleted Downloads
+ */
+ public int getNumberOfDownloads() {
+ return downloads.size();
+ }
+
+ public String getFeedfilePath(Context context)
+ throws DownloadRequestException {
+ return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
+ .toString() + "/";
+ }
+
+ public String getFeedfileName(Feed feed) {
+ String filename = feed.getDownload_url();
+ if (feed.getTitle() != null && !feed.getTitle().isEmpty()) {
+ filename = feed.getTitle();
+ }
+ return "feed-" + FileNameGenerator.generateFileName(filename);
+ }
+
+ public String getImagefilePath(Context context)
+ throws DownloadRequestException {
+ return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH)
+ .toString() + "/";
+ }
+
+ public String getImagefileName(FeedImage image) {
+ String filename = image.getDownload_url();
+ if (image.getFeed() != null && image.getFeed().getTitle() != null) {
+ filename = image.getFeed().getTitle();
+ }
+ return "image-" + FileNameGenerator.generateFileName(filename);
+ }
+
+ public String getMediafilePath(Context context, FeedMedia media)
+ throws DownloadRequestException {
+ File externalStorage = getExternalFilesDirOrThrowException(
+ context,
+ MEDIA_DOWNLOADPATH
+ + FileNameGenerator.generateFileName(media.getItem()
+ .getFeed().getTitle()) + "/");
+ return externalStorage.toString();
+ }
+
+ private File getExternalFilesDirOrThrowException(Context context,
+ String type) throws DownloadRequestException {
+ File result = UserPreferences.getDataFolder(context, type);
+ if (result == null) {
+ throw new DownloadRequestException(
+ "Failed to access external storage");
+ }
+ return result;
+ }
+
+ public String getMediafilename(FeedMedia media) {
+ String filename;
+ String titleBaseFilename = "";
+
+ // Try to generate the filename by the item title
+ if (media.getItem() != null && media.getItem().getTitle() != null) {
+ String title = media.getItem().getTitle();
+ // Delete reserved characters
+ titleBaseFilename = title.replaceAll("[\\\\/%\\?\\*:|<>\"\\p{Cntrl}]", "");
+ titleBaseFilename = titleBaseFilename.trim();
+ }
+
+ String URLBaseFilename = URLUtil.guessFileName(media.getDownload_url(),
+ null, media.getMime_type());;
+
+ if (titleBaseFilename != "") {
+ // Append extension
+ filename = titleBaseFilename + FilenameUtils.EXTENSION_SEPARATOR +
+ FilenameUtils.getExtension(URLBaseFilename);
+ } else {
+ // Fall back on URL file name
+ filename = URLBaseFilename;
+ }
+ return filename;
+ }
}
diff --git a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java
new file mode 100644
index 000000000..6b79dd144
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java
@@ -0,0 +1,42 @@
+package de.danoeh.antennapod.storage;
+
+import java.util.Date;
+
+/**
+ * Contains information about a feed's items.
+ */
+public class FeedItemStatistics {
+ private long feedID;
+ private int numberOfItems;
+ private int numberOfNewItems;
+ private int numberOfInProgressItems;
+ private Date lastUpdate;
+
+ public FeedItemStatistics(long feedID, int numberOfItems, int numberOfNewItems, int numberOfInProgressItems, Date lastUpdate) {
+ this.feedID = feedID;
+ this.numberOfItems = numberOfItems;
+ this.numberOfNewItems = numberOfNewItems;
+ this.numberOfInProgressItems = numberOfInProgressItems;
+ this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
+ }
+
+ public long getFeedID() {
+ return feedID;
+ }
+
+ public int getNumberOfItems() {
+ return numberOfItems;
+ }
+
+ public int getNumberOfNewItems() {
+ return numberOfNewItems;
+ }
+
+ public int getNumberOfInProgressItems() {
+ return numberOfInProgressItems;
+ }
+
+ public Date getLastUpdate() {
+ return (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/FeedSearcher.java b/src/de/danoeh/antennapod/storage/FeedSearcher.java
new file mode 100644
index 000000000..e7aa93f83
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/FeedSearcher.java
@@ -0,0 +1,57 @@
+package de.danoeh.antennapod.storage;
+
+import android.content.Context;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.SearchResult;
+import de.danoeh.antennapod.util.comparator.SearchResultValueComparator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Performs search on Feeds and FeedItems
+ */
+public class FeedSearcher {
+ private static final String TAG = "FeedSearcher";
+
+
+ /**
+ * Performs a search in all feeds or one specific feed.
+ */
+ public static List<SearchResult> performSearch(final Context context,
+ final String query, final long selectedFeed) {
+ final int values[] = {0, 0, 1, 2};
+ final String[] subtitles = {context.getString(R.string.found_in_shownotes_label),
+ context.getString(R.string.found_in_shownotes_label),
+ context.getString(R.string.found_in_chapters_label),
+ context.getString(R.string.found_in_title_label)};
+
+ List<SearchResult> result = new ArrayList<SearchResult>();
+
+ FutureTask<List<FeedItem>>[] tasks = new FutureTask[4];
+ (tasks[0] = DBTasks.searchFeedItemContentEncoded(context, selectedFeed, query)).run();
+ (tasks[1] = DBTasks.searchFeedItemDescription(context, selectedFeed, query)).run();
+ (tasks[2] = DBTasks.searchFeedItemChapters(context, selectedFeed, query)).run();
+ (tasks[3] = DBTasks.searchFeedItemTitle(context, selectedFeed, query)).run();
+ try {
+ for (int i = 0; i < tasks.length; i++) {
+ FutureTask task = tasks[i];
+ List<FeedItem> items = (List<FeedItem>) task.get();
+ for (FeedItem item : items) {
+ result.add(new SearchResult(item, values[i], subtitles[i]));
+ }
+
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ Collections.sort(result, new SearchResultValueComparator());
+ return result;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java
index 420264840..d36d6184c 100644
--- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java
+++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java
@@ -14,772 +14,1096 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.service.download.DownloadStatus;
+
+// TODO Remove media column from feeditem table
/**
* Implements methods for accessing the database
- * */
+ */
public class PodDBAdapter {
- private static final String TAG = "PodDBAdapter";
- private static final int DATABASE_VERSION = 8;
- private static final String DATABASE_NAME = "Antennapod.db";
-
- /** Maximum number of arguments for IN-operator. */
- public static final int IN_OPERATOR_MAXIMUM = 800;
-
- // ----------- Column indices
- // ----------- General indices
- public static final int KEY_ID_INDEX = 0;
- public static final int KEY_TITLE_INDEX = 1;
- public static final int KEY_FILE_URL_INDEX = 2;
- public static final int KEY_DOWNLOAD_URL_INDEX = 3;
- public static final int KEY_DOWNLOADED_INDEX = 4;
- public static final int KEY_LINK_INDEX = 5;
- public static final int KEY_DESCRIPTION_INDEX = 6;
- public static final int KEY_PAYMENT_LINK_INDEX = 7;
- // ----------- Feed indices
- public static final int KEY_LAST_UPDATE_INDEX = 8;
- public static final int KEY_LANGUAGE_INDEX = 9;
- public static final int KEY_AUTHOR_INDEX = 10;
- public static final int KEY_IMAGE_INDEX = 11;
- public static final int KEY_TYPE_INDEX = 12;
- public static final int KEY_FEED_IDENTIFIER_INDEX = 13;
- // ----------- FeedItem indices
- public static final int KEY_CONTENT_ENCODED_INDEX = 2;
- public static final int KEY_PUBDATE_INDEX = 3;
- public static final int KEY_READ_INDEX = 4;
- public static final int KEY_MEDIA_INDEX = 8;
- public static final int KEY_FEED_INDEX = 9;
- public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10;
- public static final int KEY_ITEM_IDENTIFIER_INDEX = 11;
- // ---------- FeedMedia indices
- public static final int KEY_DURATION_INDEX = 1;
- public static final int KEY_POSITION_INDEX = 5;
- public static final int KEY_SIZE_INDEX = 6;
- public static final int KEY_MIME_TYPE_INDEX = 7;
- public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8;
- // --------- Download log indices
- public static final int KEY_FEEDFILE_INDEX = 1;
- public static final int KEY_FEEDFILETYPE_INDEX = 2;
- public static final int KEY_REASON_INDEX = 3;
- public static final int KEY_SUCCESSFUL_INDEX = 4;
- public static final int KEY_COMPLETION_DATE_INDEX = 5;
- public static final int KEY_REASON_DETAILED_INDEX = 6;
- public static final int KEY_DOWNLOADSTATUS_TITLE_INDEX = 7;
- // --------- Queue indices
- public static final int KEY_FEEDITEM_INDEX = 1;
- public static final int KEY_QUEUE_FEED_INDEX = 2;
- // --------- Chapters indices
- public static final int KEY_CHAPTER_START_INDEX = 2;
- public static final int KEY_CHAPTER_FEEDITEM_INDEX = 3;
- public static final int KEY_CHAPTER_LINK_INDEX = 4;
- public static final int KEY_CHAPTER_TYPE_INDEX = 5;
-
- // Key-constants
- public static final String KEY_ID = "id";
- public static final String KEY_TITLE = "title";
- public static final String KEY_NAME = "name";
- public static final String KEY_LINK = "link";
- public static final String KEY_DESCRIPTION = "description";
- public static final String KEY_FILE_URL = "file_url";
- public static final String KEY_DOWNLOAD_URL = "download_url";
- public static final String KEY_PUBDATE = "pubDate";
- public static final String KEY_READ = "read";
- public static final String KEY_DURATION = "duration";
- public static final String KEY_POSITION = "position";
- public static final String KEY_SIZE = "filesize";
- public static final String KEY_MIME_TYPE = "mime_type";
- public static final String KEY_IMAGE = "image";
- public static final String KEY_FEED = "feed";
- public static final String KEY_MEDIA = "media";
- public static final String KEY_DOWNLOADED = "downloaded";
- public static final String KEY_LASTUPDATE = "last_update";
- public static final String KEY_FEEDFILE = "feedfile";
- public static final String KEY_REASON = "reason";
- public static final String KEY_SUCCESSFUL = "successful";
- public static final String KEY_FEEDFILETYPE = "feedfile_type";
- public static final String KEY_COMPLETION_DATE = "completion_date";
- public static final String KEY_FEEDITEM = "feeditem";
- public static final String KEY_CONTENT_ENCODED = "content_encoded";
- public static final String KEY_PAYMENT_LINK = "payment_link";
- public static final String KEY_START = "start";
- public static final String KEY_LANGUAGE = "language";
- public static final String KEY_AUTHOR = "author";
- public static final String KEY_HAS_CHAPTERS = "has_simple_chapters";
- public static final String KEY_TYPE = "type";
- public static final String KEY_ITEM_IDENTIFIER = "item_identifier";
- public static final String KEY_FEED_IDENTIFIER = "feed_identifier";
- public static final String KEY_REASON_DETAILED = "reason_detailed";
- public static final String KEY_DOWNLOADSTATUS_TITLE = "title";
- public static final String KEY_CHAPTER_TYPE = "type";
- public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date";
-
- // Table names
- public static final String TABLE_NAME_FEEDS = "Feeds";
- public static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
- public static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
- public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
- public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
- public static final String TABLE_NAME_QUEUE = "Queue";
- public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
-
- // SQL Statements for creating new tables
- private static final String TABLE_PRIMARY_KEY = KEY_ID
- + " INTEGER PRIMARY KEY AUTOINCREMENT ,";
-
- private static final String CREATE_TABLE_FEEDS = "CREATE TABLE "
- + TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
- + KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT,"
- + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
- + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
- + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT,"
- + KEY_FEED_IDENTIFIER + " TEXT)";;
-
- private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
- + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_CONTENT_ENCODED + " TEXT," + KEY_PUBDATE
- + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT,"
- + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
- + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
- + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)";
-
- private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
- + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
- + KEY_DOWNLOADED + " INTEGER)";
-
- private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE "
- + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION
- + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL
- + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION
- + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
- + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER)";
-
- private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
- + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
- + " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON
- + " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE
- + " INTEGER," + KEY_REASON_DETAILED + " TEXT,"
- + KEY_DOWNLOADSTATUS_TITLE + " TEXT)";
-
- private static final String CREATE_TABLE_QUEUE = "CREATE TABLE "
- + TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
- + KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)";
-
- private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE "
- + TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
- + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
-
- private SQLiteDatabase db;
- private final Context context;
- private PodDBHelper helper;
-
- /**
- * Select all columns from the feeditems-table except description and
- * content-encoded.
- */
- private static final String[] SEL_FI_SMALL = { KEY_ID, KEY_TITLE,
- KEY_PUBDATE, KEY_READ, KEY_LINK, KEY_PAYMENT_LINK, KEY_MEDIA,
- KEY_FEED, KEY_HAS_CHAPTERS, KEY_ITEM_IDENTIFIER };
-
- // column indices for SEL_FI_SMALL
-
- public static final int IDX_FI_SMALL_ID = 0;
- public static final int IDX_FI_SMALL_TITLE = 1;
- public static final int IDX_FI_SMALL_PUBDATE = 2;
- public static final int IDX_FI_SMALL_READ = 3;
- public static final int IDX_FI_SMALL_LINK = 4;
- public static final int IDX_FI_SMALL_PAYMENT_LINK = 5;
- public static final int IDX_FI_SMALL_MEDIA = 6;
- public static final int IDX_FI_SMALL_FEED = 7;
- public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8;
- public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9;
-
- /** Select id, description and content-encoded column from feeditems. */
- public static final String[] SEL_FI_EXTRA = { KEY_ID, KEY_DESCRIPTION,
- KEY_CONTENT_ENCODED, KEY_FEED };
-
- // column indices for SEL_FI_EXTRA
-
- public static final int IDX_FI_EXTRA_ID = 0;
- public static final int IDX_FI_EXTRA_DESCRIPTION = 1;
- public static final int IDX_FI_EXTRA_CONTENT_ENCODED = 2;
- public static final int IDX_FI_EXTRA_FEED = 3;
-
- public PodDBAdapter(Context c) {
- this.context = c;
- helper = new PodDBHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- public PodDBAdapter open() {
- if (db == null || !db.isOpen() || db.isReadOnly()) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Opening DB");
- try {
- db = helper.getWritableDatabase();
- } catch (SQLException ex) {
- ex.printStackTrace();
- db = helper.getReadableDatabase();
- }
- }
- return this;
- }
-
- public void close() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Closing DB");
- db.close();
- }
-
- /**
- * Inserts or updates a feed entry
- *
- * @return the id of the entry
- * */
- public long setFeed(Feed feed) {
- ContentValues values = new ContentValues();
- values.put(KEY_TITLE, feed.getTitle());
- values.put(KEY_LINK, feed.getLink());
- values.put(KEY_DESCRIPTION, feed.getDescription());
- values.put(KEY_PAYMENT_LINK, feed.getPaymentLink());
- values.put(KEY_AUTHOR, feed.getAuthor());
- values.put(KEY_LANGUAGE, feed.getLanguage());
- if (feed.getImage() != null) {
- if (feed.getImage().getId() == 0) {
- setImage(feed.getImage());
- }
- values.put(KEY_IMAGE, feed.getImage().getId());
- }
-
- values.put(KEY_FILE_URL, feed.getFile_url());
- values.put(KEY_DOWNLOAD_URL, feed.getDownload_url());
- values.put(KEY_DOWNLOADED, feed.isDownloaded());
- values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime());
- values.put(KEY_TYPE, feed.getType());
- values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier());
- if (feed.getId() == 0) {
- // Create new entry
- if (AppConfig.DEBUG)
- Log.d(this.toString(), "Inserting new Feed into db");
- feed.setId(db.insert(TABLE_NAME_FEEDS, null, values));
- } else {
- if (AppConfig.DEBUG)
- Log.d(this.toString(), "Updating existing Feed in db");
- db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?",
- new String[] { Long.toString(feed.getId()) });
- }
- return feed.getId();
- }
-
- /**
- * Inserts or updates an image entry
- *
- * @return the id of the entry
- * */
- public long setImage(FeedImage image) {
- ContentValues values = new ContentValues();
- values.put(KEY_TITLE, image.getTitle());
- values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
- values.put(KEY_DOWNLOADED, image.isDownloaded());
- values.put(KEY_FILE_URL, image.getFile_url());
- if (image.getId() == 0) {
- image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
- } else {
- db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
- new String[] { String.valueOf(image.getId()) });
- }
- return image.getId();
- }
-
- /**
- * Inserts or updates an image entry
- *
- * @return the id of the entry
- */
- public long setMedia(FeedMedia media) {
- ContentValues values = new ContentValues();
- values.put(KEY_DURATION, media.getDuration());
- values.put(KEY_POSITION, media.getPosition());
- values.put(KEY_SIZE, media.getSize());
- values.put(KEY_MIME_TYPE, media.getMime_type());
- values.put(KEY_DOWNLOAD_URL, media.getDownload_url());
- values.put(KEY_DOWNLOADED, media.isDownloaded());
- values.put(KEY_FILE_URL, media.getFile_url());
- if (media.getPlaybackCompletionDate() != null) {
- values.put(KEY_PLAYBACK_COMPLETION_DATE, media
- .getPlaybackCompletionDate().getTime());
- } else {
- values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
- }
- if (media.getId() == 0) {
- media.setId(db.insert(TABLE_NAME_FEED_MEDIA, null, values));
- } else {
- db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
- new String[] { String.valueOf(media.getId()) });
- }
- return media.getId();
- }
-
- /**
- * Insert all FeedItems of a feed and the feed object itself in a single
- * transaction
- */
- public void setCompleteFeed(Feed feed) {
- db.beginTransaction();
- setFeed(feed);
- for (FeedItem item : feed.getItemsArray()) {
- setFeedItem(item);
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- }
-
- public long setSingleFeedItem(FeedItem item) {
- db.beginTransaction();
- long result = setFeedItem(item);
- db.setTransactionSuccessful();
- db.endTransaction();
- return result;
- }
-
- /**
- * Inserts or updates a feeditem entry
- *
- * @return the id of the entry
- */
- private long setFeedItem(FeedItem item) {
- ContentValues values = new ContentValues();
- values.put(KEY_TITLE, item.getTitle());
- values.put(KEY_LINK, item.getLink());
- if (item.getDescription() != null) {
- values.put(KEY_DESCRIPTION, item.getDescription());
- }
- if (item.getContentEncoded() != null) {
- values.put(KEY_CONTENT_ENCODED, item.getContentEncoded());
- }
- values.put(KEY_PUBDATE, item.getPubDate().getTime());
- values.put(KEY_PAYMENT_LINK, item.getPaymentLink());
- if (item.getMedia() != null) {
- if (item.getMedia().getId() == 0) {
- setMedia(item.getMedia());
- }
- values.put(KEY_MEDIA, item.getMedia().getId());
- }
- if (item.getFeed().getId() == 0) {
- setFeed(item.getFeed());
- }
- values.put(KEY_FEED, item.getFeed().getId());
- values.put(KEY_READ, item.isRead());
- values.put(KEY_HAS_CHAPTERS, item.getChapters() != null);
- values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
- if (item.getId() == 0) {
- item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values));
- } else {
- db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?",
- new String[] { String.valueOf(item.getId()) });
- }
- if (item.getChapters() != null) {
- setChapters(item);
- }
- return item.getId();
- }
-
- public void setChapters(FeedItem item) {
- ContentValues values = new ContentValues();
- for (Chapter chapter : item.getChapters()) {
- values.put(KEY_TITLE, chapter.getTitle());
- values.put(KEY_START, chapter.getStart());
- values.put(KEY_FEEDITEM, item.getId());
- values.put(KEY_LINK, chapter.getLink());
- values.put(KEY_CHAPTER_TYPE, chapter.getChapterType());
- if (chapter.getId() == 0) {
- chapter.setId(db
- .insert(TABLE_NAME_SIMPLECHAPTERS, null, values));
- } else {
- db.update(TABLE_NAME_SIMPLECHAPTERS, values, KEY_ID + "=?",
- new String[] { String.valueOf(chapter.getId()) });
- }
- }
- }
-
- /**
- * Inserts or updates a download status.
- * */
- public long setDownloadStatus(DownloadStatus status) {
- ContentValues values = new ContentValues();
- if (status.getFeedFile() != null) {
- values.put(KEY_FEEDFILE, status.getFeedFile().getId());
- if (status.getFeedFile().getClass() == Feed.class) {
- values.put(KEY_FEEDFILETYPE, Feed.FEEDFILETYPE_FEED);
- } else if (status.getFeedFile().getClass() == FeedImage.class) {
- values.put(KEY_FEEDFILETYPE, FeedImage.FEEDFILETYPE_FEEDIMAGE);
- } else if (status.getFeedFile().getClass() == FeedMedia.class) {
- values.put(KEY_FEEDFILETYPE, FeedMedia.FEEDFILETYPE_FEEDMEDIA);
- }
- }
- values.put(KEY_REASON, status.getReason());
- values.put(KEY_SUCCESSFUL, status.isSuccessful());
- values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime());
- values.put(KEY_REASON_DETAILED, status.getReasonDetailed());
- values.put(KEY_DOWNLOADSTATUS_TITLE, status.getTitle());
- if (status.getId() == 0) {
- status.setId(db.insert(TABLE_NAME_DOWNLOAD_LOG, null, values));
- } else {
- db.update(TABLE_NAME_DOWNLOAD_LOG, values, KEY_ID + "=?",
- new String[] { String.valueOf(status.getId()) });
- }
-
- return status.getId();
- }
-
- public void setQueue(List<FeedItem> queue) {
- ContentValues values = new ContentValues();
- db.beginTransaction();
- db.delete(TABLE_NAME_QUEUE, null, null);
- for (int i = 0; i < queue.size(); i++) {
- FeedItem item = queue.get(i);
- values.put(KEY_ID, i);
- values.put(KEY_FEEDITEM, item.getId());
- values.put(KEY_FEED, item.getFeed().getId());
- db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values,
- SQLiteDatabase.CONFLICT_REPLACE);
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- }
-
- public void removeFeedMedia(FeedMedia media) {
- db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?",
- new String[] { String.valueOf(media.getId()) });
- }
-
- public void removeChaptersOfItem(FeedItem item) {
- db.delete(TABLE_NAME_SIMPLECHAPTERS, KEY_FEEDITEM + "=?",
- new String[] { String.valueOf(item.getId()) });
- }
-
- public void removeFeedImage(FeedImage image) {
- db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?",
- new String[] { String.valueOf(image.getId()) });
- }
-
- /** Remove a FeedItem and its FeedMedia entry. */
- public void removeFeedItem(FeedItem item) {
- if (item.getMedia() != null) {
- removeFeedMedia(item.getMedia());
- }
- if (item.getChapters() != null) {
- removeChaptersOfItem(item);
- }
- db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?",
- new String[] { String.valueOf(item.getId()) });
- }
-
- /** Remove a feed with all its FeedItems and Media entries. */
- public void removeFeed(Feed feed) {
- db.beginTransaction();
- if (feed.getImage() != null) {
- removeFeedImage(feed.getImage());
- }
- for (FeedItem item : feed.getItemsArray()) {
- removeFeedItem(item);
- }
- db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
- new String[] { String.valueOf(feed.getId()) });
- db.setTransactionSuccessful();
- db.endTransaction();
- }
-
- public void removeDownloadStatus(DownloadStatus remove) {
- db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_ID + "=?",
- new String[] { String.valueOf(remove.getId()) });
- }
-
- /**
- * Get all Feeds from the Feed Table.
- *
- * @return The cursor of the query
- * */
- public final Cursor getAllFeedsCursor() {
- open();
- Cursor c = db.query(TABLE_NAME_FEEDS, null, null, null, null, null,
- null);
- return c;
- }
-
- /**
- * Returns a cursor with all FeedItems of a Feed. Uses SEL_FI_SMALL
- *
- * @param feed
- * The feed you want to get the FeedItems from.
- * @return The cursor of the query
- * */
- public final Cursor getAllItemsOfFeedCursor(final Feed feed) {
- open();
- Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
- + "=?", new String[] { String.valueOf(feed.getId()) }, null,
- null, null);
- return c;
- }
-
- /** Return a cursor with the SEL_FI_EXTRA selection of a single feeditem. */
- public final Cursor getExtraInformationOfItem(final FeedItem item) {
- open();
- Cursor c = db
- .query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_ID + "=?",
- new String[] { String.valueOf(item.getId()) }, null,
- null, null);
- return c;
- }
-
- /**
- * Returns a cursor for a DB query in the FeedMedia table for a given ID.
- *
- * @param item
- * The item you want to get the FeedMedia from
- * @return The cursor of the query
- * */
- public final Cursor getFeedMediaOfItemCursor(final FeedItem item) {
- open();
- Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?",
- new String[] { String.valueOf(item.getMedia().getId()) }, null,
- null, null);
- return c;
- }
-
- /**
- * Returns a cursor for a DB query in the FeedImages table for a given ID.
- *
- * @param id
- * ID of the FeedImage
- * @return The cursor of the query
- * */
- public final Cursor getImageOfFeedCursor(final long id) {
- open();
- Cursor c = db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + "=?",
- new String[] { String.valueOf(id) }, null, null, null);
- return c;
- }
-
- public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) {
- open();
- Cursor c = db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM
- + "=?", new String[] { String.valueOf(item.getId()) }, null,
- null, null);
- return c;
- }
-
- public final Cursor getDownloadLogCursor() {
- open();
- Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
- null, null);
- return c;
- }
-
- public final Cursor getQueueCursor() {
- open();
- Cursor c = db.query(TABLE_NAME_QUEUE, null, null, null, null, null,
- null);
- return c;
- }
-
- public final Cursor getFeedMediaCursor(String... mediaIds) {
- int length = mediaIds.length;
- if (length > IN_OPERATOR_MAXIMUM) {
- Log.w(TAG, "Length of id array is larger than "
- + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors");
- int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1;
- Cursor[] cursors = new Cursor[numCursors];
- for (int i = 0; i < numCursors; i++) {
- int neededLength = 0;
- String[] parts = null;
- final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM;
-
- if (elementsLeft >= IN_OPERATOR_MAXIMUM) {
- neededLength = IN_OPERATOR_MAXIMUM;
- parts = Arrays.copyOfRange(mediaIds, i
- * IN_OPERATOR_MAXIMUM, (i + 1)
- * IN_OPERATOR_MAXIMUM);
- } else {
- neededLength = elementsLeft;
- parts = Arrays.copyOfRange(mediaIds, i
- * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM)
- + neededLength);
- }
-
- cursors[i] = db.rawQuery("SELECT * FROM "
- + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_ID + " IN "
- + buildInOperator(neededLength), parts);
- }
- return new MergeCursor(cursors);
- } else {
- return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + " IN "
- + buildInOperator(length), mediaIds, null, null, null);
- }
- }
-
- /** Builds an IN-operator argument depending on the number of items. */
- private String buildInOperator(int size) {
- StringBuffer buffer = new StringBuffer("(");
- for (int i = 0; i <= size; i++) {
- buffer.append("?,");
- }
- buffer.append("?)");
- return buffer.toString();
- }
-
- /**
- * Searches the DB for a FeedImage of the given id.
- *
- * @param id
- * The id of the object
- * @return The found object
- * */
- public final FeedImage getFeedImage(final long id) throws SQLException {
- Cursor cursor = this.getImageOfFeedCursor(id);
- if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
- throw new SQLException("No FeedImage found at index: " + id);
- }
- FeedImage image = new FeedImage(id, cursor.getString(cursor
- .getColumnIndex(KEY_TITLE)), cursor.getString(cursor
- .getColumnIndex(KEY_FILE_URL)), cursor.getString(cursor
- .getColumnIndex(KEY_DOWNLOAD_URL)), cursor.getInt(cursor
- .getColumnIndex(KEY_DOWNLOADED)) > 0);
- cursor.close();
- return image;
- }
-
- /**
- * Uses DatabaseUtils to escape a search query and removes ' at the
- * beginning and the end of the string returned by the escape method.
- */
- private String prepareSearchQuery(String query) {
- StringBuilder builder = new StringBuilder();
- DatabaseUtils.appendEscapedSQLString(builder, query);
- builder.deleteCharAt(0);
- builder.deleteCharAt(builder.length() - 1);
- return builder.toString();
- }
-
- /**
- * Searches for the given query in the description of all items or the items
- * of a specified feed.
- *
- * @return A cursor with all search results in SEL_FI_EXTRA selection.
- * */
- public Cursor searchItemDescriptions(Feed feed, String query) {
- if (feed != null) {
- // search items in specific feed
- return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_FEED
- + "=? AND " + KEY_DESCRIPTION + " LIKE '%"
- + prepareSearchQuery(query) + "%'",
- new String[] { String.valueOf(feed.getId()) }, null, null,
- null);
- } else {
- // search through all items
- return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA,
- KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query)
- + "%'", null, null, null, null);
- }
- }
-
- /**
- * Searches for the given query in the content-encoded field of all items or
- * the items of a specified feed.
- *
- * @return A cursor with all search results in SEL_FI_EXTRA selection.
- * */
- public Cursor searchItemContentEncoded(Feed feed, String query) {
- if (feed != null) {
- // search items in specific feed
- return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_FEED
- + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%"
- + prepareSearchQuery(query) + "%'",
- new String[] { String.valueOf(feed.getId()) }, null, null,
- null);
- } else {
- // search through all items
- return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA,
- KEY_CONTENT_ENCODED + " LIKE '%"
- + prepareSearchQuery(query) + "%'", null, null,
- null, null);
- }
- }
-
- /** Helper class for opening the Antennapod database. */
- private static class PodDBHelper extends SQLiteOpenHelper {
- /**
- * Constructor.
- *
- * @param context
- * Context to use
- * @param name
- * Name of the database
- * @param factory
- * to use for creating cursor objects
- * @param version
- * number of the database
- * */
- public PodDBHelper(final Context context, final String name,
- final CursorFactory factory, final int version) {
- super(context, name, factory, version);
- }
-
- @Override
- public void onCreate(final SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_FEEDS);
- db.execSQL(CREATE_TABLE_FEED_ITEMS);
- db.execSQL(CREATE_TABLE_FEED_IMAGES);
- db.execSQL(CREATE_TABLE_FEED_MEDIA);
- db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
- db.execSQL(CREATE_TABLE_QUEUE);
- db.execSQL(CREATE_TABLE_SIMPLECHAPTERS);
- }
-
- @Override
- public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
- final int newVersion) {
- Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to "
- + newVersion + ".");
- if (oldVersion <= 1) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN "
- + KEY_TYPE + " TEXT");
- }
- if (oldVersion <= 2) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS
- + " ADD COLUMN " + KEY_LINK + " TEXT");
- }
- if (oldVersion <= 3) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS
- + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT");
- }
- if (oldVersion <= 4) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN "
- + KEY_FEED_IDENTIFIER + " TEXT");
- }
- if (oldVersion <= 5) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG
- + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT");
- db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG
- + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT");
- }
- if (oldVersion <= 6) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS
- + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER");
- }
- if (oldVersion <= 7) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA
- + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE
- + " INTEGER");
- }
- }
- }
+ private static final String TAG = "PodDBAdapter";
+ private static final int DATABASE_VERSION = 9;
+ public static final String DATABASE_NAME = "Antennapod.db";
+
+ /**
+ * Maximum number of arguments for IN-operator.
+ */
+ public static final int IN_OPERATOR_MAXIMUM = 800;
+
+ /**
+ * Maximum number of entries per search request.
+ */
+ public static final int SEARCH_LIMIT = 30;
+
+ // ----------- Column indices
+ // ----------- General indices
+ public static final int KEY_ID_INDEX = 0;
+ public static final int KEY_TITLE_INDEX = 1;
+ public static final int KEY_FILE_URL_INDEX = 2;
+ public static final int KEY_DOWNLOAD_URL_INDEX = 3;
+ public static final int KEY_DOWNLOADED_INDEX = 4;
+ public static final int KEY_LINK_INDEX = 5;
+ public static final int KEY_DESCRIPTION_INDEX = 6;
+ public static final int KEY_PAYMENT_LINK_INDEX = 7;
+ // ----------- Feed indices
+ public static final int KEY_LAST_UPDATE_INDEX = 8;
+ public static final int KEY_LANGUAGE_INDEX = 9;
+ public static final int KEY_AUTHOR_INDEX = 10;
+ public static final int KEY_IMAGE_INDEX = 11;
+ public static final int KEY_TYPE_INDEX = 12;
+ public static final int KEY_FEED_IDENTIFIER_INDEX = 13;
+ // ----------- FeedItem indices
+ public static final int KEY_CONTENT_ENCODED_INDEX = 2;
+ public static final int KEY_PUBDATE_INDEX = 3;
+ public static final int KEY_READ_INDEX = 4;
+ public static final int KEY_MEDIA_INDEX = 8;
+ public static final int KEY_FEED_INDEX = 9;
+ public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10;
+ public static final int KEY_ITEM_IDENTIFIER_INDEX = 11;
+ // ---------- FeedMedia indices
+ public static final int KEY_DURATION_INDEX = 1;
+ public static final int KEY_POSITION_INDEX = 5;
+ public static final int KEY_SIZE_INDEX = 6;
+ public static final int KEY_MIME_TYPE_INDEX = 7;
+ public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8;
+ public static final int KEY_MEDIA_FEEDITEM_INDEX = 9;
+ // --------- Download log indices
+ public static final int KEY_FEEDFILE_INDEX = 1;
+ public static final int KEY_FEEDFILETYPE_INDEX = 2;
+ public static final int KEY_REASON_INDEX = 3;
+ public static final int KEY_SUCCESSFUL_INDEX = 4;
+ public static final int KEY_COMPLETION_DATE_INDEX = 5;
+ public static final int KEY_REASON_DETAILED_INDEX = 6;
+ public static final int KEY_DOWNLOADSTATUS_TITLE_INDEX = 7;
+ // --------- Queue indices
+ public static final int KEY_FEEDITEM_INDEX = 1;
+ public static final int KEY_QUEUE_FEED_INDEX = 2;
+ // --------- Chapters indices
+ public static final int KEY_CHAPTER_START_INDEX = 2;
+ public static final int KEY_CHAPTER_FEEDITEM_INDEX = 3;
+ public static final int KEY_CHAPTER_LINK_INDEX = 4;
+ public static final int KEY_CHAPTER_TYPE_INDEX = 5;
+
+ // Key-constants
+ public static final String KEY_ID = "id";
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_NAME = "name";
+ public static final String KEY_LINK = "link";
+ public static final String KEY_DESCRIPTION = "description";
+ public static final String KEY_FILE_URL = "file_url";
+ public static final String KEY_DOWNLOAD_URL = "download_url";
+ public static final String KEY_PUBDATE = "pubDate";
+ public static final String KEY_READ = "read";
+ public static final String KEY_DURATION = "duration";
+ public static final String KEY_POSITION = "position";
+ public static final String KEY_SIZE = "filesize";
+ public static final String KEY_MIME_TYPE = "mime_type";
+ public static final String KEY_IMAGE = "image";
+ public static final String KEY_FEED = "feed";
+ public static final String KEY_MEDIA = "media";
+ public static final String KEY_DOWNLOADED = "downloaded";
+ public static final String KEY_LASTUPDATE = "last_update";
+ public static final String KEY_FEEDFILE = "feedfile";
+ public static final String KEY_REASON = "reason";
+ public static final String KEY_SUCCESSFUL = "successful";
+ public static final String KEY_FEEDFILETYPE = "feedfile_type";
+ public static final String KEY_COMPLETION_DATE = "completion_date";
+ public static final String KEY_FEEDITEM = "feeditem";
+ public static final String KEY_CONTENT_ENCODED = "content_encoded";
+ public static final String KEY_PAYMENT_LINK = "payment_link";
+ public static final String KEY_START = "start";
+ public static final String KEY_LANGUAGE = "language";
+ public static final String KEY_AUTHOR = "author";
+ public static final String KEY_HAS_CHAPTERS = "has_simple_chapters";
+ public static final String KEY_TYPE = "type";
+ public static final String KEY_ITEM_IDENTIFIER = "item_identifier";
+ public static final String KEY_FEED_IDENTIFIER = "feed_identifier";
+ public static final String KEY_REASON_DETAILED = "reason_detailed";
+ public static final String KEY_DOWNLOADSTATUS_TITLE = "title";
+ public static final String KEY_CHAPTER_TYPE = "type";
+ public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date";
+
+ // Table names
+ public static final String TABLE_NAME_FEEDS = "Feeds";
+ public static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
+ public static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
+ public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
+ public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
+ public static final String TABLE_NAME_QUEUE = "Queue";
+ public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
+
+ // SQL Statements for creating new tables
+ private static final String TABLE_PRIMARY_KEY = KEY_ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT ,";
+
+ private static final String CREATE_TABLE_FEEDS = "CREATE TABLE "
+ + TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
+ + KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT,"
+ + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
+ + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT,"
+ + KEY_FEED_IDENTIFIER + " TEXT)";
+ ;
+
+ private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ + " TEXT," + KEY_CONTENT_ENCODED + " TEXT," + KEY_PUBDATE
+ + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT,"
+ + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)";
+
+ private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
+ + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
+ + KEY_DOWNLOADED + " INTEGER)";
+
+ private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE "
+ + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION
+ + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL
+ + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION
+ + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
+ + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ + KEY_FEEDITEM + " INTEGER)";
+
+ private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
+ + " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON
+ + " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE
+ + " INTEGER," + KEY_REASON_DETAILED + " TEXT,"
+ + KEY_DOWNLOADSTATUS_TITLE + " TEXT)";
+
+ private static final String CREATE_TABLE_QUEUE = "CREATE TABLE "
+ + TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
+ + KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)";
+
+ private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE "
+ + TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
+ + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
+
+ private SQLiteDatabase db;
+ private final Context context;
+ private PodDBHelper helper;
+
+ /**
+ * Select all columns from the feeditems-table except description and
+ * content-encoded.
+ */
+ private static final String[] SEL_FI_SMALL = {
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_TITLE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_LINK,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_FEED,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER};
+
+ /**
+ * Contains SEL_FI_SMALL as comma-separated list. Useful for raw queries.
+ */
+ private static final String SEL_FI_SMALL_STR;
+
+ static {
+ String selFiSmall = Arrays.toString(SEL_FI_SMALL);
+ SEL_FI_SMALL_STR = selFiSmall.substring(1, selFiSmall.length() - 1);
+ }
+
+ // column indices for SEL_FI_SMALL
+
+ public static final int IDX_FI_SMALL_ID = 0;
+ public static final int IDX_FI_SMALL_TITLE = 1;
+ public static final int IDX_FI_SMALL_PUBDATE = 2;
+ public static final int IDX_FI_SMALL_READ = 3;
+ public static final int IDX_FI_SMALL_LINK = 4;
+ public static final int IDX_FI_SMALL_PAYMENT_LINK = 5;
+ public static final int IDX_FI_SMALL_MEDIA = 6;
+ public static final int IDX_FI_SMALL_FEED = 7;
+ public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8;
+ public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9;
+
+ /**
+ * Select id, description and content-encoded column from feeditems.
+ */
+ private static final String[] SEL_FI_EXTRA = {KEY_ID, KEY_DESCRIPTION,
+ KEY_CONTENT_ENCODED, KEY_FEED};
+
+ // column indices for SEL_FI_EXTRA
+
+ public static final int IDX_FI_EXTRA_ID = 0;
+ public static final int IDX_FI_EXTRA_DESCRIPTION = 1;
+ public static final int IDX_FI_EXTRA_CONTENT_ENCODED = 2;
+ public static final int IDX_FI_EXTRA_FEED = 3;
+
+ static PodDBHelper dbHelperSingleton;
+
+ private static synchronized PodDBHelper getDbHelperSingleton(Context appContext) {
+ if (dbHelperSingleton == null) {
+ dbHelperSingleton = new PodDBHelper(appContext, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+ return dbHelperSingleton;
+ }
+
+ public PodDBAdapter(Context c) {
+ this.context = c;
+ helper = getDbHelperSingleton(c.getApplicationContext());
+ }
+
+ public PodDBAdapter open() {
+ if (db == null || !db.isOpen() || db.isReadOnly()) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Opening DB");
+ try {
+ db = helper.getWritableDatabase();
+ } catch (SQLException ex) {
+ ex.printStackTrace();
+ db = helper.getReadableDatabase();
+ }
+ }
+ return this;
+ }
+
+ public void close() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Closing DB");
+ //db.close();
+ }
+
+ public static boolean deleteDatabase(Context context) {
+ Log.w(TAG, "Deleting database");
+ dbHelperSingleton.close();
+ dbHelperSingleton = null;
+ return context.deleteDatabase(DATABASE_NAME);
+ }
+
+ /**
+ * Inserts or updates a feed entry
+ *
+ * @return the id of the entry
+ */
+ public long setFeed(Feed feed) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_TITLE, feed.getTitle());
+ values.put(KEY_LINK, feed.getLink());
+ values.put(KEY_DESCRIPTION, feed.getDescription());
+ values.put(KEY_PAYMENT_LINK, feed.getPaymentLink());
+ values.put(KEY_AUTHOR, feed.getAuthor());
+ values.put(KEY_LANGUAGE, feed.getLanguage());
+ if (feed.getImage() != null) {
+ if (feed.getImage().getId() == 0) {
+ setImage(feed.getImage());
+ }
+ values.put(KEY_IMAGE, feed.getImage().getId());
+ }
+
+ values.put(KEY_FILE_URL, feed.getFile_url());
+ values.put(KEY_DOWNLOAD_URL, feed.getDownload_url());
+ values.put(KEY_DOWNLOADED, feed.isDownloaded());
+ values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime());
+ values.put(KEY_TYPE, feed.getType());
+ values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier());
+ if (feed.getId() == 0) {
+ // Create new entry
+ if (AppConfig.DEBUG)
+ Log.d(this.toString(), "Inserting new Feed into db");
+ feed.setId(db.insert(TABLE_NAME_FEEDS, null, values));
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(this.toString(), "Updating existing Feed in db");
+ db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?",
+ new String[]{String.valueOf(feed.getId())});
+ }
+ return feed.getId();
+ }
+
+ /**
+ * Inserts or updates an image entry
+ *
+ * @return the id of the entry
+ */
+ public long setImage(FeedImage image) {
+ db.beginTransaction();
+ ContentValues values = new ContentValues();
+ values.put(KEY_TITLE, image.getTitle());
+ values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
+ values.put(KEY_DOWNLOADED, image.isDownloaded());
+ values.put(KEY_FILE_URL, image.getFile_url());
+ if (image.getId() == 0) {
+ image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
+ } else {
+ db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
+ new String[]{String.valueOf(image.getId())});
+ }
+ if (image.getFeed() != null && image.getFeed().getId() != 0) {
+ values.clear();
+ values.put(KEY_IMAGE, image.getId());
+ db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getFeed().getId())});
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ return image.getId();
+ }
+
+ /**
+ * Inserts or updates an image entry
+ *
+ * @return the id of the entry
+ */
+ public long setMedia(FeedMedia media) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_DURATION, media.getDuration());
+ values.put(KEY_POSITION, media.getPosition());
+ values.put(KEY_SIZE, media.getSize());
+ values.put(KEY_MIME_TYPE, media.getMime_type());
+ values.put(KEY_DOWNLOAD_URL, media.getDownload_url());
+ values.put(KEY_DOWNLOADED, media.isDownloaded());
+ values.put(KEY_FILE_URL, media.getFile_url());
+
+ if (media.getPlaybackCompletionDate() != null) {
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, media
+ .getPlaybackCompletionDate().getTime());
+ } else {
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
+ }
+ if (media.getItem() != null) {
+ values.put(KEY_FEEDITEM, media.getItem().getId());
+ }
+ if (media.getId() == 0) {
+ media.setId(db.insert(TABLE_NAME_FEED_MEDIA, null, values));
+ } else {
+ db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
+ new String[]{String.valueOf(media.getId())});
+ }
+ return media.getId();
+ }
+
+ public void setFeedMediaPlaybackInformation(FeedMedia media) {
+ if (media.getId() != 0) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_POSITION, media.getPosition());
+ values.put(KEY_DURATION, media.getDuration());
+ db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
+ new String[]{String.valueOf(media.getId())});
+ } else {
+ Log.e(TAG, "setFeedMediaPlaybackInformation: ID of media was 0");
+ }
+ }
+
+ public void setFeedMediaPlaybackCompletionDate(FeedMedia media) {
+ if (media.getId() != 0) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime());
+ db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
+ new String[]{String.valueOf(media.getId())});
+ } else {
+ Log.e(TAG, "setFeedMediaPlaybackCompletionDate: ID of media was 0");
+ }
+ }
+
+ /**
+ * Insert all FeedItems of a feed and the feed object itself in a single
+ * transaction
+ */
+ public void setCompleteFeed(Feed feed) {
+ db.beginTransaction();
+ setFeed(feed);
+ if (feed.getItems() != null) {
+ for (FeedItem item : feed.getItems()) {
+ setFeedItem(item, false);
+ }
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void setFeedItemlist(List<FeedItem> items) {
+ db.beginTransaction();
+ for (FeedItem item : items) {
+ setFeedItem(item, true);
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public long setSingleFeedItem(FeedItem item) {
+ db.beginTransaction();
+ long result = setFeedItem(item, true);
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ return result;
+ }
+
+ /**
+ * Inserts or updates a feeditem entry
+ * @param item The FeedItem
+ * @param saveFeed true if the Feed of the item should also be saved. This should be set to
+ * false if the method is executed on a list of FeedItems of the same Feed.
+ * @return the id of the entry
+ */
+ private long setFeedItem(FeedItem item, boolean saveFeed) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_TITLE, item.getTitle());
+ values.put(KEY_LINK, item.getLink());
+ if (item.getDescription() != null) {
+ values.put(KEY_DESCRIPTION, item.getDescription());
+ }
+ if (item.getContentEncoded() != null) {
+ values.put(KEY_CONTENT_ENCODED, item.getContentEncoded());
+ }
+ values.put(KEY_PUBDATE, item.getPubDate().getTime());
+ values.put(KEY_PAYMENT_LINK, item.getPaymentLink());
+ if (saveFeed && item.getFeed() != null) {
+ setFeed(item.getFeed());
+ }
+ values.put(KEY_FEED, item.getFeed().getId());
+ values.put(KEY_READ, item.isRead());
+ values.put(KEY_HAS_CHAPTERS, item.getChapters() != null);
+ values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
+ if (item.getId() == 0) {
+ item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values));
+ } else {
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?",
+ new String[]{String.valueOf(item.getId())});
+ }
+ if (item.getMedia() != null) {
+ setMedia(item.getMedia());
+ }
+ if (item.getChapters() != null) {
+ setChapters(item);
+ }
+ return item.getId();
+ }
+
+ public void setFeedItemRead(boolean read, long itemId, long mediaId,
+ boolean resetMediaPosition) {
+ db.beginTransaction();
+ ContentValues values = new ContentValues();
+
+ values.put(KEY_READ, read);
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)});
+
+ if (resetMediaPosition) {
+ values.clear();
+ values.put(KEY_POSITION, 0);
+ db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
+ }
+
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void setFeedItemRead(boolean read, long... itemIds) {
+ db.beginTransaction();
+ ContentValues values = new ContentValues();
+ for (long id : itemIds) {
+ values.clear();
+ values.put(KEY_READ, read);
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)});
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void setChapters(FeedItem item) {
+ ContentValues values = new ContentValues();
+ for (Chapter chapter : item.getChapters()) {
+ values.put(KEY_TITLE, chapter.getTitle());
+ values.put(KEY_START, chapter.getStart());
+ values.put(KEY_FEEDITEM, item.getId());
+ values.put(KEY_LINK, chapter.getLink());
+ values.put(KEY_CHAPTER_TYPE, chapter.getChapterType());
+ if (chapter.getId() == 0) {
+ chapter.setId(db
+ .insert(TABLE_NAME_SIMPLECHAPTERS, null, values));
+ } else {
+ db.update(TABLE_NAME_SIMPLECHAPTERS, values, KEY_ID + "=?",
+ new String[]{String.valueOf(chapter.getId())});
+ }
+ }
+ }
+
+ /**
+ * Inserts or updates a download status.
+ */
+ public long setDownloadStatus(DownloadStatus status) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_FEEDFILE, status.getFeedfileId());
+ values.put(KEY_FEEDFILETYPE, status.getFeedfileType());
+ values.put(KEY_REASON, status.getReason().getCode());
+ values.put(KEY_SUCCESSFUL, status.isSuccessful());
+ values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime());
+ values.put(KEY_REASON_DETAILED, status.getReasonDetailed());
+ values.put(KEY_DOWNLOADSTATUS_TITLE, status.getTitle());
+ if (status.getId() == 0) {
+ status.setId(db.insert(TABLE_NAME_DOWNLOAD_LOG, null, values));
+ } else {
+ db.update(TABLE_NAME_DOWNLOAD_LOG, values, KEY_ID + "=?",
+ new String[]{String.valueOf(status.getId())});
+ }
+
+ return status.getId();
+ }
+
+ public long getDownloadLogSize() {
+ Cursor result = db.rawQuery("SELECT COUNT(?) AS ? FROM ?",
+ new String[]{KEY_ID, KEY_ID, TABLE_NAME_DOWNLOAD_LOG});
+ long count = result.getLong(KEY_ID_INDEX);
+ result.close();
+ return count;
+ }
+
+ public void removeDownloadLogItems(long count) {
+ if (count > 0) {
+ db.rawQuery("DELETE FROM ? ORDER BY ? ASC LIMIT ?",
+ new String[]{TABLE_NAME_DOWNLOAD_LOG,
+ KEY_COMPLETION_DATE, String.valueOf(count)});
+ }
+ }
+
+ public void setQueue(List<FeedItem> queue) {
+ ContentValues values = new ContentValues();
+ db.beginTransaction();
+ db.delete(TABLE_NAME_QUEUE, null, null);
+ for (int i = 0; i < queue.size(); i++) {
+ FeedItem item = queue.get(i);
+ values.put(KEY_ID, i);
+ values.put(KEY_FEEDITEM, item.getId());
+ values.put(KEY_FEED, item.getFeed().getId());
+ db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE);
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void clearQueue() {
+ db.delete(TABLE_NAME_QUEUE, null, null);
+ }
+
+ public void removeFeedMedia(FeedMedia media) {
+ db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?",
+ new String[]{String.valueOf(media.getId())});
+ }
+
+ public void removeChaptersOfItem(FeedItem item) {
+ db.delete(TABLE_NAME_SIMPLECHAPTERS, KEY_FEEDITEM + "=?",
+ new String[]{String.valueOf(item.getId())});
+ }
+
+ public void removeFeedImage(FeedImage image) {
+ db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?",
+ new String[]{String.valueOf(image.getId())});
+ }
+
+ /**
+ * Remove a FeedItem and its FeedMedia entry.
+ */
+ public void removeFeedItem(FeedItem item) {
+ if (item.getMedia() != null) {
+ removeFeedMedia(item.getMedia());
+ }
+ if (item.getChapters() != null) {
+ removeChaptersOfItem(item);
+ }
+ db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?",
+ new String[]{String.valueOf(item.getId())});
+ }
+
+ /**
+ * Remove a feed with all its FeedItems and Media entries.
+ */
+ public void removeFeed(Feed feed) {
+ db.beginTransaction();
+ if (feed.getImage() != null) {
+ removeFeedImage(feed.getImage());
+ }
+ if (feed.getItems() != null) {
+ for (FeedItem item : feed.getItems()) {
+ removeFeedItem(item);
+ }
+ }
+ db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
+ new String[]{String.valueOf(feed.getId())});
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void removeDownloadStatus(DownloadStatus remove) {
+ db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_ID + "=?",
+ new String[]{String.valueOf(remove.getId())});
+ }
+
+ public void clearPlaybackHistory() {
+ ContentValues values = new ContentValues();
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
+ db.update(TABLE_NAME_FEED_MEDIA, values, null, null);
+ }
+
+ /**
+ * Get all Feeds from the Feed Table.
+ *
+ * @return The cursor of the query
+ */
+ public final Cursor getAllFeedsCursor() {
+ Cursor c = db.query(TABLE_NAME_FEEDS, null, null, null, null, null,
+ KEY_TITLE + " ASC");
+ return c;
+ }
+
+ public final Cursor getExpiredFeedsCursor(long expirationTime) {
+ Cursor c = db.query(TABLE_NAME_FEEDS, null, "?<?", new String[]{
+ KEY_LASTUPDATE, String.valueOf(System.currentTimeMillis() - expirationTime)}, null, null,
+ null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor with all FeedItems of a Feed. Uses SEL_FI_SMALL
+ *
+ * @param feed The feed you want to get the FeedItems from.
+ * @return The cursor of the query
+ */
+ public final Cursor getAllItemsOfFeedCursor(final Feed feed) {
+ return getAllItemsOfFeedCursor(feed.getId());
+ }
+
+ public final Cursor getAllItemsOfFeedCursor(final long feedId) {
+ Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
+ + "=?", new String[]{String.valueOf(feedId)}, null, null,
+ null);
+ return c;
+ }
+
+ /**
+ * Return a cursor with the SEL_FI_EXTRA selection of a single feeditem.
+ */
+ public final Cursor getExtraInformationOfItem(final FeedItem item) {
+ Cursor c = db
+ .query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_ID + "=?",
+ new String[]{String.valueOf(item.getId())}, null,
+ null, null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor for a DB query in the FeedMedia table for a given ID.
+ *
+ * @param item The item you want to get the FeedMedia from
+ * @return The cursor of the query
+ */
+ public final Cursor getFeedMediaOfItemCursor(final FeedItem item) {
+ Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?",
+ new String[]{String.valueOf(item.getMedia().getId())}, null,
+ null, null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor for a DB query in the FeedImages table for a given ID.
+ *
+ * @param id ID of the FeedImage
+ * @return The cursor of the query
+ */
+ public final Cursor getImageOfFeedCursor(final long id) {
+ Cursor c = db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + "=?",
+ new String[]{String.valueOf(id)}, null, null, null);
+ return c;
+ }
+
+ public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) {
+ Cursor c = db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM
+ + "=?", new String[]{String.valueOf(item.getId())}, null,
+ null, null);
+ return c;
+ }
+
+ public final Cursor getDownloadLogCursor(final int limit) {
+ Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
+ null, KEY_COMPLETION_DATE + " DESC LIMIT " + limit);
+ return c;
+ }
+
+ /**
+ * Returns a cursor which contains all feed items in the queue. The returned
+ * cursor uses the SEL_FI_SMALL selection.
+ */
+ public final Cursor getQueueCursor() {
+ Object[] args = (Object[]) new String[]{
+ SEL_FI_SMALL_STR + "," + TABLE_NAME_QUEUE + "." + KEY_ID,
+ TABLE_NAME_FEED_ITEMS, TABLE_NAME_QUEUE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID,
+ TABLE_NAME_QUEUE + "." + KEY_FEEDITEM,
+ TABLE_NAME_QUEUE + "." + KEY_ID};
+ String query = String.format(
+ "SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s", args);
+ Cursor c = db.rawQuery(query, null);
+ /*
+ * Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL,
+ * "INNER JOIN ? ON ?=?", new String[] { TABLE_NAME_QUEUE,
+ * TABLE_NAME_FEED_ITEMS + "." + KEY_ID, TABLE_NAME_QUEUE + "." +
+ * KEY_FEEDITEM }, null, null, TABLE_NAME_QUEUE + "." + KEY_FEEDITEM);
+ */
+ return c;
+ }
+
+ public Cursor getQueueIDCursor() {
+ Cursor c = db.query(TABLE_NAME_QUEUE, new String[]{KEY_FEEDITEM}, null, null, null, null, KEY_ID + " ASC", null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor which contains all feed items in the unread items list.
+ * The returned cursor uses the SEL_FI_SMALL selection.
+ */
+ public final Cursor getUnreadItemsCursor() {
+ Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_READ
+ + "=0", null, null, null, KEY_PUBDATE + " DESC");
+ return c;
+ }
+
+ public final Cursor getUnreadItemIdsCursor() {
+ Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID},
+ KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC");
+ return c;
+
+ }
+
+ public Cursor getDownloadedItemsCursor() {
+ final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + " WHERE "
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0";
+ Cursor c = db.rawQuery(query, null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor which contains feed media objects with a playback
+ * completion date in descending order.
+ *
+ * @param limit The maximum row count of the returned cursor. Must be an
+ * integer >= 0.
+ * @throws IllegalArgumentException if limit < 0
+ */
+ public final Cursor getCompletedMediaCursor(int limit) {
+ if (limit < 0) {
+ throw new IllegalArgumentException("Limit must be >= 0");
+ }
+ Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null,
+ KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null,
+ null, KEY_PLAYBACK_COMPLETION_DATE + " DESC LIMIT " + limit);
+ return c;
+ }
+
+ public final Cursor getSingleFeedMediaCursor(long id) {
+ return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", new String[]{String.valueOf(id)}, null, null, null);
+ }
+
+ public final Cursor getFeedMediaCursorByItemID(String... mediaIds) {
+ int length = mediaIds.length;
+ if (length > IN_OPERATOR_MAXIMUM) {
+ Log.w(TAG, "Length of id array is larger than "
+ + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors");
+ int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1;
+ Cursor[] cursors = new Cursor[numCursors];
+ for (int i = 0; i < numCursors; i++) {
+ int neededLength = 0;
+ String[] parts = null;
+ final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM;
+
+ if (elementsLeft >= IN_OPERATOR_MAXIMUM) {
+ neededLength = IN_OPERATOR_MAXIMUM;
+ parts = Arrays.copyOfRange(mediaIds, i
+ * IN_OPERATOR_MAXIMUM, (i + 1)
+ * IN_OPERATOR_MAXIMUM);
+ } else {
+ neededLength = elementsLeft;
+ parts = Arrays.copyOfRange(mediaIds, i
+ * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM)
+ + neededLength);
+ }
+
+ cursors[i] = db.rawQuery("SELECT * FROM "
+ + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_FEEDITEM + " IN "
+ + buildInOperator(neededLength), parts);
+ }
+ return new MergeCursor(cursors);
+ } else {
+ return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN "
+ + buildInOperator(length), mediaIds, null, null, null);
+ }
+ }
+
+ /**
+ * Builds an IN-operator argument depending on the number of items.
+ */
+ private String buildInOperator(int size) {
+ if (size == 1) {
+ return "(?)";
+ }
+ StringBuffer buffer = new StringBuffer("(");
+ for (int i = 0; i < size - 1; i++) {
+ buffer.append("?,");
+ }
+ buffer.append("?)");
+ return buffer.toString();
+ }
+
+ public final Cursor getFeedCursor(final long id) {
+ Cursor c = db.query(TABLE_NAME_FEEDS, null, KEY_ID + "=" + id, null,
+ null, null, null);
+ return c;
+ }
+
+ public final Cursor getFeedItemCursor(final String... ids) {
+ if (ids.length > IN_OPERATOR_MAXIMUM) {
+ throw new IllegalArgumentException(
+ "number of IDs must not be larger than "
+ + IN_OPERATOR_MAXIMUM);
+ }
+
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_ID + " IN "
+ + buildInOperator(ids.length), ids, null, null, null);
+
+ }
+
+ public final int getNumberOfUnreadItems() {
+ final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS +
+ " WHERE " + KEY_READ + " = 0";
+ Cursor c = db.rawQuery(query, null);
+ int result = 0;
+ if (c.moveToFirst()) {
+ result = c.getInt(0);
+ }
+ c.close();
+ return result;
+ }
+
+ public final int getNumberOfDownloadedEpisodes() {
+ final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA +
+ " WHERE " + KEY_DOWNLOADED + " > 0";
+
+ Cursor c = db.rawQuery(query, null);
+ int result = 0;
+ if (c.moveToFirst()) {
+ result = c.getInt(0);
+ }
+ c.close();
+ return result;
+ }
+
+ /**
+ * Uses DatabaseUtils to escape a search query and removes ' at the
+ * beginning and the end of the string returned by the escape method.
+ */
+ private String prepareSearchQuery(String query) {
+ StringBuilder builder = new StringBuilder();
+ DatabaseUtils.appendEscapedSQLString(builder, query);
+ builder.deleteCharAt(0);
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ }
+
+ /**
+ * Searches for the given query in the description of all items or the items
+ * of a specified feed.
+ *
+ * @return A cursor with all search results in SEL_FI_EXTRA selection.
+ */
+ public Cursor searchItemDescriptions(long feedID, String query) {
+ if (feedID != 0) {
+ // search items in specific feed
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
+ + "=? AND " + KEY_DESCRIPTION + " LIKE '%"
+ + prepareSearchQuery(query) + "%'",
+ new String[]{String.valueOf(feedID)}, null, null,
+ null);
+ } else {
+ // search through all items
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL,
+ KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query)
+ + "%'", null, null, null, null);
+ }
+ }
+
+ /**
+ * Searches for the given query in the content-encoded field of all items or
+ * the items of a specified feed.
+ *
+ * @return A cursor with all search results in SEL_FI_EXTRA selection.
+ */
+ public Cursor searchItemContentEncoded(long feedID, String query) {
+ if (feedID != 0) {
+ // search items in specific feed
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
+ + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%"
+ + prepareSearchQuery(query) + "%'",
+ new String[]{String.valueOf(feedID)}, null, null,
+ null);
+ } else {
+ // search through all items
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL,
+ KEY_CONTENT_ENCODED + " LIKE '%"
+ + prepareSearchQuery(query) + "%'", null, null,
+ null, null);
+ }
+ }
+
+ public Cursor searchItemTitles(long feedID, String query) {
+ if (feedID != 0) {
+ // search items in specific feed
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
+ + "=? AND " + KEY_TITLE + " LIKE '%"
+ + prepareSearchQuery(query) + "%'",
+ new String[]{String.valueOf(feedID)}, null, null,
+ null);
+ } else {
+ // search through all items
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL,
+ KEY_TITLE + " LIKE '%"
+ + prepareSearchQuery(query) + "%'", null, null,
+ null, null);
+ }
+ }
+
+ public Cursor searchItemChapters(long feedID, String searchQuery) {
+ final String query;
+ if (feedID != 0) {
+ query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " +
+ TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" +
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" +
+ feedID + " AND " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%"
+ + prepareSearchQuery(searchQuery) + "%'";
+ } else {
+ query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " +
+ TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" +
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%"
+ + prepareSearchQuery(searchQuery) + "%'";
+ }
+ return db.rawQuery(query, null);
+ }
+
+
+ public static final int IDX_FEEDSTATISTICS_FEED = 0;
+ public static final int IDX_FEEDSTATISTICS_NUM_ITEMS = 1;
+ public static final int IDX_FEEDSTATISTICS_NEW_ITEMS = 2;
+ public static final int IDX_FEEDSTATISTICS_LATEST_EPISODE = 3;
+ public static final int IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES = 4;
+
+ /**
+ * Select number of items, new items, the date of the latest episode and the number of episodes in progress. The result
+ * is sorted by the title of the feed.
+ */
+ private static final String FEED_STATISTICS_QUERY = "SELECT feed, num_items, new_items, latest_episode, in_progress FROM " +
+ "(SELECT feed,count(*) AS num_items," +
+ " COUNT(CASE WHEN read=0 THEN 1 END) AS new_items," +
+ " MAX(pubDate) AS latest_episode," +
+ " COUNT(CASE WHEN position>0 THEN 1 END) AS in_progress," +
+ " COUNT(CASE WHEN downloaded=1 THEN 1 END) AS episodes_downloaded " +
+ " FROM FeedItems LEFT JOIN FeedMedia ON FeedItems.id=FeedMedia.feeditem GROUP BY FeedItems.feed)" +
+ " INNER JOIN Feeds ON Feeds.id = feed ORDER BY Feeds.title;";
+
+ public Cursor getFeedStatisticsCursor() {
+ return db.rawQuery(FEED_STATISTICS_QUERY, null);
+ }
+
+ /**
+ * Helper class for opening the Antennapod database.
+ */
+ private static class PodDBHelper extends SQLiteOpenHelper {
+ /**
+ * Constructor.
+ *
+ * @param context Context to use
+ * @param name Name of the database
+ * @param factory to use for creating cursor objects
+ * @param version number of the database
+ */
+ public PodDBHelper(final Context context, final String name,
+ final CursorFactory factory, final int version) {
+ super(context, name, factory, version);
+ }
+
+ @Override
+ public void onCreate(final SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE_FEEDS);
+ db.execSQL(CREATE_TABLE_FEED_ITEMS);
+ db.execSQL(CREATE_TABLE_FEED_IMAGES);
+ db.execSQL(CREATE_TABLE_FEED_MEDIA);
+ db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
+ db.execSQL(CREATE_TABLE_QUEUE);
+ db.execSQL(CREATE_TABLE_SIMPLECHAPTERS);
+ }
+
+ @Override
+ public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
+ final int newVersion) {
+ Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to "
+ + newVersion + ".");
+ if (oldVersion <= 1) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN "
+ + KEY_TYPE + " TEXT");
+ }
+ if (oldVersion <= 2) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + KEY_LINK + " TEXT");
+ }
+ if (oldVersion <= 3) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 4) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN "
+ + KEY_FEED_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 5) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT");
+ db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT");
+ }
+ if (oldVersion <= 6) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER");
+ }
+ if (oldVersion <= 7) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE
+ + " INTEGER");
+ }
+ if (oldVersion <= 8) {
+ final int KEY_ID_POSITION = 0;
+ final int KEY_MEDIA_POSITION = 1;
+ // Add feeditem column to feedmedia table
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + KEY_FEEDITEM
+ + " INTEGER");
+ Cursor feeditemCursor = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID, KEY_MEDIA}, "? > 0", new String[]{KEY_MEDIA}, null, null, null);
+ if (feeditemCursor.moveToFirst()) {
+ db.beginTransaction();
+ ContentValues contentValues = new ContentValues();
+ do {
+ long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION);
+ contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION));
+ db.update(TABLE_NAME_FEED_MEDIA, contentValues, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
+ contentValues.clear();
+ } while (feeditemCursor.moveToNext());
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+ feeditemCursor.close();
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java
index 1efaac359..bcb0422ce 100644
--- a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java
+++ b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java
@@ -121,7 +121,7 @@ public class NSAtom extends Namespace {
if (state.getContentBuf() != null) {
content = state.getContentBuf().toString();
} else {
- content = new String();
+ content = "";
}
SyndElement topElement = state.getTagstack().peek();
String top = topElement.getName();
diff --git a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java
index 30835434f..a1ed01354 100644
--- a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java
+++ b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java
@@ -11,7 +11,7 @@ import android.util.Log;
public class SyndDateUtils {
private static final String TAG = "DateUtils";
- public static final String[] RFC822DATES = { "dd MMM yy HH:mm:ss Z", };
+ private static final String[] RFC822DATES = { "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'";
@@ -123,12 +123,12 @@ public class SyndDateUtils {
int idx = 0;
if (parts.length == 3) {
// string has hours
- result += Integer.valueOf(parts[idx]) * 3600000;
+ result += Integer.valueOf(parts[idx]) * 3600000L;
idx++;
}
- result += Integer.valueOf(parts[idx]) * 60000;
+ result += Integer.valueOf(parts[idx]) * 60000L;
idx++;
- result += (Float.valueOf(parts[idx])) * 1000;
+ result += (Float.valueOf(parts[idx])) * 1000L;
return result;
}
}
diff --git a/src/de/danoeh/antennapod/util/ChapterUtils.java b/src/de/danoeh/antennapod/util/ChapterUtils.java
index ac8149119..521bfebea 100644
--- a/src/de/danoeh/antennapod/util/ChapterUtils.java
+++ b/src/de/danoeh/antennapod/util/ChapterUtils.java
@@ -35,9 +35,9 @@ public class ChapterUtils {
* chapters.
*/
public static void readID3ChaptersFromPlayableStreamUrl(Playable p) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
if (p != null && p.getStreamUrl() != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
InputStream in = null;
try {
URL url = new URL(p.getStreamUrl());
@@ -86,9 +86,9 @@ public class ChapterUtils {
* chapters.
*/
public static void readID3ChaptersFromPlayableFileUrl(Playable p) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
if (p != null && p.localFileAvailable() && p.getLocalMediaUrl() != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
File source = new File(p.getLocalMediaUrl());
if (source.exists()) {
ChapterReader reader = new ChapterReader();
diff --git a/src/de/danoeh/antennapod/util/ConnectionTester.java b/src/de/danoeh/antennapod/util/ConnectionTester.java
index 2fd22d356..5d940d9e1 100644
--- a/src/de/danoeh/antennapod/util/ConnectionTester.java
+++ b/src/de/danoeh/antennapod/util/ConnectionTester.java
@@ -14,7 +14,7 @@ public class ConnectionTester implements Runnable {
private static final String TAG = "ConnectionTester";
private String strUrl;
private Callback callback;
- private int reason;
+ private DownloadError reason;
private Handler handler;
@@ -68,10 +68,10 @@ public class ConnectionTester implements Runnable {
public static abstract class Callback {
public abstract void onConnectionSuccessful();
- public abstract void onConnectionFailure(int reason);
+ public abstract void onConnectionFailure(DownloadError reason);
}
- public int getReason() {
+ public DownloadError getReason() {
return reason;
}
diff --git a/src/de/danoeh/antennapod/util/DownloadError.java b/src/de/danoeh/antennapod/util/DownloadError.java
index 4723a521c..c37a14584 100644
--- a/src/de/danoeh/antennapod/util/DownloadError.java
+++ b/src/de/danoeh/antennapod/util/DownloadError.java
@@ -4,54 +4,46 @@ import android.content.Context;
import de.danoeh.antennapod.R;
/** Utility class for Download Errors. */
-public class DownloadError {
- public static final int ERROR_PARSER_EXCEPTION = 1;
- public static final int ERROR_UNSUPPORTED_TYPE = 2;
- public static final int ERROR_CONNECTION_ERROR = 3;
- public static final int ERROR_MALFORMED_URL = 4;
- public static final int ERROR_IO_ERROR = 5;
- public static final int ERROR_FILE_EXISTS = 6;
- public static final int ERROR_DOWNLOAD_CANCELLED = 7;
- public static final int ERROR_DEVICE_NOT_FOUND = 8;
- public static final int ERROR_HTTP_DATA_ERROR = 9;
- public static final int ERROR_NOT_ENOUGH_SPACE = 10;
- public static final int ERROR_UNKNOWN_HOST = 11;
- public static final int ERROR_REQUEST_ERROR = 12;
-
- /** Get a human-readable string for a specific error code. */
- public static String getErrorString(Context context, int code) {
- int resId;
- switch(code) {
- case ERROR_NOT_ENOUGH_SPACE:
- resId = R.string.download_error_insufficient_space;
- break;
- case ERROR_DEVICE_NOT_FOUND:
- resId = R.string.download_error_device_not_found;
- break;
- case ERROR_IO_ERROR:
- resId = R.string.download_error_io_error;
- break;
- case ERROR_HTTP_DATA_ERROR:
- resId = R.string.download_error_http_data_error;
- break;
- case ERROR_PARSER_EXCEPTION:
- resId = R.string.download_error_parser_exception;
- break;
- case ERROR_UNSUPPORTED_TYPE:
- resId = R.string.download_error_unsupported_type;
- break;
- case ERROR_CONNECTION_ERROR:
- resId = R.string.download_error_connection_error;
- break;
- case ERROR_UNKNOWN_HOST:
- resId = R.string.download_error_unknown_host;
- break;
- case ERROR_REQUEST_ERROR:
- resId = R.string.download_error_request_error;
- break;
- default:
- resId = R.string.download_error_error_unknown;
+public enum DownloadError {
+ SUCCESS(0, R.string.download_successful),
+ ERROR_PARSER_EXCEPTION(1, R.string.download_error_parser_exception),
+ ERROR_UNSUPPORTED_TYPE(2, R.string.download_error_unsupported_type),
+ ERROR_CONNECTION_ERROR(3, R.string.download_error_connection_error),
+ ERROR_MALFORMED_URL(4, R.string.download_error_error_unknown),
+ ERROR_IO_ERROR(5, R.string.download_error_io_error),
+ ERROR_FILE_EXISTS(6, R.string.download_error_error_unknown),
+ ERROR_DOWNLOAD_CANCELLED(7, R.string.download_error_error_unknown),
+ ERROR_DEVICE_NOT_FOUND(8, R.string.download_error_device_not_found),
+ ERROR_HTTP_DATA_ERROR(9, R.string.download_error_http_data_error),
+ ERROR_NOT_ENOUGH_SPACE(10, R.string.download_error_insufficient_space),
+ ERROR_UNKNOWN_HOST(11, R.string.download_error_unknown_host),
+ ERROR_REQUEST_ERROR(12, R.string.download_error_request_error);
+
+ private final int code;
+ private final int resId;
+
+ private DownloadError(int code, int resId) {
+ this.code = code;
+ this.resId = resId;
+ }
+
+ /** Return DownloadError from its associated code. */
+ public static DownloadError fromCode(int code) {
+ for (DownloadError reason : values()) {
+ if (reason.getCode() == code) {
+ return reason;
+ }
}
+ throw new IllegalArgumentException("unknown code: " + code);
+ }
+
+ /** Get machine-readable code. */
+ public int getCode() {
+ return code;
+ }
+
+ /** Get a human-readable string. */
+ public String getErrorString(Context context) {
return context.getString(resId);
}
diff --git a/src/de/danoeh/antennapod/util/DuckType.java b/src/de/danoeh/antennapod/util/DuckType.java
new file mode 100644
index 000000000..0dfc01508
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/DuckType.java
@@ -0,0 +1,115 @@
+/* Adapted from: http://thinking-in-code.blogspot.com/2008/11/duck-typing-in-java-using-dynamic.html */
+
+package de.danoeh.antennapod.util;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Allows "duck typing" or dynamic invocation based on method signature rather
+ * than type hierarchy. In other words, rather than checking whether something
+ * IS-a duck, check whether it WALKS-like-a duck or QUACKS-like a duck.
+ *
+ * To use first use the coerce static method to indicate the object you want to
+ * do Duck Typing for, then specify an interface to the to method which you want
+ * to coerce the type to, e.g:
+ *
+ * public interface Foo { void aMethod(); } class Bar { ... public void
+ * aMethod() { ... } ... } Bar bar = ...; Foo foo =
+ * DuckType.coerce(bar).to(Foo.class); foo.aMethod();
+ *
+ *
+ */
+public class DuckType {
+
+ private final Object objectToCoerce;
+
+ private DuckType(Object objectToCoerce) {
+ this.objectToCoerce = objectToCoerce;
+ }
+
+ private class CoercedProxy implements InvocationHandler {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Method delegateMethod = findMethodBySignature(method);
+ assert delegateMethod != null;
+ return delegateMethod.invoke(DuckType.this.objectToCoerce, args);
+ }
+ }
+
+ /**
+ * Specify the duck typed object to coerce.
+ *
+ * @param object
+ * the object to coerce
+ * @return
+ */
+ public static DuckType coerce(Object object) {
+ return new DuckType(object);
+ }
+
+ /**
+ * Coerce the Duck Typed object to the given interface providing it
+ * implements all the necessary methods.
+ *
+ * @param
+ * @param iface
+ * @return an instance of the given interface that wraps the duck typed
+ * class
+ * @throws ClassCastException
+ * if the object being coerced does not implement all the
+ * methods in the given interface.
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public <T> T to(Class iface) {
+ assert iface.isInterface() : "cannot coerce object to a class, must be an interface";
+ if (isA(iface)) {
+ return (T) iface.cast(objectToCoerce);
+ }
+ if (quacksLikeA(iface)) {
+ return generateProxy(iface);
+ }
+ throw new ClassCastException("Could not coerce object of type " + objectToCoerce.getClass() + " to " + iface);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private boolean isA(Class iface) {
+ return objectToCoerce.getClass().isInstance(iface);
+ }
+
+ /**
+ * Determine whether the duck typed object can be used with the given
+ * interface.
+ *
+ * @param Type
+ * of the interface to check.
+ * @param iface
+ * Interface class to check
+ * @return true if the object will support all the methods in the interface,
+ * false otherwise.
+ */
+ @SuppressWarnings("rawtypes")
+ public boolean quacksLikeA(Class iface) {
+ for (Method method : iface.getMethods()) {
+ if (findMethodBySignature(method) == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private <T> T generateProxy(Class iface) {
+ return (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new CoercedProxy());
+ }
+
+ private Method findMethodBySignature(Method method) {
+ try {
+ return objectToCoerce.getClass().getMethod(method.getName(), method.getParameterTypes());
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/de/danoeh/antennapod/util/LangUtils.java b/src/de/danoeh/antennapod/util/LangUtils.java
index 53f8de773..e6e1d8399 100644
--- a/src/de/danoeh/antennapod/util/LangUtils.java
+++ b/src/de/danoeh/antennapod/util/LangUtils.java
@@ -1,8 +1,11 @@
package de.danoeh.antennapod.util;
+import java.nio.charset.Charset;
import java.util.HashMap;
public class LangUtils {
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
private static HashMap<String, String> languages;
static {
languages = new HashMap<String, String>();
diff --git a/src/de/danoeh/antennapod/util/QueueAccess.java b/src/de/danoeh/antennapod/util/QueueAccess.java
new file mode 100644
index 000000000..7a1c7fef2
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/QueueAccess.java
@@ -0,0 +1,93 @@
+package de.danoeh.antennapod.util;
+
+import de.danoeh.antennapod.feed.FeedItem;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Provides methods for accessing the queue. It is possible to load only a part of the information about the queue that
+ * is stored in the database (e.g. sometimes the user just has to test if a specific item is contained in the List.
+ * QueueAccess provides an interface for accessing the queue without having to care about the type of the queue
+ * representation.
+ */
+public abstract class QueueAccess {
+ /**
+ * Returns true if the item is in the queue, false otherwise.
+ */
+ public abstract boolean contains(long id);
+
+ /**
+ * Removes the item from the queue.
+ *
+ * @return true if the queue was modified by this operation.
+ */
+ public abstract boolean remove(long id);
+
+ private QueueAccess() {
+
+ }
+
+ public static QueueAccess IDListAccess(final List<Long> ids) {
+ return new QueueAccess() {
+ @Override
+ public boolean contains(long id) {
+ return (ids != null) && ids.contains(id);
+ }
+
+ @Override
+ public boolean remove(long id) {
+ return ids.remove(id);
+ }
+
+
+ };
+ }
+
+ public static QueueAccess ItemListAccess(final List<FeedItem> items) {
+ return new QueueAccess() {
+ @Override
+ public boolean contains(long id) {
+ if (items == null) {
+ return false;
+ }
+ for (FeedItem item : items) {
+ if (item.getId() == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean remove(long id) {
+ Iterator<FeedItem> it = items.iterator();
+ FeedItem item;
+ while (it.hasNext()) {
+ item = it.next();
+ if (item.getId() == id) {
+ it.remove();
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+ public static QueueAccess NotInQueueAccess() {
+ return new QueueAccess() {
+ @Override
+ public boolean contains(long id) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(long id) {
+ return false;
+ }
+ };
+
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/util/ShownotesProvider.java b/src/de/danoeh/antennapod/util/ShownotesProvider.java
new file mode 100644
index 000000000..d273e0b8f
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/ShownotesProvider.java
@@ -0,0 +1,17 @@
+package de.danoeh.antennapod.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Created by daniel on 04.08.13.
+ */
+public interface ShownotesProvider {
+ /**
+ * Loads shownotes. If the shownotes have to be loaded from a file or from a
+ * database, it should be done in a separate thread. After the shownotes
+ * have been loaded, callback.onShownotesLoaded should be called.
+ */
+ public Callable<String> loadShownotes();
+
+}
diff --git a/src/de/danoeh/antennapod/util/URLChecker.java b/src/de/danoeh/antennapod/util/URLChecker.java
index 6d9b8ff03..13668d4a9 100644
--- a/src/de/danoeh/antennapod/util/URLChecker.java
+++ b/src/de/danoeh/antennapod/util/URLChecker.java
@@ -19,13 +19,12 @@ public final class URLChecker {
* */
public static String prepareURL(String url) {
StringBuilder builder = new StringBuilder();
- url = url.trim();
- if (!url.startsWith("http")) {
+ if (url.startsWith("feed://")) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Replacing feed:// with http://");
+ url = url.replace("feed://", "http://");
+ } else if (!(url.startsWith("http://") || url.startsWith("https://"))) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Adding http:// at the beginning of the URL");
builder.append("http://");
- if (AppConfig.DEBUG) Log.d(TAG, "Missing http; appending");
- } else if (url.startsWith("https")) {
- if (AppConfig.DEBUG) Log.d(TAG, "Replacing https with http");
- url = url.replaceFirst("https", "http");
}
builder.append(url);
diff --git a/src/de/danoeh/antennapod/util/UndoBarController.java b/src/de/danoeh/antennapod/util/UndoBarController.java
index e726717a1..a0240e7ce 100644
--- a/src/de/danoeh/antennapod/util/UndoBarController.java
+++ b/src/de/danoeh/antennapod/util/UndoBarController.java
@@ -16,15 +16,17 @@
package de.danoeh.antennapod.util;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.text.TextUtils;
import android.view.View;
-import android.view.ViewPropertyAnimator;
import android.widget.TextView;
+import com.nineoldandroids.animation.Animator;
+import com.nineoldandroids.animation.AnimatorListenerAdapter;
+import com.nineoldandroids.view.ViewHelper;
+import com.nineoldandroids.view.ViewPropertyAnimator;
+import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
import de.danoeh.antennapod.R;
@@ -46,7 +48,7 @@ public class UndoBarController {
public UndoBarController(View undoBarView, UndoListener undoListener) {
mBarView = undoBarView;
- mBarAnimator = mBarView.animate();
+ mBarAnimator = animate(mBarView);
mUndoListener = undoListener;
mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message);
@@ -73,7 +75,7 @@ public class UndoBarController {
mBarView.setVisibility(View.VISIBLE);
if (immediate) {
- mBarView.setAlpha(1);
+ ViewHelper.setAlpha(mBarView, 1);
} else {
mBarAnimator.cancel();
mBarAnimator
@@ -89,7 +91,7 @@ public class UndoBarController {
mHideHandler.removeCallbacks(mHideRunnable);
if (immediate) {
mBarView.setVisibility(View.GONE);
- mBarView.setAlpha(0);
+ ViewHelper.setAlpha(mBarView, 0);
mUndoMessage = null;
mUndoToken = null;
diff --git a/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java b/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
index 12f800565..d0561252f 100644
--- a/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
+++ b/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
@@ -2,15 +2,14 @@ package de.danoeh.antennapod.util.comparator;
import java.util.Comparator;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
+import de.danoeh.antennapod.service.download.*;
/** Compares the completion date of two Downloadstatus objects. */
public class DownloadStatusComparator implements Comparator<DownloadStatus> {
@Override
public int compare(DownloadStatus lhs, DownloadStatus rhs) {
- return -lhs.getCompletionDate().compareTo(rhs.getCompletionDate());
-
+ return rhs.getCompletionDate().compareTo(lhs.getCompletionDate());
}
}
diff --git a/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java b/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java
index b9ee6c07e..c95c0833c 100644
--- a/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java
+++ b/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java
@@ -13,7 +13,7 @@ public class FeedItemPubdateComparator implements Comparator<FeedItem> {
}*/
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
- return -lhs.getPubDate().compareTo(rhs.getPubDate());
+ return rhs.getPubDate().compareTo(lhs.getPubDate());
}
}
diff --git a/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java b/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java
index 2d0ce75ca..434a5a956 100644
--- a/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java
+++ b/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java
@@ -11,8 +11,8 @@ public class PlaybackCompletionDateComparator implements Comparator<FeedItem> {
&& lhs.getMedia().getPlaybackCompletionDate() != null
&& rhs.getMedia() != null
&& rhs.getMedia().getPlaybackCompletionDate() != null) {
- return -lhs.getMedia().getPlaybackCompletionDate()
- .compareTo(rhs.getMedia().getPlaybackCompletionDate());
+ return rhs.getMedia().getPlaybackCompletionDate()
+ .compareTo(lhs.getMedia().getPlaybackCompletionDate());
}
return 0;
}
diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
index 472124bf7..e99a733dc 100644
--- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
+++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
@@ -7,12 +7,16 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.PlaybackService;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.ShareUtils;
+import java.util.List;
+
/** Handles interactions with the FeedItemMenu. */
public class FeedItemMenuHandler {
private FeedItemMenuHandler() {
@@ -21,7 +25,7 @@ public class FeedItemMenuHandler {
/**
* Used by the MenuHandler to access different types of menus through one
- * interface (for example android.view.Menu and com.actionbarsherlock.Menu)
+ * interface
*/
public interface MenuInterface {
/**
@@ -45,11 +49,15 @@ public class FeedItemMenuHandler {
* True if MenuItems that let the user share information about
* the FeedItem and visit its website should be set visible. This
* parameter should be set to false if the menu space is limited.
- * @return Always returns true
+ * @param queueAccess
+ * Used for testing if the queue contains the selected item
+ * @return Returns true if selectedItem is not null.
* */
public static boolean onPrepareMenu(MenuInterface mi,
- FeedItem selectedItem, boolean showExtendedMenu) {
- FeedManager manager = FeedManager.getInstance();
+ FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) {
+ if (selectedItem == null) {
+ return false;
+ }
DownloadRequester requester = DownloadRequester.getInstance();
boolean hasMedia = selectedItem.getMedia() != null;
boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded();
@@ -79,7 +87,7 @@ public class FeedItemMenuHandler {
mi.setItemVisibility(R.id.cancel_download_item, false);
}
- boolean isInQueue = manager.isInQueue(selectedItem);
+ boolean isInQueue = queueAccess.contains(selectedItem.getId());
if (!isInQueue || isPlaying) {
mi.setItemVisibility(R.id.remove_from_queue_item, false);
}
@@ -111,39 +119,38 @@ public class FeedItemMenuHandler {
public static boolean onMenuItemClicked(Context context, int menuItemId,
FeedItem selectedItem) throws DownloadRequestException {
DownloadRequester requester = DownloadRequester.getInstance();
- FeedManager manager = FeedManager.getInstance();
switch (menuItemId) {
case R.id.skip_episode_item:
context.sendBroadcast(new Intent(
PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
break;
case R.id.download_item:
- manager.downloadFeedItem(context, selectedItem);
+ DBTasks.downloadFeedItems(context, selectedItem);
break;
case R.id.play_item:
- manager.playMedia(context, selectedItem.getMedia(), true, true,
+ DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
false);
break;
case R.id.remove_item:
- manager.deleteFeedMedia(context, selectedItem.getMedia());
+ DBWriter.deleteFeedMediaOfItem(context, selectedItem.getMedia().getId());
break;
case R.id.cancel_download_item:
requester.cancelDownload(context, selectedItem.getMedia());
break;
case R.id.mark_read_item:
- manager.markItemRead(context, selectedItem, true, true);
+ DBWriter.markItemRead(context, selectedItem, true, true);
break;
case R.id.mark_unread_item:
- manager.markItemRead(context, selectedItem, false, true);
+ DBWriter.markItemRead(context, selectedItem, false, true);
break;
case R.id.add_to_queue_item:
- manager.addQueueItem(context, selectedItem);
+ DBWriter.addQueueItem(context, selectedItem.getId());
break;
case R.id.remove_from_queue_item:
- manager.removeQueueItem(context, selectedItem, true);
+ DBWriter.removeQueueItem(context, selectedItem.getId(), true);
break;
case R.id.stream_item:
- manager.playMedia(context, selectedItem.getMedia(), true, true,
+ DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
true);
break;
case R.id.visit_website_item:
diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
index af8538e83..446e024d9 100644
--- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
+++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
@@ -5,17 +5,17 @@ import android.content.Intent;
import android.net.Uri;
import android.util.Log;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.FeedInfoActivity;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.download.DownloadService;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.ShareUtils;
@@ -30,6 +30,10 @@ public class FeedMenuHandler {
}
public static boolean onPrepareOptionsMenu(Menu menu, Feed selectedFeed) {
+ if (selectedFeed == null) {
+ return false;
+ }
+
if (AppConfig.DEBUG)
Log.d(TAG, "Preparing options menu");
menu.findItem(R.id.mark_all_read_item).setVisible(
@@ -56,7 +60,6 @@ public class FeedMenuHandler {
*/
public static boolean onOptionsItemClicked(Context context, MenuItem item,
Feed selectedFeed) throws DownloadRequestException {
- FeedManager manager = FeedManager.getInstance();
switch (item.getItemId()) {
case R.id.show_info_item:
Intent startIntent = new Intent(context, FeedInfoActivity.class);
@@ -65,10 +68,10 @@ public class FeedMenuHandler {
context.startActivity(startIntent);
break;
case R.id.refresh_item:
- manager.refreshFeed(context, selectedFeed);
+ DBTasks.refreshFeed(context, selectedFeed);
break;
case R.id.mark_all_read_item:
- manager.markFeedRead(context, selectedFeed);
+ DBWriter.markFeedRead(context, selectedFeed.getId());
break;
case R.id.visit_website_item:
Uri uri = Uri.parse(selectedFeed.getLink());
diff --git a/src/de/danoeh/antennapod/util/playback/AudioPlayer.java b/src/de/danoeh/antennapod/util/playback/AudioPlayer.java
new file mode 100644
index 000000000..68d31324d
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/playback/AudioPlayer.java
@@ -0,0 +1,30 @@
+package de.danoeh.antennapod.util.playback;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import com.aocate.media.MediaPlayer;
+
+public class AudioPlayer extends MediaPlayer implements IPlayer {
+ private static final String TAG = "AudioPlayer";
+
+ public AudioPlayer(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setScreenOnWhilePlaying(boolean screenOn) {
+ Log.e(TAG, "Setting screen on while playing not supported in Audio Player");
+ throw new UnsupportedOperationException("Setting screen on while playing not supported in Audio Player");
+
+ }
+
+ @Override
+ public void setDisplay(SurfaceHolder sh) {
+ if (sh != null) {
+ Log.e(TAG, "Setting display not supported in Audio Player");
+ throw new UnsupportedOperationException("Setting display not supported in Audio Player");
+ }
+ }
+}
diff --git a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
index c0a92904b..e937ee437 100644
--- a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
+++ b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.util.playback;
import java.io.InputStream;
import java.util.List;
+import java.util.concurrent.Callable;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@@ -24,7 +25,6 @@ public class ExternalMedia implements Playable {
private String episodeTitle;
private String feedTitle;
- private String shownotes;
private MediaType mediaType = MediaType.AUDIO;
private List<Chapter> chapters;
private int duration;
@@ -79,8 +79,13 @@ public class ExternalMedia implements Playable {
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
feedTitle = mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
- duration = Integer.parseInt(mmr
+ try {
+ duration = Integer.parseInt(mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ throw new PlayableException("NumberFormatException when reading duration of media file");
+ }
ChapterUtils.loadChaptersFromFileUrl(this);
}
@@ -95,8 +100,13 @@ public class ExternalMedia implements Playable {
}
@Override
- public void loadShownotes(ShownoteLoaderCallback callback) {
- callback.onShownotesLoaded(null);
+ public Callable<String> loadShownotes() {
+ return new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ return "";
+ }
+ };
}
@Override
diff --git a/src/de/danoeh/antennapod/util/playback/IPlayer.java b/src/de/danoeh/antennapod/util/playback/IPlayer.java
new file mode 100644
index 000000000..ca9b36358
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/playback/IPlayer.java
@@ -0,0 +1,64 @@
+package de.danoeh.antennapod.util.playback;
+
+import java.io.IOException;
+
+import android.view.SurfaceHolder;
+
+public interface IPlayer {
+ boolean canSetPitch();
+
+ boolean canSetSpeed();
+
+ float getCurrentPitchStepsAdjustment();
+
+ int getCurrentPosition();
+
+ float getCurrentSpeedMultiplier();
+
+ int getDuration();
+
+ float getMaxSpeedMultiplier();
+
+ float getMinSpeedMultiplier();
+
+ boolean isLooping();
+
+ boolean isPlaying();
+
+ void pause();
+
+ void prepare() throws IllegalStateException, IOException;
+
+ void prepareAsync();
+
+ void release();
+
+ void reset();
+
+ void seekTo(int msec);
+
+ void setAudioStreamType(int streamtype);
+
+ void setScreenOnWhilePlaying(boolean screenOn);
+
+ void setDataSource(String path) throws IllegalStateException, IOException,
+ IllegalArgumentException, SecurityException;
+
+ void setDisplay(SurfaceHolder sh);
+
+ void setEnableSpeedAdjustment(boolean enableSpeedAdjustment);
+
+ void setLooping(boolean looping);
+
+ void setPitchStepsAdjustment(float pitchSteps);
+
+ void setPlaybackPitch(float f);
+
+ void setPlaybackSpeed(float f);
+
+ void setVolume(float left, float right);
+
+ void start();
+
+ void stop();
+}
diff --git a/src/de/danoeh/antennapod/util/playback/Playable.java b/src/de/danoeh/antennapod/util/playback/Playable.java
index 0404379e2..98d5fbb36 100644
--- a/src/de/danoeh/antennapod/util/playback/Playable.java
+++ b/src/de/danoeh/antennapod/util/playback/Playable.java
@@ -3,7 +3,11 @@ package de.danoeh.antennapod.util.playback;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
+import java.util.concurrent.FutureTask;
+import android.content.Context;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.util.ShownotesProvider;
import org.apache.commons.io.IOUtils;
import android.content.SharedPreferences;
@@ -12,262 +16,263 @@ import android.os.Parcelable;
import android.util.Log;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.feed.MediaType;
-/** Interface for objects that can be played by the PlaybackService. */
+/**
+ * Interface for objects that can be played by the PlaybackService.
+ */
public interface Playable extends Parcelable,
- ImageLoader.ImageWorkerTaskResource {
-
- /**
- * Save information about the playable in a preference so that it can be
- * restored later via PlayableUtils.createInstanceFromPreferences.
- * Implementations must NOT call commit() after they have written the values
- * to the preferences file.
- */
- public void writeToPreferences(SharedPreferences.Editor prefEditor);
-
- /**
- * This method is called from a separate thread by the PlaybackService.
- * Playable objects should load their metadata in this method. This method
- * should execute as quickly as possible and NOT load chapter marks if no
- * local file is available.
- */
- public void loadMetadata() throws PlayableException;
-
- /**
- * This method is called from a separate thread by the PlaybackService.
- * Playable objects should load their chapter marks in this method if no
- * local file was available when loadMetadata() was called.
- */
- public void loadChapterMarks();
-
- /** Returns the title of the episode that this playable represents */
- public String getEpisodeTitle();
-
- /**
- * Loads shownotes. If the shownotes have to be loaded from a file or from a
- * database, it should be done in a separate thread. After the shownotes
- * have been loaded, callback.onShownotesLoaded should be called.
- */
- public void loadShownotes(ShownoteLoaderCallback callback);
-
- /**
- * Returns a list of chapter marks or null if this Playable has no chapters.
- */
- public List<Chapter> getChapters();
-
- /** Returns a link to a website that is meant to be shown in a browser */
- public String getWebsiteLink();
-
- public String getPaymentLink();
-
- /** Returns the title of the feed this Playable belongs to. */
- public String getFeedTitle();
-
- /**
- * Returns a unique identifier, for example a file url or an ID from a
- * database.
- */
- public Object getIdentifier();
-
- /** Return duration of object or 0 if duration is unknown. */
- public int getDuration();
-
- /** Return position of object or 0 if position is unknown. */
- public int getPosition();
-
- /**
- * Returns the type of media. This method should return the correct value
- * BEFORE loadMetadata() is called.
- */
- public MediaType getMediaType();
-
- /**
- * Returns an url to a local file that can be played or null if this file
- * does not exist.
- */
- public String getLocalMediaUrl();
-
- /**
- * Returns an url to a file that can be streamed by the player or null if
- * this url is not known.
- */
- public String getStreamUrl();
-
- /**
- * Returns true if a local file that can be played is available. getFileUrl
- * MUST return a non-null string if this method returns true.
- */
- public boolean localFileAvailable();
-
- /**
- * Returns true if a streamable file is available. getStreamUrl MUST return
- * a non-null string if this method returns true.
- */
- public boolean streamAvailable();
-
- /**
- * Saves the current position of this object. Implementations can use the
- * provided SharedPreference to save this information and retrieve it later
- * via PlayableUtils.createInstanceFromPreferences.
- */
- public void saveCurrentPosition(SharedPreferences pref, int newPosition);
-
- public void setPosition(int newPosition);
-
- public void setDuration(int newDuration);
-
- /** Is called by the PlaybackService when playback starts. */
- public void onPlaybackStart();
-
- /** Is called by the PlaybackService when playback is completed. */
- public void onPlaybackCompleted();
-
- /**
- * Returns an integer that must be unique among all Playable classes. The
- * return value is later used by PlayableUtils to determine the type of the
- * Playable object that is restored.
- */
- public int getPlayableType();
-
- public void setChapters(List<Chapter> chapters);
-
- /** Provides utility methods for Playable objects. */
- public static class PlayableUtils {
- private static final String TAG = "PlayableUtils";
-
- /**
- * Restores a playable object from a sharedPreferences file.
- *
- * @param type
- * An integer that represents the type of the Playable object
- * that is restored.
- * @param pref
- * The SharedPreferences file from which the Playable object
- * is restored
- * @return The restored Playable object
- */
- public static Playable createInstanceFromPreferences(int type,
- SharedPreferences pref) {
- // ADD new Playable types here:
- switch (type) {
- case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA:
- long feedId = pref.getLong(FeedMedia.PREF_FEED_ID, -1);
- long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1);
- if (feedId != -1 && mediaId != -1) {
- Feed feed = FeedManager.getInstance().getFeed(feedId);
- if (feed != null) {
- return FeedManager.getInstance().getFeedMedia(mediaId,
- feed);
- }
- }
- break;
- case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA:
- String source = pref.getString(ExternalMedia.PREF_SOURCE_URL,
- null);
- String mediaType = pref.getString(
- ExternalMedia.PREF_MEDIA_TYPE, null);
- if (source != null && mediaType != null) {
- int position = pref.getInt(ExternalMedia.PREF_POSITION, 0);
- return new ExternalMedia(source,
- MediaType.valueOf(mediaType), position);
- }
- break;
- }
- Log.e(TAG, "Could not restore Playable object from preferences");
- return null;
- }
- }
-
- public static class PlayableException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public PlayableException() {
- super();
- }
-
- public PlayableException(String detailMessage, Throwable throwable) {
- super(detailMessage, throwable);
- }
-
- public PlayableException(String detailMessage) {
- super(detailMessage);
- }
-
- public PlayableException(Throwable throwable) {
- super(throwable);
- }
-
- }
-
- public static interface ShownoteLoaderCallback {
- void onShownotesLoaded(String shownotes);
- }
-
- /** Uses local file as image resource if it is available. */
- public static class DefaultPlayableImageLoader implements
- ImageLoader.ImageWorkerTaskResource {
- private Playable playable;
-
- public DefaultPlayableImageLoader(Playable playable) {
- if (playable == null) {
- throw new IllegalArgumentException("Playable must not be null");
- }
- this.playable = playable;
- }
-
- @Override
- public InputStream openImageInputStream() {
- if (playable.localFileAvailable()
- && playable.getLocalMediaUrl() != null) {
- MediaMetadataRetriever mmr = new MediaMetadataRetriever();
- try {
- mmr.setDataSource(playable.getLocalMediaUrl());
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- return null;
- }
- byte[] imgData = mmr.getEmbeddedPicture();
- if (imgData != null) {
- return new PublicByteArrayInputStream(imgData);
-
- }
- }
- return null;
- }
-
- @Override
- public String getImageLoaderCacheKey() {
- return playable.getLocalMediaUrl();
- }
-
- @Override
- public InputStream reopenImageInputStream(InputStream input) {
- if (input instanceof PublicByteArrayInputStream) {
- IOUtils.closeQuietly(input);
- byte[] imgData = ((PublicByteArrayInputStream) input)
- .getByteArray();
- if (imgData != null) {
- ByteArrayInputStream out = new ByteArrayInputStream(imgData);
- return out;
- }
-
- }
- return null;
- }
-
- private static class PublicByteArrayInputStream extends
- ByteArrayInputStream {
- public PublicByteArrayInputStream(byte[] buf) {
- super(buf);
- }
-
- public byte[] getByteArray() {
- return buf;
- }
- }
- }
+ ImageLoader.ImageWorkerTaskResource, ShownotesProvider {
+
+ /**
+ * Save information about the playable in a preference so that it can be
+ * restored later via PlayableUtils.createInstanceFromPreferences.
+ * Implementations must NOT call commit() after they have written the values
+ * to the preferences file.
+ */
+ public void writeToPreferences(SharedPreferences.Editor prefEditor);
+
+ /**
+ * This method is called from a separate thread by the PlaybackService.
+ * Playable objects should load their metadata in this method. This method
+ * should execute as quickly as possible and NOT load chapter marks if no
+ * local file is available.
+ */
+ public void loadMetadata() throws PlayableException;
+
+ /**
+ * This method is called from a separate thread by the PlaybackService.
+ * Playable objects should load their chapter marks in this method if no
+ * local file was available when loadMetadata() was called.
+ */
+ public void loadChapterMarks();
+
+ /**
+ * Returns the title of the episode that this playable represents
+ */
+ public String getEpisodeTitle();
+
+ /**
+ * Returns a list of chapter marks or null if this Playable has no chapters.
+ */
+ public List<Chapter> getChapters();
+
+ /**
+ * Returns a link to a website that is meant to be shown in a browser
+ */
+ public String getWebsiteLink();
+
+ public String getPaymentLink();
+
+ /**
+ * Returns the title of the feed this Playable belongs to.
+ */
+ public String getFeedTitle();
+
+ /**
+ * Returns a unique identifier, for example a file url or an ID from a
+ * database.
+ */
+ public Object getIdentifier();
+
+ /**
+ * Return duration of object or 0 if duration is unknown.
+ */
+ public int getDuration();
+
+ /**
+ * Return position of object or 0 if position is unknown.
+ */
+ public int getPosition();
+
+ /**
+ * Returns the type of media. This method should return the correct value
+ * BEFORE loadMetadata() is called.
+ */
+ public MediaType getMediaType();
+
+ /**
+ * Returns an url to a local file that can be played or null if this file
+ * does not exist.
+ */
+ public String getLocalMediaUrl();
+
+ /**
+ * Returns an url to a file that can be streamed by the player or null if
+ * this url is not known.
+ */
+ public String getStreamUrl();
+
+ /**
+ * Returns true if a local file that can be played is available. getFileUrl
+ * MUST return a non-null string if this method returns true.
+ */
+ public boolean localFileAvailable();
+
+ /**
+ * Returns true if a streamable file is available. getStreamUrl MUST return
+ * a non-null string if this method returns true.
+ */
+ public boolean streamAvailable();
+
+ /**
+ * Saves the current position of this object. Implementations can use the
+ * provided SharedPreference to save this information and retrieve it later
+ * via PlayableUtils.createInstanceFromPreferences.
+ */
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition);
+
+ public void setPosition(int newPosition);
+
+ public void setDuration(int newDuration);
+
+ /**
+ * Is called by the PlaybackService when playback starts.
+ */
+ public void onPlaybackStart();
+
+ /**
+ * Is called by the PlaybackService when playback is completed.
+ */
+ public void onPlaybackCompleted();
+
+ /**
+ * Returns an integer that must be unique among all Playable classes. The
+ * return value is later used by PlayableUtils to determine the type of the
+ * Playable object that is restored.
+ */
+ public int getPlayableType();
+
+ public void setChapters(List<Chapter> chapters);
+
+ /**
+ * Provides utility methods for Playable objects.
+ */
+ public static class PlayableUtils {
+ private static final String TAG = "PlayableUtils";
+
+ /**
+ * Restores a playable object from a sharedPreferences file. This method might load data from the database,
+ * depending on the type of playable that was restored.
+ *
+ * @param type An integer that represents the type of the Playable object
+ * that is restored.
+ * @param pref The SharedPreferences file from which the Playable object
+ * is restored
+ * @return The restored Playable object
+ */
+ public static Playable createInstanceFromPreferences(Context context, int type,
+ SharedPreferences pref) {
+ // ADD new Playable types here:
+ switch (type) {
+ case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA:
+ long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1);
+ if (mediaId != -1) {
+ return DBReader.getFeedMedia(context, mediaId);
+ }
+ break;
+ case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA:
+ String source = pref.getString(ExternalMedia.PREF_SOURCE_URL,
+ null);
+ String mediaType = pref.getString(
+ ExternalMedia.PREF_MEDIA_TYPE, null);
+ if (source != null && mediaType != null) {
+ int position = pref.getInt(ExternalMedia.PREF_POSITION, 0);
+ return new ExternalMedia(source,
+ MediaType.valueOf(mediaType), position);
+ }
+ break;
+ }
+ Log.e(TAG, "Could not restore Playable object from preferences");
+ return null;
+ }
+ }
+
+ public static class PlayableException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public PlayableException() {
+ super();
+ }
+
+ public PlayableException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+ public PlayableException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ public PlayableException(Throwable throwable) {
+ super(throwable);
+ }
+
+ }
+
+ /**
+ * Uses local file as image resource if it is available.
+ */
+ public static class DefaultPlayableImageLoader implements
+ ImageLoader.ImageWorkerTaskResource {
+ private Playable playable;
+
+ public DefaultPlayableImageLoader(Playable playable) {
+ if (playable == null) {
+ throw new IllegalArgumentException("Playable must not be null");
+ }
+ this.playable = playable;
+ }
+
+ @Override
+ public InputStream openImageInputStream() {
+ if (playable.localFileAvailable()
+ && playable.getLocalMediaUrl() != null) {
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ try {
+ mmr.setDataSource(playable.getLocalMediaUrl());
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ return null;
+ }
+ byte[] imgData = mmr.getEmbeddedPicture();
+ if (imgData != null) {
+ return new PublicByteArrayInputStream(imgData);
+
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getImageLoaderCacheKey() {
+ return playable.getLocalMediaUrl();
+ }
+
+ @Override
+ public InputStream reopenImageInputStream(InputStream input) {
+ if (input instanceof PublicByteArrayInputStream) {
+ IOUtils.closeQuietly(input);
+ byte[] imgData = ((PublicByteArrayInputStream) input)
+ .getByteArray();
+ if (imgData != null) {
+ ByteArrayInputStream out = new ByteArrayInputStream(imgData);
+ return out;
+ }
+
+ }
+ return null;
+ }
+
+ private static class PublicByteArrayInputStream extends
+ ByteArrayInputStream {
+ public PublicByteArrayInputStream(byte[] buf) {
+ super(buf);
+ }
+
+ public byte[] getByteArray() {
+ return buf;
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/util/playback/PlaybackController.java b/src/de/danoeh/antennapod/util/playback/PlaybackController.java
index cebb11cf0..017a0cd5b 100644
--- a/src/de/danoeh/antennapod/util/playback/PlaybackController.java
+++ b/src/de/danoeh/antennapod/util/playback/PlaybackController.java
@@ -16,6 +16,7 @@ import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
+import android.os.AsyncTask;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
@@ -28,11 +29,11 @@ import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.PlayerStatus;
+import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.playback.Playable.PlayableUtils;
@@ -41,661 +42,707 @@ import de.danoeh.antennapod.util.playback.Playable.PlayableUtils;
* control playback instead of communicating with the PlaybackService directly.
*/
public abstract class PlaybackController {
- private static final String TAG = "PlaybackController";
+ private static final String TAG = "PlaybackController";
- static final int DEFAULT_SEEK_DELTA = 30000;
+ public static final int DEFAULT_SEEK_DELTA = 30000;
public static final int INVALID_TIME = -1;
- private Activity activity;
-
- private PlaybackService playbackService;
- private Playable media;
- private PlayerStatus status;
-
- private ScheduledThreadPoolExecutor schedExecutor;
- private static final int SCHED_EX_POOLSIZE = 1;
-
- protected MediaPositionObserver positionObserver;
- protected ScheduledFuture positionObserverFuture;
-
- private boolean mediaInfoLoaded = false;
- private boolean released = false;
-
- /**
- * True if controller should reinit playback service if 'pause' button is
- * pressed.
- */
- private boolean reinitOnPause;
-
- public PlaybackController(Activity activity, boolean reinitOnPause) {
- this.activity = activity;
- this.reinitOnPause = reinitOnPause;
- schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOLSIZE,
- 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,
- "Rejected execution of runnable in schedExecutor");
- }
- });
- }
-
- /**
- * Creates a new connection to the playbackService. Should be called in the
- * activity's onResume() method.
- */
- public void init() {
- activity.registerReceiver(statusUpdate, new IntentFilter(
- PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
-
- activity.registerReceiver(notificationReceiver, new IntentFilter(
- PlaybackService.ACTION_PLAYER_NOTIFICATION));
-
- activity.registerReceiver(shutdownReceiver, new IntentFilter(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
-
- if (!released) {
- bindToService();
- } else {
- throw new IllegalStateException(
- "Can't call init() after release() has been called");
- }
- }
-
- /**
- * Should be called if the PlaybackController is no longer needed, for
- * example in the activity's onStop() method.
- */
- public void release() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Releasing PlaybackController");
-
- try {
- activity.unregisterReceiver(statusUpdate);
- } catch (IllegalArgumentException e) {
- // ignore
- }
-
- try {
- activity.unregisterReceiver(notificationReceiver);
- } catch (IllegalArgumentException e) {
- // ignore
- }
-
- try {
- activity.unbindService(mConnection);
- } catch (IllegalArgumentException e) {
- // ignore
- }
-
- try {
- activity.unregisterReceiver(shutdownReceiver);
- } catch (IllegalArgumentException e) {
- // ignore
- }
- cancelPositionObserver();
- schedExecutor.shutdownNow();
- media = null;
- released = true;
-
- }
-
- /** Should be called in the activity's onPause() method. */
- public void pause() {
- mediaInfoLoaded = false;
- if (playbackService != null && playbackService.isPlayingVideo()) {
- playbackService.pause(true, true);
- }
- }
-
- /**
- * Tries to establish a connection to the PlaybackService. If it isn't
- * running, the PlaybackService will be started with the last played media
- * as the arguments of the launch intent.
- */
- private void bindToService() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Trying to connect to service");
- Intent serviceIntent = getPlayLastPlayedMediaIntent();
- boolean bound = false;
- if (!PlaybackService.isRunning) {
- if (serviceIntent != null) {
- activity.startService(serviceIntent);
- bound = activity.bindService(serviceIntent, mConnection, 0);
- } else {
- status = PlayerStatus.STOPPED;
- setupGUI();
- handleStatus();
- }
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "PlaybackService is running, trying to connect without start command.");
- bound = activity.bindService(new Intent(activity,
- PlaybackService.class), mConnection, 0);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Result for service binding: " + bound);
- }
-
- /**
- * Returns an intent that starts the PlaybackService and plays the last
- * played media or null if no last played media could be found.
- */
- private Intent getPlayLastPlayedMediaIntent() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Trying to restore last played media");
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(activity.getApplicationContext());
- long currentlyPlayingMedia = PlaybackPreferences
- .getCurrentlyPlayingMedia();
- if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
- Playable media = PlayableUtils.createInstanceFromPreferences(
- (int) currentlyPlayingMedia, prefs);
- if (media != null) {
- Intent serviceIntent = new Intent(activity,
- PlaybackService.class);
- serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- serviceIntent.putExtra(
- PlaybackService.EXTRA_START_WHEN_PREPARED, false);
- serviceIntent.putExtra(
- PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false);
- boolean fileExists = media.localFileAvailable();
- boolean lastIsStream = PlaybackPreferences
- .getCurrentEpisodeIsStream();
- if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
- FeedManager.getInstance().notifyMissingFeedMediaFile(
- activity, (FeedMedia) media);
- }
- serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
- lastIsStream || !fileExists);
- return serviceIntent;
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "No last played media found");
- return null;
- }
-
- public abstract void setupGUI();
-
- private void setupPositionObserver() {
- if ((positionObserverFuture != null && positionObserverFuture
- .isCancelled())
- || (positionObserverFuture != null && positionObserverFuture
- .isDone()) || positionObserverFuture == null) {
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting up position observer");
- positionObserver = new MediaPositionObserver();
- positionObserverFuture = schedExecutor.scheduleWithFixedDelay(
- positionObserver, MediaPositionObserver.WAITING_INTERVALL,
- MediaPositionObserver.WAITING_INTERVALL,
- TimeUnit.MILLISECONDS);
- }
- }
-
- private void cancelPositionObserver() {
- if (positionObserverFuture != null) {
- boolean result = positionObserverFuture.cancel(true);
- if (AppConfig.DEBUG)
- Log.d(TAG, "PositionObserver cancelled. Result: " + result);
- }
- }
-
- public abstract void onPositionObserverUpdate();
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- playbackService = ((PlaybackService.LocalBinder) service)
- .getService();
-
- queryService();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Connection to Service established");
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- playbackService = null;
- if (AppConfig.DEBUG)
- Log.d(TAG, "Disconnected from Service");
-
- }
- };
-
- protected BroadcastReceiver statusUpdate = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received statusUpdate Intent.");
- if (isConnectedToPlaybackService()) {
- status = playbackService.getStatus();
- handleStatus();
- } else {
- Log.w(TAG,
- "Couldn't receive status update: playbackService was null");
- bindToService();
- }
- }
- };
-
- protected BroadcastReceiver notificationReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (isConnectedToPlaybackService()) {
- int type = intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_TYPE, -1);
- int code = intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_CODE, -1);
- if (code != -1 && type != -1) {
- switch (type) {
- case PlaybackService.NOTIFICATION_TYPE_ERROR:
- handleError(code);
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE:
- float progress = ((float) code) / 100;
- onBufferUpdate(progress);
- break;
- case PlaybackService.NOTIFICATION_TYPE_RELOAD:
- cancelPositionObserver();
- mediaInfoLoaded = false;
- onReloadNotification(intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_CODE, -1));
- queryService();
-
- break;
- case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE:
- onSleepTimerUpdate();
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_START:
- onBufferStart();
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_END:
- onBufferEnd();
- break;
- case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
- onPlaybackEnd();
- break;
- }
-
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Bad arguments. Won't handle intent");
- }
- } else {
- bindToService();
- }
- }
-
- };
-
- private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (isConnectedToPlaybackService()) {
- if (intent.getAction().equals(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
- release();
- onShutdownNotification();
- }
- }
- }
- };
-
- public abstract void onShutdownNotification();
-
- /** Called when the currently displayed information should be refreshed. */
- public abstract void onReloadNotification(int code);
-
- public abstract void onBufferStart();
-
- public abstract void onBufferEnd();
-
- public abstract void onBufferUpdate(float progress);
-
- public abstract void onSleepTimerUpdate();
-
- public abstract void handleError(int code);
-
- public abstract void onPlaybackEnd();
-
- /**
- * Is called whenever the PlaybackService changes it's status. This method
- * should be used to update the GUI or start/cancel background threads.
- */
- private void handleStatus() {
- TypedArray res = activity.obtainStyledAttributes(new int[] {
- R.attr.av_play, R.attr.av_pause });
- final int playResource = res.getResourceId(0, R.drawable.av_play);
- final int pauseResource = res.getResourceId(1, R.drawable.av_pause);
- res.recycle();
-
- switch (status) {
-
- case ERROR:
- postStatusMsg(R.string.player_error_msg);
- break;
- case PAUSED:
- clearStatusMsg();
- checkMediaInfoLoaded();
- cancelPositionObserver();
- updatePlayButtonAppearance(playResource);
- break;
- case PLAYING:
- clearStatusMsg();
- checkMediaInfoLoaded();
- setupPositionObserver();
- updatePlayButtonAppearance(pauseResource);
- break;
- case PREPARING:
- postStatusMsg(R.string.player_preparing_msg);
- checkMediaInfoLoaded();
- if (playbackService != null) {
- if (playbackService.isStartWhenPrepared()) {
- updatePlayButtonAppearance(pauseResource);
- } else {
- updatePlayButtonAppearance(playResource);
- }
- }
- break;
- case STOPPED:
- postStatusMsg(R.string.player_stopped_msg);
- break;
- case PREPARED:
- checkMediaInfoLoaded();
- postStatusMsg(R.string.player_ready_msg);
- updatePlayButtonAppearance(playResource);
- break;
- case SEEKING:
- postStatusMsg(R.string.player_seeking_msg);
- break;
- case AWAITING_VIDEO_SURFACE:
- onAwaitingVideoSurface();
- break;
- case INITIALIZED:
- checkMediaInfoLoaded();
- clearStatusMsg();
- updatePlayButtonAppearance(playResource);
- break;
- }
- }
-
- private void checkMediaInfoLoaded() {
- if (!mediaInfoLoaded) {
- loadMediaInfo();
- }
- mediaInfoLoaded = true;
- }
-
- private void updatePlayButtonAppearance(int resource) {
- ImageButton butPlay = getPlayButton();
- butPlay.setImageResource(resource);
- }
-
- public abstract ImageButton getPlayButton();
-
- public abstract void postStatusMsg(int msg);
-
- public abstract void clearStatusMsg();
-
- public abstract void loadMediaInfo();
-
- public abstract void onAwaitingVideoSurface();
-
- /**
- * Called when connection to playback service has been established or
- * information has to be refreshed
- */
- void queryService() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Querying service info");
- if (playbackService != null) {
- status = playbackService.getStatus();
- media = playbackService.getMedia();
- if (media == null) {
- Log.w(TAG,
- "PlaybackService has no media object. Trying to restore last played media.");
- Intent serviceIntent = getPlayLastPlayedMediaIntent();
- if (serviceIntent != null) {
- activity.startService(serviceIntent);
- }
- }
- onServiceQueried();
-
- setupGUI();
- handleStatus();
- // make sure that new media is loaded if it's available
- mediaInfoLoaded = false;
-
- } else {
- Log.e(TAG,
- "queryService() was called without an existing connection to playbackservice");
- }
- }
-
- public abstract void onServiceQueried();
-
- /**
- * Should be used by classes which implement the OnSeekBarChanged interface.
- */
- public float onSeekBarProgressChanged(SeekBar seekBar, int progress,
- boolean fromUser, TextView txtvPosition) {
- if (fromUser && playbackService != null) {
- float prog = progress / ((float) seekBar.getMax());
- int duration = media.getDuration();
- txtvPosition.setText(Converter
- .getDurationStringLong((int) (prog * duration)));
- return prog;
- }
- return 0;
-
- }
-
- /**
- * Should be used by classes which implement the OnSeekBarChanged interface.
- */
- public void onSeekBarStartTrackingTouch(SeekBar seekBar) {
- // interrupt position Observer, restart later
- cancelPositionObserver();
- }
-
- /**
- * Should be used by classes which implement the OnSeekBarChanged interface.
- */
- public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) {
- if (playbackService != null) {
- playbackService.seek((int) (prog * media.getDuration()));
- setupPositionObserver();
- }
- }
-
- public OnClickListener newOnPlayButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (playbackService != null) {
- switch (status) {
- case PLAYING:
- playbackService.pause(true, reinitOnPause);
- break;
- case PAUSED:
- case PREPARED:
- playbackService.play();
- break;
- case PREPARING:
- playbackService.setStartWhenPrepared(!playbackService
- .isStartWhenPrepared());
- if (reinitOnPause
- && playbackService.isStartWhenPrepared() == false) {
- playbackService.reinit();
- }
- break;
- case INITIALIZED:
- playbackService.setStartWhenPrepared(true);
- playbackService.prepare();
- break;
- }
- } else {
- Log.w(TAG,
- "Play/Pause button was pressed, but playbackservice was null!");
- }
- }
-
- };
- }
-
- public OnClickListener newOnRevButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (status == PlayerStatus.PLAYING) {
- playbackService.seekDelta(-DEFAULT_SEEK_DELTA);
- }
- }
- };
- }
-
- public OnClickListener newOnFFButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (status == PlayerStatus.PLAYING) {
- playbackService.seekDelta(DEFAULT_SEEK_DELTA);
- }
- }
- };
- }
-
- public boolean serviceAvailable() {
- return playbackService != null;
- }
-
- public int getPosition() {
+ private Activity activity;
+
+ private PlaybackService playbackService;
+ private Playable media;
+ private PlayerStatus status;
+
+ private ScheduledThreadPoolExecutor schedExecutor;
+ private static final int SCHED_EX_POOLSIZE = 1;
+
+ protected MediaPositionObserver positionObserver;
+ protected ScheduledFuture positionObserverFuture;
+
+ private boolean mediaInfoLoaded = false;
+ private boolean released = false;
+
+ /**
+ * True if controller should reinit playback service if 'pause' button is
+ * pressed.
+ */
+ private boolean reinitOnPause;
+
+ public PlaybackController(Activity activity, boolean reinitOnPause) {
+ this.activity = activity;
+ this.reinitOnPause = reinitOnPause;
+ schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOLSIZE,
+ 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,
+ "Rejected execution of runnable in schedExecutor");
+ }
+ }
+ );
+ }
+
+ /**
+ * Creates a new connection to the playbackService. Should be called in the
+ * activity's onResume() method.
+ */
+ public void init() {
+ activity.registerReceiver(statusUpdate, new IntentFilter(
+ PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
+
+ activity.registerReceiver(notificationReceiver, new IntentFilter(
+ PlaybackService.ACTION_PLAYER_NOTIFICATION));
+
+ activity.registerReceiver(shutdownReceiver, new IntentFilter(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+
+ if (!released) {
+ bindToService();
+ } else {
+ throw new IllegalStateException(
+ "Can't call init() after release() has been called");
+ }
+ }
+
+ /**
+ * Should be called if the PlaybackController is no longer needed, for
+ * example in the activity's onStop() method.
+ */
+ public void release() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Releasing PlaybackController");
+
+ try {
+ activity.unregisterReceiver(statusUpdate);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+
+ try {
+ activity.unregisterReceiver(notificationReceiver);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+
+ try {
+ activity.unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+
+ try {
+ activity.unregisterReceiver(shutdownReceiver);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ cancelPositionObserver();
+ schedExecutor.shutdownNow();
+ media = null;
+ released = true;
+
+ }
+
+ /**
+ * Should be called in the activity's onPause() method.
+ */
+ public void pause() {
+ mediaInfoLoaded = false;
+ if (playbackService != null && playbackService.isPlayingVideo()) {
+ playbackService.pause(true, true);
+ }
+ }
+
+ /**
+ * Tries to establish a connection to the PlaybackService. If it isn't
+ * running, the PlaybackService will be started with the last played media
+ * as the arguments of the launch intent.
+ */
+ private void bindToService() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Trying to connect to service");
+ AsyncTask<Void, Void, Intent> intentLoader = new AsyncTask<Void, Void, Intent>() {
+ @Override
+ protected Intent doInBackground(Void... voids) {
+ return getPlayLastPlayedMediaIntent();
+ }
+
+ @Override
+ protected void onPostExecute(Intent serviceIntent) {
+ boolean bound = false;
+ if (!PlaybackService.isRunning) {
+ if (serviceIntent != null) {
+ activity.startService(serviceIntent);
+ bound = activity.bindService(serviceIntent, mConnection, 0);
+ } else {
+ status = PlayerStatus.STOPPED;
+ setupGUI();
+ handleStatus();
+ }
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "PlaybackService is running, trying to connect without start command.");
+ bound = activity.bindService(new Intent(activity,
+ PlaybackService.class), mConnection, 0);
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Result for service binding: " + bound);
+ }
+ };
+ intentLoader.execute();
+ }
+
+ /**
+ * Returns an intent that starts the PlaybackService and plays the last
+ * played media or null if no last played media could be found.
+ */
+ private Intent getPlayLastPlayedMediaIntent() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Trying to restore last played media");
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(activity.getApplicationContext());
+ long currentlyPlayingMedia = PlaybackPreferences
+ .getCurrentlyPlayingMedia();
+ if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
+ Playable media = PlayableUtils.createInstanceFromPreferences(activity,
+ (int) currentlyPlayingMedia, prefs);
+ if (media != null) {
+ Intent serviceIntent = new Intent(activity,
+ PlaybackService.class);
+ serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
+ serviceIntent.putExtra(
+ PlaybackService.EXTRA_START_WHEN_PREPARED, false);
+ serviceIntent.putExtra(
+ PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false);
+ boolean fileExists = media.localFileAvailable();
+ boolean lastIsStream = PlaybackPreferences
+ .getCurrentEpisodeIsStream();
+ if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
+ DBTasks.notifyMissingFeedMediaFile(
+ activity, (FeedMedia) media);
+ }
+ serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
+ lastIsStream || !fileExists);
+ return serviceIntent;
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "No last played media found");
+ return null;
+ }
+
+ public abstract void setupGUI();
+
+ private void setupPositionObserver() {
+ if ((positionObserverFuture != null && positionObserverFuture
+ .isCancelled())
+ || (positionObserverFuture != null && positionObserverFuture
+ .isDone()) || positionObserverFuture == null) {
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting up position observer");
+ positionObserver = new MediaPositionObserver();
+ positionObserverFuture = schedExecutor.scheduleWithFixedDelay(
+ positionObserver, MediaPositionObserver.WAITING_INTERVALL,
+ MediaPositionObserver.WAITING_INTERVALL,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private void cancelPositionObserver() {
+ if (positionObserverFuture != null) {
+ boolean result = positionObserverFuture.cancel(true);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "PositionObserver cancelled. Result: " + result);
+ }
+ }
+
+ public abstract void onPositionObserverUpdate();
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ playbackService = ((PlaybackService.LocalBinder) service)
+ .getService();
+ if (!released) {
+ queryService();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Connection to Service established");
+ } else {
+ Log.i(TAG, "Connection to playback service has been established, but controller has already been released");
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ playbackService = null;
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Disconnected from Service");
+
+ }
+ };
+
+ protected BroadcastReceiver statusUpdate = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received statusUpdate Intent.");
+ if (isConnectedToPlaybackService()) {
+ status = playbackService.getStatus();
+ handleStatus();
+ } else {
+ Log.w(TAG,
+ "Couldn't receive status update: playbackService was null");
+ bindToService();
+ }
+ }
+ };
+
+ protected BroadcastReceiver notificationReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isConnectedToPlaybackService()) {
+ int type = intent.getIntExtra(
+ PlaybackService.EXTRA_NOTIFICATION_TYPE, -1);
+ int code = intent.getIntExtra(
+ PlaybackService.EXTRA_NOTIFICATION_CODE, -1);
+ if (code != -1 && type != -1) {
+ switch (type) {
+ case PlaybackService.NOTIFICATION_TYPE_ERROR:
+ handleError(code);
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE:
+ float progress = ((float) code) / 100;
+ onBufferUpdate(progress);
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_RELOAD:
+ cancelPositionObserver();
+ mediaInfoLoaded = false;
+ onReloadNotification(intent.getIntExtra(
+ PlaybackService.EXTRA_NOTIFICATION_CODE, -1));
+ queryService();
+
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE:
+ onSleepTimerUpdate();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_START:
+ onBufferStart();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_END:
+ onBufferEnd();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
+ onPlaybackEnd();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE:
+ onPlaybackSpeedChange();
+ break;
+ }
+
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Bad arguments. Won't handle intent");
+ }
+ } else {
+ bindToService();
+ }
+ }
+
+ };
+
+ private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isConnectedToPlaybackService()) {
+ if (intent.getAction().equals(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
+ release();
+ onShutdownNotification();
+ }
+ }
+ }
+ };
+
+ public abstract void onPlaybackSpeedChange();
+
+ public abstract void onShutdownNotification();
+
+ /**
+ * Called when the currently displayed information should be refreshed.
+ */
+ public abstract void onReloadNotification(int code);
+
+ public abstract void onBufferStart();
+
+ public abstract void onBufferEnd();
+
+ public abstract void onBufferUpdate(float progress);
+
+ public abstract void onSleepTimerUpdate();
+
+ public abstract void handleError(int code);
+
+ public abstract void onPlaybackEnd();
+
+ /**
+ * Is called whenever the PlaybackService changes it's status. This method
+ * should be used to update the GUI or start/cancel background threads.
+ */
+ private void handleStatus() {
+ TypedArray res = activity.obtainStyledAttributes(new int[]{
+ R.attr.av_play, R.attr.av_pause});
+ final int playResource = res.getResourceId(0, R.drawable.av_play);
+ final int pauseResource = res.getResourceId(1, R.drawable.av_pause);
+ res.recycle();
+
+ switch (status) {
+
+ case ERROR:
+ postStatusMsg(R.string.player_error_msg);
+ break;
+ case PAUSED:
+ clearStatusMsg();
+ checkMediaInfoLoaded();
+ cancelPositionObserver();
+ updatePlayButtonAppearance(playResource);
+ break;
+ case PLAYING:
+ clearStatusMsg();
+ checkMediaInfoLoaded();
+ setupPositionObserver();
+ updatePlayButtonAppearance(pauseResource);
+ break;
+ case PREPARING:
+ postStatusMsg(R.string.player_preparing_msg);
+ checkMediaInfoLoaded();
+ if (playbackService != null) {
+ if (playbackService.isStartWhenPrepared()) {
+ updatePlayButtonAppearance(pauseResource);
+ } else {
+ updatePlayButtonAppearance(playResource);
+ }
+ }
+ break;
+ case STOPPED:
+ postStatusMsg(R.string.player_stopped_msg);
+ break;
+ case PREPARED:
+ checkMediaInfoLoaded();
+ postStatusMsg(R.string.player_ready_msg);
+ updatePlayButtonAppearance(playResource);
+ break;
+ case SEEKING:
+ postStatusMsg(R.string.player_seeking_msg);
+ break;
+ case AWAITING_VIDEO_SURFACE:
+ onAwaitingVideoSurface();
+ break;
+ case INITIALIZED:
+ checkMediaInfoLoaded();
+ clearStatusMsg();
+ updatePlayButtonAppearance(playResource);
+ break;
+ }
+ }
+
+ private void checkMediaInfoLoaded() {
+ if (!mediaInfoLoaded) {
+ loadMediaInfo();
+ }
+ mediaInfoLoaded = true;
+ }
+
+ private void updatePlayButtonAppearance(int resource) {
+ ImageButton butPlay = getPlayButton();
+ butPlay.setImageResource(resource);
+ }
+
+ public abstract ImageButton getPlayButton();
+
+ public abstract void postStatusMsg(int msg);
+
+ public abstract void clearStatusMsg();
+
+ public abstract void loadMediaInfo();
+
+ public abstract void onAwaitingVideoSurface();
+
+ /**
+ * Called when connection to playback service has been established or
+ * information has to be refreshed
+ */
+ void queryService() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Querying service info");
+ if (playbackService != null) {
+ status = playbackService.getStatus();
+ media = playbackService.getMedia();
+ if (media == null) {
+ Log.w(TAG,
+ "PlaybackService has no media object. Trying to restore last played media.");
+ Intent serviceIntent = getPlayLastPlayedMediaIntent();
+ if (serviceIntent != null) {
+ activity.startService(serviceIntent);
+ }
+ }
+ onServiceQueried();
+
+ setupGUI();
+ handleStatus();
+ // make sure that new media is loaded if it's available
+ mediaInfoLoaded = false;
+
+ } else {
+ Log.e(TAG,
+ "queryService() was called without an existing connection to playbackservice");
+ }
+ }
+
+ public abstract void onServiceQueried();
+
+ /**
+ * Should be used by classes which implement the OnSeekBarChanged interface.
+ */
+ public float onSeekBarProgressChanged(SeekBar seekBar, int progress,
+ boolean fromUser, TextView txtvPosition) {
+ if (fromUser && playbackService != null) {
+ float prog = progress / ((float) seekBar.getMax());
+ int duration = media.getDuration();
+ txtvPosition.setText(Converter
+ .getDurationStringLong((int) (prog * duration)));
+ return prog;
+ }
+ return 0;
+
+ }
+
+ /**
+ * Should be used by classes which implement the OnSeekBarChanged interface.
+ */
+ public void onSeekBarStartTrackingTouch(SeekBar seekBar) {
+ // interrupt position Observer, restart later
+ cancelPositionObserver();
+ }
+
+ /**
+ * Should be used by classes which implement the OnSeekBarChanged interface.
+ */
+ public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) {
+ if (playbackService != null) {
+ playbackService.seek((int) (prog * media.getDuration()));
+ setupPositionObserver();
+ }
+ }
+
+ public OnClickListener newOnPlayButtonClickListener() {
+ return new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (playbackService != null) {
+ switch (status) {
+ case PLAYING:
+ playbackService.pause(true, reinitOnPause);
+ break;
+ case PAUSED:
+ case PREPARED:
+ playbackService.play();
+ break;
+ case PREPARING:
+ playbackService.setStartWhenPrepared(!playbackService
+ .isStartWhenPrepared());
+ if (reinitOnPause
+ && playbackService.isStartWhenPrepared() == false) {
+ playbackService.reinit();
+ }
+ break;
+ case INITIALIZED:
+ playbackService.setStartWhenPrepared(true);
+ playbackService.prepare();
+ break;
+ }
+ } else {
+ Log.w(TAG,
+ "Play/Pause button was pressed, but playbackservice was null!");
+ }
+ }
+
+ };
+ }
+
+ public OnClickListener newOnRevButtonClickListener() {
+ return new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (status == PlayerStatus.PLAYING) {
+ playbackService.seekDelta(-DEFAULT_SEEK_DELTA);
+ }
+ }
+ };
+ }
+
+ public OnClickListener newOnFFButtonClickListener() {
+ return new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (status == PlayerStatus.PLAYING) {
+ playbackService.seekDelta(DEFAULT_SEEK_DELTA);
+ }
+ }
+ };
+ }
+
+ public boolean serviceAvailable() {
+ return playbackService != null;
+ }
+
+ public int getPosition() {
+ if (playbackService != null) {
+ return playbackService.getCurrentPositionSafe();
+ } else {
+ return PlaybackService.INVALID_TIME;
+ }
+ }
+
+ public int getDuration() {
+ if (playbackService != null) {
+ return playbackService.getDurationSafe();
+ } else {
+ return PlaybackService.INVALID_TIME;
+ }
+ }
+
+ public Playable getMedia() {
+ return media;
+ }
+
+ public boolean sleepTimerActive() {
+ return playbackService != null && playbackService.sleepTimerActive();
+ }
+
+ public boolean sleepTimerNotActive() {
+ return playbackService != null && !playbackService.sleepTimerActive();
+ }
+
+ public void disableSleepTimer() {
+ if (playbackService != null) {
+ playbackService.disableSleepTimer();
+ }
+ }
+
+ public long getSleepTimerTimeLeft() {
+ if (playbackService != null) {
+ return playbackService.getSleepTimerTimeLeft();
+ } else {
+ return INVALID_TIME;
+ }
+ }
+
+ public void setSleepTimer(long time) {
+ if (playbackService != null) {
+ playbackService.setSleepTimer(time);
+ }
+ }
+
+ public void seekToChapter(Chapter chapter) {
+ if (playbackService != null) {
+ playbackService.seekToChapter(chapter);
+ }
+ }
+
+ public void setVideoSurface(SurfaceHolder holder) {
+ if (playbackService != null) {
+ playbackService.setVideoSurface(holder);
+ }
+ }
+
+ public PlayerStatus getStatus() {
+ return status;
+ }
+
+ public boolean canSetPlaybackSpeed() {
+ return playbackService != null && playbackService.canSetSpeed();
+ }
+
+ public void setPlaybackSpeed(float speed) {
if (playbackService != null) {
- return playbackService.getCurrentPositionSafe();
- } else {
- return PlaybackService.INVALID_TIME;
+ playbackService.setSpeed(speed);
}
}
-
- public int getDuration() {
- if (playbackService != null) {
- return playbackService.getDurationSafe();
+
+ public float getCurrentPlaybackSpeedMultiplier() {
+ if (canSetPlaybackSpeed()) {
+ return playbackService.getCurrentPlaybackSpeed();
} else {
- return PlaybackService.INVALID_TIME;
+ return -1;
}
}
- public Playable getMedia() {
- return media;
- }
-
- public boolean sleepTimerActive() {
- return playbackService != null && playbackService.sleepTimerActive();
- }
-
- public boolean sleepTimerNotActive() {
- return playbackService != null && !playbackService.sleepTimerActive();
- }
-
- public void disableSleepTimer() {
- if (playbackService != null) {
- playbackService.disableSleepTimer();
- }
- }
-
- public long getSleepTimerTimeLeft() {
- if (playbackService != null) {
- return playbackService.getSleepTimerTimeLeft();
- } else {
- return INVALID_TIME;
- }
- }
-
- public void setSleepTimer(long time) {
- if (playbackService != null) {
- playbackService.setSleepTimer(time);
- }
- }
-
- public void seekToChapter(Chapter chapter) {
- if (playbackService != null) {
- playbackService.seekToChapter(chapter);
- }
- }
-
- public void setVideoSurface(SurfaceHolder holder) {
- if (playbackService != null) {
- playbackService.setVideoSurface(holder);
- }
- }
-
- public PlayerStatus getStatus() {
- return status;
- }
-
- public boolean isPlayingVideo() {
- if (playbackService != null) {
- return PlaybackService.isPlayingVideo();
- }
- return false;
- }
-
- /**
- * Returns true if PlaybackController can communicate with the playback
- * service.
- */
- public boolean isConnectedToPlaybackService() {
- return playbackService != null;
- }
-
- public void notifyVideoSurfaceAbandoned() {
- if (playbackService != null) {
- playbackService.notifyVideoSurfaceAbandoned();
- }
- }
-
- /** Move service into INITIALIZED state if it's paused to save bandwidth */
- public void reinitServiceIfPaused() {
- if (playbackService != null
- && playbackService.isShouldStream()
- && (playbackService.getStatus() == PlayerStatus.PAUSED || (playbackService
- .getStatus() == PlayerStatus.PREPARING && playbackService
- .isStartWhenPrepared() == false))) {
- playbackService.reinit();
- }
- }
-
- /** Refreshes the current position of the media file that is playing. */
- public class MediaPositionObserver implements Runnable {
-
- public static final int WAITING_INTERVALL = 1000;
-
- @Override
- public void run() {
- if (playbackService != null && playbackService.getPlayer() != null
- && playbackService.getPlayer().isPlaying()) {
- activity.runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- onPositionObserverUpdate();
- }
- });
- }
- }
- }
+ public boolean isPlayingVideo() {
+ if (playbackService != null) {
+ return PlaybackService.isPlayingVideo();
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns true if PlaybackController can communicate with the playback
+ * service.
+ */
+ public boolean isConnectedToPlaybackService() {
+ return playbackService != null;
+ }
+
+ public void notifyVideoSurfaceAbandoned() {
+ if (playbackService != null) {
+ playbackService.notifyVideoSurfaceAbandoned();
+ }
+ }
+
+ /**
+ * Move service into INITIALIZED state if it's paused to save bandwidth
+ */
+ public void reinitServiceIfPaused() {
+ if (playbackService != null
+ && playbackService.isShouldStream()
+ && (playbackService.getStatus() == PlayerStatus.PAUSED || (playbackService
+ .getStatus() == PlayerStatus.PREPARING && playbackService
+ .isStartWhenPrepared() == false))) {
+ playbackService.reinit();
+ }
+ }
+
+ /**
+ * Refreshes the current position of the media file that is playing.
+ */
+ public class MediaPositionObserver implements Runnable {
+
+ public static final int WAITING_INTERVALL = 1000;
+
+ @Override
+ public void run() {
+ if (playbackService != null && playbackService.getPlayer() != null
+ && playbackService.getPlayer().isPlaying()) {
+ activity.runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ onPositionObserverUpdate();
+ }
+ });
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/util/playback/VideoPlayer.java b/src/de/danoeh/antennapod/util/playback/VideoPlayer.java
new file mode 100644
index 000000000..f0a50542c
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/playback/VideoPlayer.java
@@ -0,0 +1,62 @@
+package de.danoeh.antennapod.util.playback;
+
+import android.media.MediaPlayer;
+import android.util.Log;
+
+public class VideoPlayer extends MediaPlayer implements IPlayer {
+ private static final String TAG = "VideoPlayer";
+
+ @Override
+ public boolean canSetPitch() {
+ return false;
+ }
+
+ @Override
+ public boolean canSetSpeed() {
+ return false;
+ }
+
+ @Override
+ public float getCurrentPitchStepsAdjustment() {
+ return 1;
+ }
+
+ @Override
+ public float getCurrentSpeedMultiplier() {
+ return 1;
+ }
+
+ @Override
+ public float getMaxSpeedMultiplier() {
+ return 1;
+ }
+
+ @Override
+ public float getMinSpeedMultiplier() {
+ return 1;
+ }
+
+ @Override
+ public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) throws UnsupportedOperationException {
+ Log.e(TAG, "Setting enable speed adjustment unsupported in video player");
+ throw new UnsupportedOperationException("Setting enable speed adjustment unsupported in video player");
+ }
+
+ @Override
+ public void setPitchStepsAdjustment(float pitchSteps) {
+ Log.e(TAG, "Setting pitch steps adjustment unsupported in video player");
+ throw new UnsupportedOperationException("Setting pitch steps adjustment unsupported in video player");
+ }
+
+ @Override
+ public void setPlaybackPitch(float f) {
+ Log.e(TAG, "Setting playback pitch unsupported in video player");
+ throw new UnsupportedOperationException("Setting playback pitch unsupported in video player");
+ }
+
+ @Override
+ public void setPlaybackSpeed(float f) {
+ Log.e(TAG, "Setting playback speed unsupported in video player");
+ throw new UnsupportedOperationException("Setting playback speed unsupported in video player");
+ }
+}