summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md1
-rw-r--r--app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java60
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java4
-rw-r--r--app/src/main/AndroidManifest.xml2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java255
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java11
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java567
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java (renamed from app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java)131
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java5
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java5
-rw-r--r--app/src/main/res/layout-v14/time_dialog.xml33
-rw-r--r--app/src/main/res/layout/time_dialog.xml31
-rw-r--r--core/src/main/AndroidManifest.xml1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java26
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java105
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java65
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java4
-rw-r--r--core/src/main/res/values/strings.xml3
22 files changed, 746 insertions, 598 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ccb3a274a..100875b99 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,6 +15,7 @@ How to submit a feature request
- If an issue has already been opened, see if you can add anything useful to it.
- Otherwise, create a new issue on the "issues" page
- Give a brief explanation about the problem that currently exists and why your requested feature solves this problem.
+- Try do be as specific as possible. Please not only explain *what* the feature does, but also *how*. When your request is about changing or extending the UI, describe what the UI would look like and how the user would interact with it.
Translating AntennaPod
----------------------
diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java
index edb576249..385201f25 100644
--- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java
@@ -122,11 +122,21 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
}
@Override
+ public void onSleepTimerAlmostExpired() {
+
+ }
+
+ @Override
public void onSleepTimerExpired() {
}
@Override
+ public void onSleepTimerReset() {
+
+ }
+
+ @Override
public void onWidgetUpdaterTick() {
}
@@ -170,11 +180,21 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
}
@Override
+ public void onSleepTimerAlmostExpired() {
+
+ }
+
+ @Override
public void onSleepTimerExpired() {
}
@Override
+ public void onSleepTimerReset() {
+
+ }
+
+ @Override
public void onWidgetUpdaterTick() {
countDownLatch.countDown();
}
@@ -221,7 +241,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
pstm.startWidgetUpdater();
pstm.startPositionSaver();
- pstm.setSleepTimer(100000);
+ pstm.setSleepTimer(100000, false, false);
pstm.cancelAllTasks();
assertFalse(pstm.isPositionSaverActive());
assertFalse(pstm.isWidgetUpdaterActive());
@@ -241,6 +261,11 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
}
@Override
+ public void onSleepTimerAlmostExpired() {
+
+ }
+
+ @Override
public void onSleepTimerExpired() {
if (countDownLatch.getCount() == 0) {
fail();
@@ -249,6 +274,11 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
}
@Override
+ public void onSleepTimerReset() {
+
+ }
+
+ @Override
public void onWidgetUpdaterTick() {
}
@@ -258,7 +288,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
}
});
- pstm.setSleepTimer(TIME);
+ pstm.setSleepTimer(TIME, false, false);
countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
pstm.shutdown();
}
@@ -275,11 +305,21 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
}
@Override
+ public void onSleepTimerAlmostExpired() {
+
+ }
+
+ @Override
public void onSleepTimerExpired() {
fail("Sleeptimer expired");
}
@Override
+ public void onSleepTimerReset() {
+
+ }
+
+ @Override
public void onWidgetUpdaterTick() {
}
@@ -289,7 +329,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
}
});
- pstm.setSleepTimer(TIME);
+ pstm.setSleepTimer(TIME, false, false);
pstm.disableSleepTimer();
assertFalse(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS));
pstm.shutdown();
@@ -298,7 +338,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
public void testIsSleepTimerActivePositive() {
final Context c = getInstrumentation().getTargetContext();
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
- pstm.setSleepTimer(10000);
+ pstm.setSleepTimer(10000, false, false);
assertTrue(pstm.isSleepTimerActive());
pstm.shutdown();
}
@@ -306,7 +346,7 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
public void testIsSleepTimerActiveNegative() {
final Context c = getInstrumentation().getTargetContext();
PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM);
- pstm.setSleepTimer(10000);
+ pstm.setSleepTimer(10000, false, false);
pstm.disableSleepTimer();
assertFalse(pstm.isSleepTimerActive());
pstm.shutdown();
@@ -319,11 +359,21 @@ public class PlaybackServiceTaskManagerTest extends InstrumentationTestCase {
}
@Override
+ public void onSleepTimerAlmostExpired() {
+
+ }
+
+ @Override
public void onSleepTimerExpired() {
}
@Override
+ public void onSleepTimerReset() {
+
+ }
+
+ @Override
public void onWidgetUpdaterTick() {
}
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
index 65c962f01..286095210 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
@@ -13,8 +13,8 @@ import java.util.Arrays;
import java.util.List;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
@@ -79,7 +79,7 @@ public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActiv
solo.clickOnText(solo.getString(R.string.add_feed_label));
solo.enterText(0, feed.getDownload_url());
solo.clickOnButton(solo.getString(R.string.confirm_label));
- solo.waitForActivity(DefaultOnlineFeedViewActivity.class);
+ solo.waitForActivity(OnlineFeedViewActivity.class);
solo.waitForView(R.id.butSubscribe);
assertEquals(solo.getString(R.string.subscribe_label), solo.getButton(0).getText().toString());
solo.clickOnButton(0);
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 710446885..72e7752b1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -203,7 +203,7 @@
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
<activity
- android:name=".activity.DefaultOnlineFeedViewActivity"
+ android:name=".activity.OnlineFeedViewActivity"
android:configChanges="orientation"
android:label="@string/add_feed_label">
<meta-data
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java
deleted file mode 100644
index 0993ed6d6..000000000
--- a/app/src/main/java/de/danoeh/antennapod/activity/DefaultOnlineFeedViewActivity.java
+++ /dev/null
@@ -1,255 +0,0 @@
-package de.danoeh.antennapod.activity;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.support.v4.app.NavUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.engine.DiskCacheStrategy;
-
-import org.apache.commons.lang3.StringUtils;
-import org.jsoup.Jsoup;
-import org.jsoup.examples.HtmlToPlainText;
-import org.jsoup.nodes.Document;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-import de.danoeh.antennapod.BuildConfig;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter;
-import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
-import de.danoeh.antennapod.core.feed.EventDistributor;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.glide.ApGlideSettings;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.DownloadRequestException;
-import de.danoeh.antennapod.core.storage.DownloadRequester;
-
-/**
- * Default implementation of OnlineFeedViewActivity. Shows the downloaded feed's items with their descriptions,
- * a subscribe button and a spinner for choosing alternate feed URLs.
- */
-public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity {
- private static final String TAG = "DefaultOnlineFeedViewActivity";
-
- private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED | EventDistributor.FEED_LIST_UPDATE;
- private volatile List<Feed> feeds;
- private Feed feed;
- private String selectedDownloadUrl;
-
- private Button subscribeButton;
-
- @Override
- protected void onCreate(Bundle arg0) {
- super.onCreate(arg0);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- Intent destIntent = new Intent(this, MainActivity.class);
- if (NavUtils.shouldUpRecreateTask(this, destIntent)) {
- startActivity(destIntent);
- } else {
- NavUtils.navigateUpFromSameTask(this);
- }
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected void loadData() {
- super.loadData();
- feeds = DBReader.getFeedList(this);
- }
-
- @Override
- protected void beforeShowFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) {
- super.beforeShowFeedInformation(feed, alternateFeedUrls);
-
- // remove HTML tags from descriptions
-
- if (BuildConfig.DEBUG) Log.d(TAG, "Removing HTML from shownotes");
- if (feed.getItems() != null) {
- HtmlToPlainText formatter = new HtmlToPlainText();
- for (FeedItem item : feed.getItems()) {
- if (item.getDescription() != null) {
- Document description = Jsoup.parse(item.getDescription());
- item.setDescription(StringUtils.trim(formatter.getPlainText(description)));
- }
- }
- }
- }
-
- @Override
- protected void showFeedInformation(final Feed feed, final Map<String, String> alternateFeedUrls) {
- super.showFeedInformation(feed, alternateFeedUrls);
- setContentView(R.layout.listview_activity);
-
- this.feed = feed;
- this.selectedDownloadUrl = feed.getDownload_url();
- EventDistributor.getInstance().register(listener);
- ListView listView = (ListView) findViewById(R.id.listview);
- LayoutInflater inflater = (LayoutInflater)
- getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View header = inflater.inflate(R.layout.onlinefeedview_header, listView, false);
- listView.addHeaderView(header);
-
- listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems()));
-
- ImageView cover = (ImageView) header.findViewById(R.id.imgvCover);
- TextView title = (TextView) header.findViewById(R.id.txtvTitle);
- TextView author = (TextView) header.findViewById(R.id.txtvAuthor);
- TextView description = (TextView) header.findViewById(R.id.txtvDescription);
- Spinner spAlternateUrls = (Spinner) header.findViewById(R.id.spinnerAlternateUrls);
-
- subscribeButton = (Button) header.findViewById(R.id.butSubscribe);
-
- if (feed.getImage() != null && StringUtils.isNotBlank(feed.getImage().getDownload_url())) {
- Glide.with(this)
- .load(feed.getImage().getDownload_url())
- .placeholder(R.color.light_gray)
- .error(R.color.light_gray)
- .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
- .fitCenter()
- .dontAnimate()
- .into(cover);
- }
-
- title.setText(feed.getTitle());
- author.setText(feed.getAuthor());
- description.setText(feed.getDescription());
-
- subscribeButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- try {
- Feed f = new Feed(selectedDownloadUrl, new Date(0), feed.getTitle());
- f.setPreferences(feed.getPreferences());
- DefaultOnlineFeedViewActivity.this.feed = f;
-
- DownloadRequester.getInstance().downloadFeed(
- DefaultOnlineFeedViewActivity.this,
- f);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(DefaultOnlineFeedViewActivity.this,
- e.getMessage());
- }
- setSubscribeButtonState(feed);
- }
- });
-
- if (alternateFeedUrls.isEmpty()) {
- spAlternateUrls.setVisibility(View.GONE);
- } else {
- spAlternateUrls.setVisibility(View.VISIBLE);
-
- final List<String> alternateUrlsList = new ArrayList<String>();
- final List<String> alternateUrlsTitleList = new ArrayList<String>();
-
- alternateUrlsList.add(feed.getDownload_url());
- alternateUrlsTitleList.add(feed.getTitle());
-
-
- alternateUrlsList.addAll(alternateFeedUrls.keySet());
- for (String url : alternateFeedUrls.keySet()) {
- alternateUrlsTitleList.add(alternateFeedUrls.get(url));
- }
- ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, alternateUrlsTitleList);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spAlternateUrls.setAdapter(adapter);
- spAlternateUrls.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- selectedDownloadUrl = alternateUrlsList.get(position);
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
-
- }
- });
-
-
- }
- setSubscribeButtonState(feed);
-
- }
-
- private boolean feedInFeedlist(Feed feed) {
- if (feeds == null || feed == null)
- return false;
- for (Feed f : feeds) {
- if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) {
- return true;
- }
- }
- return false;
- }
-
- private void setSubscribeButtonState(Feed feed) {
- if (subscribeButton != null && feed != null) {
- if (DownloadRequester.getInstance().isDownloadingFile(feed.getDownload_url())) {
- subscribeButton.setEnabled(false);
- subscribeButton.setText(R.string.downloading_label);
- } else if (feedInFeedlist(feed)) {
- subscribeButton.setEnabled(false);
- subscribeButton.setText(R.string.subscribed_label);
- } else {
- subscribeButton.setEnabled(true);
- subscribeButton.setText(R.string.subscribe_label);
- }
- }
- }
-
- EventDistributor.EventListener listener = new EventDistributor.EventListener() {
- @Override
- public void update(EventDistributor eventDistributor, Integer arg) {
- if ((arg & EventDistributor.FEED_LIST_UPDATE) != 0) {
- new AsyncTask<Void, Void, List<Feed>>() {
- @Override
- protected List<Feed> doInBackground(Void... params) {
- return DBReader.getFeedList(DefaultOnlineFeedViewActivity.this);
- }
-
- @Override
- protected void onPostExecute(List<Feed> feeds) {
- super.onPostExecute(feeds);
- DefaultOnlineFeedViewActivity.this.feeds = feeds;
- setSubscribeButtonState(feed);
- }
- }.execute();
- } else if ((arg & EVENTS) != 0) {
- setSubscribeButtonState(feed);
- }
- }
- };
-
- @Override
- protected void onStop() {
- super.onStop();
- EventDistributor.getInstance().unregister(listener);
- }
-}
-
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
index 40a0d1801..e34b4dc67 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
@@ -31,7 +31,7 @@ import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
-import de.danoeh.antennapod.dialog.TimeDialog;
+import de.danoeh.antennapod.dialog.SleepTimerDialog;
/**
* Provides general features which are both needed for playing audio and video
@@ -323,13 +323,10 @@ public abstract class MediaplayerActivity extends ActionBarActivity
break;
case R.id.set_sleeptimer_item:
if (controller.serviceAvailable()) {
- TimeDialog td = new TimeDialog(this,
- R.string.set_sleeptimer_label,
- R.string.set_sleeptimer_label) {
-
+ SleepTimerDialog td = new SleepTimerDialog(this, 0, 0) {
@Override
- public void onTimeEntered(long millis) {
- controller.setSleepTimer(millis);
+ public void onTimerSet(long millis, boolean shakeToReset, boolean vibrate) {
+ controller.setSleepTimer(millis, shakeToReset, vibrate);
}
};
td.show();
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
index 3ab384012..13d5e7acf 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -4,18 +4,32 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.os.Bundle;
+import android.os.Looper;
+import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
import android.widget.LinearLayout;
+import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
import org.apache.commons.lang3.StringUtils;
-import org.xml.sax.SAXException;
+import org.jsoup.Jsoup;
+import org.jsoup.examples.HtmlToPlainText;
+import org.jsoup.nodes.Document;
import java.io.File;
import java.io.IOException;
@@ -24,16 +38,22 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
-import javax.xml.parsers.ParserConfigurationException;
-
import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter;
+import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
+import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.download.Downloader;
import de.danoeh.antennapod.core.service.download.HttpDownloader;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DownloadRequestException;
+import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
import de.danoeh.antennapod.core.syndication.handler.FeedHandlerResult;
import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException;
@@ -43,6 +63,11 @@ import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.core.util.URLChecker;
import de.danoeh.antennapod.core.util.syndication.FeedDiscoverer;
import de.danoeh.antennapod.dialog.AuthenticationDialog;
+import rx.Observable;
+import rx.Subscriber;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
/**
* Downloads a feed from a feed URL and parses it. Subclasses can display the
@@ -52,27 +77,57 @@ import de.danoeh.antennapod.dialog.AuthenticationDialog;
* 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 ActionBarActivity {
+public class OnlineFeedViewActivity extends ActionBarActivity {
+
private static final String TAG = "OnlineFeedViewActivity";
+
public static final String ARG_FEEDURL = "arg.feedurl";
- /**
- * Optional argument: specify a title for the actionbar.
- */
+ // Optional argument: specify a title for the actionbar.
public static final String ARG_TITLE = "title";
+ private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED | EventDistributor.FEED_LIST_UPDATE;
+
public static final int RESULT_ERROR = 2;
+ private volatile List<Feed> feeds;
private Feed feed;
- private Map<String, String> alternateFeedUrls;
+ private String selectedDownloadUrl;
private Downloader downloader;
private boolean isPaused;
+ private Dialog dialog;
+
+ private Button subscribeButton;
+
+ private Subscription download;
+ private Subscription parser;
+ private Subscription updater;
+
+ private EventDistributor.EventListener listener = new EventDistributor.EventListener() {
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((arg & EventDistributor.FEED_LIST_UPDATE) != 0) {
+ updater = Observable.defer(() -> Observable.just(DBReader.getFeedList(OnlineFeedViewActivity.this)))
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(feeds -> {
+ OnlineFeedViewActivity.this.feeds = feeds;
+ setSubscribeButtonState(feed);
+ }
+ );
+ } else if ((arg & EVENTS) != 0) {
+ setSubscribeButtonState(feed);
+ }
+ }
+ };
+
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
if (getIntent() != null && getIntent().hasExtra(ARG_TITLE)) {
getSupportActionBar().setTitle(getIntent().getStringExtra(ARG_TITLE));
@@ -87,7 +142,6 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|| StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
feedUrl = (StringUtils.equals(getIntent().getAction(), Intent.ACTION_SEND))
? getIntent().getStringExtra(Intent.EXTRA_TEXT) : getIntent().getDataString();
-
getSupportActionBar().setTitle(R.string.add_new_feed_label);
} else {
throw new IllegalArgumentException("Activity must be started with feedurl argument!");
@@ -102,16 +156,63 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
}
}
+ /**
+ * Displays a progress indicator.
+ */
+ private void setLoadingLayout() {
+ RelativeLayout rl = new RelativeLayout(this);
+ RelativeLayout.LayoutParams rlLayoutParams = new RelativeLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT);
+
+ ProgressBar pb = new ProgressBar(this);
+ pb.setIndeterminate(true);
+ RelativeLayout.LayoutParams pbLayoutParams = new RelativeLayout.LayoutParams(
+ LinearLayout.LayoutParams.WRAP_CONTENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT);
+ pbLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
+ rl.addView(pb, pbLayoutParams);
+ addContentView(rl, rlLayoutParams);
+ }
+
@Override
protected void onResume() {
super.onResume();
isPaused = false;
+ EventDistributor.getInstance().register(listener);
+
}
@Override
protected void onPause() {
super.onPause();
isPaused = true;
+ EventDistributor.getInstance().unregister(listener);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (downloader != null && !downloader.isFinished()) {
+ downloader.cancel();
+ }
+ if(dialog != null && dialog.isShowing()) {
+ dialog.dismiss();
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if(updater != null) {
+ updater.unsubscribe();
+ }
+ if(download != null) {
+ download.unsubscribe();
+ }
+ if(parser != null) {
+ parser.unsubscribe();
+ }
}
@Override
@@ -123,14 +224,6 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
}
}
- @Override
- protected void onStop() {
- super.onStop();
- if (downloader != null && !downloader.isFinished()) {
- downloader.cancel();
- }
- }
-
private void resetIntent(String url, String title) {
Intent intent = new Intent();
intent.putExtra(ARG_FEEDURL, url);
@@ -138,41 +231,19 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
setIntent(intent);
}
-
- private void onDownloadCompleted(final Downloader downloader) {
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- Log.d(TAG, "Download was completed");
- DownloadStatus status = downloader.getResult();
- if (status != null) {
- if (!status.isCancelled()) {
- if (status.isSuccessful()) {
- parseFeed();
- } else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
- if (!isFinishing() && !isPaused) {
- Dialog dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this,
- R.string.authentication_notification_title, downloader.getDownloadRequest().getSource());
- dialog.show();
- }
- } else {
- String errorMsg = status.getReason().getErrorString(
- OnlineFeedViewActivity.this);
- if (errorMsg != null
- && status.getReasonDetailed() != null) {
- errorMsg += " (" + status.getReasonDetailed() + ")";
- }
- showErrorDialog(errorMsg);
- }
- }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent destIntent = new Intent(this, MainActivity.class);
+ if (NavUtils.shouldUpRecreateTask(this, destIntent)) {
+ startActivity(destIntent);
} else {
- Log.wtf(TAG, "DownloadStatus returned by Downloader was null");
- finish();
+ NavUtils.navigateUpFromSameTask(this);
}
- }
- });
-
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
}
private void startFeedDownload(String url, String username, String password) {
@@ -186,133 +257,226 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
FileNameGenerator.generateFileName(feed.getDownload_url())).toString();
feed.setFile_url(fileUrl);
final DownloadRequest request = new DownloadRequest(feed.getFile_url(),
- feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password, true, null);
- downloader = new HttpDownloader(request);
- new Thread() {
- @Override
- public void run() {
- loadData();
- downloader.call();
- onDownloadCompleted(downloader);
- }
- }.start();
-
-
- }
+ feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password,
+ true, null);
- /**
- * Displays a progress indicator.
- */
- private void setLoadingLayout() {
- RelativeLayout rl = new RelativeLayout(this);
- RelativeLayout.LayoutParams rlLayoutParams = new RelativeLayout.LayoutParams(
- LinearLayout.LayoutParams.MATCH_PARENT,
- LinearLayout.LayoutParams.MATCH_PARENT);
-
- ProgressBar pb = new ProgressBar(this);
- pb.setIndeterminate(true);
- RelativeLayout.LayoutParams pbLayoutParams = new RelativeLayout.LayoutParams(
- LinearLayout.LayoutParams.WRAP_CONTENT,
- LinearLayout.LayoutParams.WRAP_CONTENT);
- pbLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
- rl.addView(pb, pbLayoutParams);
- addContentView(rl, rlLayoutParams);
+ download = Observable.create(new Observable.OnSubscribe<DownloadStatus>() {
+ @Override
+ public void call(Subscriber<? super DownloadStatus> subscriber) {
+ feeds = DBReader.getFeedList(OnlineFeedViewActivity.this);
+ downloader = new HttpDownloader(request);
+ downloader.call();
+ Log.d(TAG, "Download was completed");
+ subscriber.onNext(downloader.getResult());
+ subscriber.onCompleted();
+ }
+ })
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(status -> {
+ if (status != null) {
+ if (!status.isCancelled()) {
+ if (status.isSuccessful()) {
+ parseFeed();
+ } else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) {
+ if (!isFinishing() && !isPaused) {
+ dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this,
+ R.string.authentication_notification_title, downloader.getDownloadRequest().getSource());
+ dialog.show();
+ }
+ } else {
+ String errorMsg = status.getReason().getErrorString(OnlineFeedViewActivity.this);
+ if (errorMsg != null && status.getReasonDetailed() != null) {
+ errorMsg += " (" + status.getReasonDetailed() + ")";
+ }
+ showErrorDialog(errorMsg);
+ }
+ }
+ } else {
+ Log.wtf(TAG, "DownloadStatus returned by Downloader was null");
+ finish();
+ }
+ });
}
private void parseFeed() {
if (feed == null || feed.getFile_url() == null && feed.isDownloaded()) {
- throw new IllegalStateException(
- "feed must be non-null and downloaded when parseFeed is called");
+ throw new IllegalStateException("feed must be non-null and downloaded when parseFeed is called");
}
-
Log.d(TAG, "Parsing feed");
- Thread thread = new Thread() {
-
+ parser = Observable.create(new Observable.OnSubscribe<FeedHandlerResult>() {
@Override
- public void run() {
- String reasonDetailed = "";
- boolean successful = false;
- FeedHandler handler = new FeedHandler();
- try {
- FeedHandlerResult result = handler.parseFeed(feed);
- feed = result.feed;
- alternateFeedUrls = result.alternateFeedUrls;
- successful = true;
- } catch (SAXException e) {
- e.printStackTrace();
- reasonDetailed = e.getMessage();
- } catch (IOException e) {
- e.printStackTrace();
- reasonDetailed = e.getMessage();
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- reasonDetailed = e.getMessage();
- } catch (UnsupportedFeedtypeException e) {
- Log.d(TAG, "Unsupported feed type detected");
- if (StringUtils.equalsIgnoreCase("html", e.getRootElement())) {
- if (showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url())) {
- return;
+ public void call(Subscriber<? super FeedHandlerResult> subscriber) {
+ FeedHandler handler = new FeedHandler();
+ try {
+ FeedHandlerResult result = handler.parseFeed(feed);
+ subscriber.onNext(result);
+ } catch (UnsupportedFeedtypeException e) {
+ Log.d(TAG, "Unsupported feed type detected");
+ if (StringUtils.equalsIgnoreCase("html", e.getRootElement())) {
+ showFeedDiscoveryDialog(new File(feed.getFile_url()), feed.getDownload_url());
+ } else {
+ subscriber.onError(e);
+ }
+ } catch (Exception e) {
+ subscriber.onError(e);
+ } finally {
+ boolean rc = new File(feed.getFile_url()).delete();
+ Log.d(TAG, "Deleted feed source file. Result: " + rc);
+ subscriber.onCompleted();
}
- } else {
- e.printStackTrace();
- reasonDetailed = e.getMessage();
}
- } finally {
- boolean rc = new File(feed.getFile_url()).delete();
- Log.d(TAG, "Deleted feed source file. Result: " + rc);
- }
-
- if (successful) {
- beforeShowFeedInformation(feed, alternateFeedUrls);
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- showFeedInformation(feed, alternateFeedUrls);
- }
- });
- } else {
- final String errorMsg =
- DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
- OnlineFeedViewActivity.this)
- + " (" + reasonDetailed + ")";
- runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- showErrorDialog(errorMsg);
- }
- });
- }
- }
- };
- thread.start();
- }
-
- /**
- * Can be used to load data asynchronously.
- */
- protected void loadData() {
-
+ })
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(result -> {
+ beforeShowFeedInformation(result.feed);
+ showFeedInformation(result.feed, result.alternateFeedUrls);
+ }, error -> {
+ String errorMsg = DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
+ OnlineFeedViewActivity.this) + " (" + error.getMessage() + ")";
+ showErrorDialog(errorMsg);
+ });
}
/**
* Called after the feed has been downloaded and parsed and before showFeedInformation is called.
* This method is executed on a background thread
*/
- protected void beforeShowFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) {
-
+ private void beforeShowFeedInformation(Feed feed) {
+ // remove HTML tags from descriptions
+ Log.d(TAG, "Removing HTML from shownotes");
+ if (feed.getItems() != null) {
+ HtmlToPlainText formatter = new HtmlToPlainText();
+ for (FeedItem item : feed.getItems()) {
+ if (item.getDescription() != null) {
+ Document description = Jsoup.parse(item.getDescription());
+ item.setDescription(StringUtils.trim(formatter.getPlainText(description)));
+ }
+ }
+ }
}
/**
* Called when feed parsed successfully.
* This method is executed on the GUI thread.
*/
- protected void showFeedInformation(Feed feed, Map<String, String> alternateFeedUrls) {
+ private void showFeedInformation(final Feed feed, Map<String, String> alternateFeedUrls) {
+ setContentView(R.layout.listview_activity);
+
+ this.feed = feed;
+ this.selectedDownloadUrl = feed.getDownload_url();
+ EventDistributor.getInstance().register(listener);
+ ListView listView = (ListView) findViewById(R.id.listview);
+ LayoutInflater inflater = (LayoutInflater)
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ View header = inflater.inflate(R.layout.onlinefeedview_header, listView, false);
+ listView.addHeaderView(header);
+
+ listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems()));
+
+ ImageView cover = (ImageView) header.findViewById(R.id.imgvCover);
+ TextView title = (TextView) header.findViewById(R.id.txtvTitle);
+ TextView author = (TextView) header.findViewById(R.id.txtvAuthor);
+ TextView description = (TextView) header.findViewById(R.id.txtvDescription);
+ Spinner spAlternateUrls = (Spinner) header.findViewById(R.id.spinnerAlternateUrls);
+
+ subscribeButton = (Button) header.findViewById(R.id.butSubscribe);
+
+ if (feed.getImage() != null && StringUtils.isNotBlank(feed.getImage().getDownload_url())) {
+ Glide.with(this)
+ .load(feed.getImage().getDownload_url())
+ .placeholder(R.color.light_gray)
+ .error(R.color.light_gray)
+ .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
+ .fitCenter()
+ .dontAnimate()
+ .into(cover);
+ }
+
+ title.setText(feed.getTitle());
+ author.setText(feed.getAuthor());
+ description.setText(feed.getDescription());
+
+ subscribeButton.setOnClickListener(v -> {
+ try {
+ Feed f = new Feed(selectedDownloadUrl, new Date(0), feed.getTitle());
+ f.setPreferences(feed.getPreferences());
+ this.feed = f;
+
+ DownloadRequester.getInstance().downloadFeed(this, f);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(OnlineFeedViewActivity.this,
+ e.getMessage());
+ }
+ setSubscribeButtonState(feed);
+ });
+
+ if (alternateFeedUrls.isEmpty()) {
+ spAlternateUrls.setVisibility(View.GONE);
+ } else {
+ spAlternateUrls.setVisibility(View.VISIBLE);
+
+ final List<String> alternateUrlsList = new ArrayList<>();
+ final List<String> alternateUrlsTitleList = new ArrayList<>();
+
+ alternateUrlsList.add(feed.getDownload_url());
+ alternateUrlsTitleList.add(feed.getTitle());
+
+ alternateUrlsList.addAll(alternateFeedUrls.keySet());
+ for (String url : alternateFeedUrls.keySet()) {
+ alternateUrlsTitleList.add(alternateFeedUrls.get(url));
+ }
+ ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, alternateUrlsTitleList);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spAlternateUrls.setAdapter(adapter);
+ spAlternateUrls.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ selectedDownloadUrl = alternateUrlsList.get(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+
+ }
+ });
+ }
+ setSubscribeButtonState(feed);
+ }
+
+ private void setSubscribeButtonState(Feed feed) {
+ if (subscribeButton != null && feed != null) {
+ if (DownloadRequester.getInstance().isDownloadingFile(feed.getDownload_url())) {
+ subscribeButton.setEnabled(false);
+ subscribeButton.setText(R.string.downloading_label);
+ } else if (feedInFeedlist(feed)) {
+ subscribeButton.setEnabled(false);
+ subscribeButton.setText(R.string.subscribed_label);
+ } else {
+ subscribeButton.setEnabled(true);
+ subscribeButton.setText(R.string.subscribe_label);
+ }
+ }
+ }
+
+ private boolean feedInFeedlist(Feed feed) {
+ if (feeds == null || feed == null) {
+ return false;
+ }
+ for (Feed f : feeds) {
+ if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) {
+ return true;
+ }
+ }
+ return false;
}
private void showErrorDialog(String errorMsg) {
+ assert(Looper.myLooper() == Looper.getMainLooper()); // run on UI thread
if (!isFinishing() && !isPaused) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.error_label);
@@ -322,85 +486,71 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
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();
- }
+ (dialog, which) -> {
+ dialog.cancel();
}
);
- builder.setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- setResult(RESULT_ERROR);
- finish();
- }
+ builder.setOnCancelListener(dialog -> {
+ setResult(RESULT_ERROR);
+ finish();
});
- builder.show();
+ if(dialog != null && dialog.isShowing()) {
+ dialog.dismiss();
+ }
+ dialog = builder.show();
}
}
- private boolean showFeedDiscoveryDialog(File feedFile, String baseUrl) {
+ private void showFeedDiscoveryDialog(File feedFile, String baseUrl) {
FeedDiscoverer fd = new FeedDiscoverer();
final Map<String, String> urlsMap;
try {
urlsMap = fd.findLinks(feedFile, baseUrl);
if (urlsMap == null || urlsMap.isEmpty()) {
- return false;
+ return;
}
} catch (IOException e) {
e.printStackTrace();
- return false;
+ return;
}
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (isPaused || isFinishing()) {
- return;
- }
+ if (isPaused || isFinishing()) {
+ return;
+ }
- final List<String> titles = new ArrayList<String>();
- final List<String> urls = new ArrayList<String>();
+ final List<String> titles = new ArrayList<>();
+ final List<String> urls = new ArrayList<>();
- urls.addAll(urlsMap.keySet());
- for (String url : urls) {
- titles.add(urlsMap.get(url));
- }
+ urls.addAll(urlsMap.keySet());
+ for (String url : urls) {
+ titles.add(urlsMap.get(url));
+ }
- final ArrayAdapter<String> adapter = new ArrayAdapter<String>(OnlineFeedViewActivity.this, R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles);
- DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String selectedUrl = urls.get(which);
- dialog.dismiss();
- resetIntent(selectedUrl, titles.get(which));
- FeedPreferences prefs = feed.getPreferences();
- if(prefs != null) {
- startFeedDownload(selectedUrl, prefs.getUsername(), prefs.getPassword());
- } else {
- startFeedDownload(selectedUrl, null, null);
- }
- }
- };
-
- AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this)
- .setTitle(R.string.feeds_label)
- .setCancelable(true)
- .setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- finish();
- }
- })
- .setAdapter(adapter, onClickListener);
- ab.show();
+ final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this, R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles);
+ DialogInterface.OnClickListener onClickListener = (dialog, which) -> {
+ String selectedUrl = urls.get(which);
+ dialog.dismiss();
+ resetIntent(selectedUrl, titles.get(which));
+ FeedPreferences prefs = feed.getPreferences();
+ if(prefs != null) {
+ startFeedDownload(selectedUrl, prefs.getUsername(), prefs.getPassword());
+ } else {
+ startFeedDownload(selectedUrl, null, null);
}
- });
+ };
+ AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this)
+ .setTitle(R.string.feeds_label)
+ .setCancelable(true)
+ .setOnCancelListener(dialog -> finish())
+ .setAdapter(adapter, onClickListener);
- return true;
+ runOnUiThread(() -> {
+ if(dialog != null && dialog.isShowing()) {
+ dialog.dismiss();
+ }
+ dialog = ab.show();
+ });
}
private class FeedViewAuthenticationDialog extends AuthenticationDialog {
@@ -423,4 +573,5 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
startFeedDownload(feedUrl, username, password);
}
}
+
}
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java
index 5c4d4c430..a699706a8 100644
--- a/app/src/main/java/de/danoeh/antennapod/dialog/TimeDialog.java
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.dialog;
import android.app.Dialog;
import android.content.Context;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@@ -9,100 +10,80 @@ import android.util.Log;
import android.view.View;
import android.view.Window;
import android.view.inputmethod.InputMethodManager;
-import android.widget.*;
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.R;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.Toast;
import java.util.concurrent.TimeUnit;
-public abstract class TimeDialog extends Dialog {
- private static final String TAG = "TimeDialog";
+import de.danoeh.antennapod.R;
+
+public abstract class SleepTimerDialog extends Dialog {
+
+ private static final String TAG = SleepTimerDialog.class.getSimpleName();
private static final int DEFAULT_SPINNER_POSITION = 1;
private Context context;
+ private String PREF_NAME = "SleepTimerDialog";
+ private String PREF_VALUE = "LastValue";
+ private String PREF_TIME_UNIT = "LastTimeUnit";
+ private String PREF_VIBRATE = "Vibrate";
+ private String PREF_SHAKE_TO_RESET = "ShakeToReset";
+ private SharedPreferences prefs;
private EditText etxtTime;
private Spinner spTimeUnit;
+ private CheckBox cbShakeToReset;
+ private CheckBox cbVibrate;
private Button butConfirm;
private Button butCancel;
- private TimeUnit[] units = {TimeUnit.SECONDS, TimeUnit.MINUTES,
- TimeUnit.HOURS};
+ private TimeUnit[] units = { TimeUnit.SECONDS, TimeUnit.MINUTES, TimeUnit.HOURS };
- public TimeDialog(Context context, int titleTextId, int leftButtonTextId) {
+ public SleepTimerDialog(Context context, int titleTextId, int leftButtonTextId) {
super(context);
this.context = context;
+ prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
- String[] spinnerContent = new String[]{context.getString(R.string.time_seconds),
+ String[] spinnerContent = new String[] {
+ context.getString(R.string.time_seconds),
context.getString(R.string.time_minutes),
- context.getString(R.string.time_hours)};
+ context.getString(R.string.time_hours) };
setContentView(R.layout.time_dialog);
etxtTime = (EditText) findViewById(R.id.etxtTime);
spTimeUnit = (Spinner) findViewById(R.id.spTimeUnit);
+ cbShakeToReset = (CheckBox) findViewById(R.id.cbShakeToReset);
+ cbVibrate = (CheckBox) findViewById(R.id.cbVibrate);
butConfirm = (Button) findViewById(R.id.butConfirm);
butCancel = (Button) findViewById(R.id.butCancel);
- butConfirm.setText(R.string.set_sleeptimer_label);
- butCancel.setText(R.string.cancel_label);
setTitle(R.string.set_sleeptimer_label);
- ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(
- this.getContext(), android.R.layout.simple_spinner_item,
- spinnerContent);
- spinnerAdapter
- .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spTimeUnit.setAdapter(spinnerAdapter);
- spTimeUnit.setSelection(DEFAULT_SPINNER_POSITION);
- butCancel.setOnClickListener(new View.OnClickListener() {
-
- @Override
- public void onClick(View v) {
- dismiss();
- }
- });
- butConfirm.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- try {
- long input = readTimeMillis();
- onTimeEntered(input);
- dismiss();
- } catch (NumberFormatException e) {
- e.printStackTrace();
- Toast toast = Toast.makeText(context,
- R.string.time_dialog_invalid_input,
- Toast.LENGTH_LONG);
- toast.show();
- }
- }
- });
+ etxtTime.setText(prefs.getString(PREF_VALUE, "15"));
etxtTime.addTextChangedListener(new TextWatcher() {
-
@Override
public void afterTextChanged(Editable s) {
checkInputLength(s.length());
}
@Override
- public void beforeTextChanged(CharSequence s, int start, int count,
- int after) {
-
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
- public void onTextChanged(CharSequence s, int start, int before,
- int count) {
-
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
- checkInputLength(etxtTime.getText().length());
etxtTime.postDelayed(new Runnable() {
@Override
public void run() {
@@ -111,23 +92,54 @@ public abstract class TimeDialog extends Dialog {
}
}, 100);
+ ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(this.getContext(),
+ android.R.layout.simple_spinner_item, spinnerContent);
+ spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ spTimeUnit.setAdapter(spinnerAdapter);
+ int selection = prefs.getInt(PREF_TIME_UNIT, DEFAULT_SPINNER_POSITION);
+ spTimeUnit.setSelection(selection);
+ cbShakeToReset.setChecked(prefs.getBoolean(PREF_SHAKE_TO_RESET, true));
+ cbVibrate.setChecked(prefs.getBoolean(PREF_VIBRATE, true));
+ butConfirm.setText(R.string.set_sleeptimer_label);
+ butConfirm.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ savePreferences();
+ long input = readTimeMillis();
+ onTimerSet(input, cbShakeToReset.isChecked(), cbVibrate.isChecked());
+ dismiss();
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ Toast toast = Toast.makeText(context, R.string.time_dialog_invalid_input,
+ Toast.LENGTH_LONG);
+ toast.show();
+ }
+ }
+ });
+
+ butCancel.setText(R.string.cancel_label);
+ butCancel.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
}
private void checkInputLength(int length) {
if (length > 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Length is larger than 0, enabling confirm button");
+ Log.d(TAG, "Length is larger than 0, enabling confirm button");
butConfirm.setEnabled(true);
} else {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Length is smaller than 0, disabling confirm button");
+ Log.d(TAG, "Length is smaller than 0, disabling confirm button");
butConfirm.setEnabled(false);
}
}
- public abstract void onTimeEntered(long millis);
+ public abstract void onTimerSet(long millis, boolean shakeToReset, boolean vibrate);
private long readTimeMillis() {
TimeUnit selectedUnit = units[spTimeUnit.getSelectedItemPosition()];
@@ -135,4 +147,13 @@ public abstract class TimeDialog extends Dialog {
return selectedUnit.toMillis(value);
}
+ private void savePreferences() {
+ prefs.edit()
+ .putString(PREF_VALUE, etxtTime.getText().toString())
+ .putInt(PREF_TIME_UNIT, spTimeUnit.getSelectedItemPosition())
+ .putBoolean(PREF_SHAKE_TO_RESET, cbShakeToReset.isChecked())
+ .putBoolean(PREF_VIBRATE, cbVibrate.isChecked())
+ .apply();
+ }
+
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
index bbe6fab46..f6c80aa7c 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
@@ -10,7 +10,6 @@ import android.widget.Button;
import android.widget.EditText;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.activity.OpmlImportFromPathActivity;
@@ -73,7 +72,7 @@ public class AddFeedFragment extends Fragment {
butConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- Intent intent = new Intent(getActivity(), DefaultOnlineFeedViewActivity.class);
+ Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, etxtFeedurl.getText().toString());
intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.add_feed_label));
startActivity(intent);
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
index edd4da7fe..72704245f 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItunesSearchFragment.java
@@ -28,7 +28,6 @@ import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.itunes.ItunesAdapter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -96,13 +95,13 @@ public class ItunesSearchFragment extends Fragment {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(getActivity(),
- DefaultOnlineFeedViewActivity.class);
+ OnlineFeedViewActivity.class);
//Tell the OnlineFeedViewActivity where to go
String url = searchResults.get(position).feedUrl;
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, url);
- intent.putExtra(DefaultOnlineFeedViewActivity.ARG_TITLE, "iTunes");
+ intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, "iTunes");
startActivity(intent);
}
});
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
index 623c6faa7..204f36956 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java
@@ -23,7 +23,6 @@ import android.widget.TextView;
import java.util.List;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter;
@@ -104,9 +103,9 @@ public abstract class PodcastListFragment extends Fragment {
protected void onPodcastSelected(GpodnetPodcast selection) {
Log.d(TAG, "Selected podcast: " + selection.toString());
- Intent intent = new Intent(getActivity(), DefaultOnlineFeedViewActivity.class);
+ Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, selection.getUrl());
- intent.putExtra(DefaultOnlineFeedViewActivity.ARG_TITLE, getString(R.string.gpodnet_main_label));
+ intent.putExtra(OnlineFeedViewActivity.ARG_TITLE, getString(R.string.gpodnet_main_label));
startActivity(intent);
}
diff --git a/app/src/main/res/layout-v14/time_dialog.xml b/app/src/main/res/layout-v14/time_dialog.xml
index 7fd4309d5..aefb82b8b 100644
--- a/app/src/main/res/layout-v14/time_dialog.xml
+++ b/app/src/main/res/layout-v14/time_dialog.xml
@@ -2,7 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical" >
+ android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
@@ -31,6 +31,37 @@
android:layout_marginTop="8dp" />
</LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:textSize="16sp"
+ android:text="@string/timer_about_to_expire_label"/>
+
+ <CheckBox android:id="@+id/cbShakeToReset"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/shake_to_reset_label"/>
+
+ <CheckBox android:id="@+id/cbVibrate"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/timer_vibration_label"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
<RelativeLayout
android:id="@+id/footer"
android:layout_width="fill_parent"
diff --git a/app/src/main/res/layout/time_dialog.xml b/app/src/main/res/layout/time_dialog.xml
index a42f87781..e387a1689 100644
--- a/app/src/main/res/layout/time_dialog.xml
+++ b/app/src/main/res/layout/time_dialog.xml
@@ -33,6 +33,37 @@
</LinearLayout>
<LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:textSize="16sp"
+ android:text="@string/timer_about_to_expire_label"/>
+
+ <CheckBox android:id="@+id/cbShakeToReset"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/shake_to_reset_label"/>
+
+ <CheckBox android:id="@+id/cbVibrate"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/timer_vibration_label"/>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
style="@android:style/ButtonBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml
index f5b000abf..47bd8b666 100644
--- a/core/src/main/AndroidManifest.xml
+++ b/core/src/main/AndroidManifest.xml
@@ -8,6 +8,7 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <uses-permission android:name="android.permission.VIBRATE"/>
<application
android:allowBackup="true"
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
index 7878d19a0..915f5ee1b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java
@@ -260,8 +260,10 @@ public class GpodnetSyncService extends Service {
GpodnetEpisodeAction mostRecent = mostRecentPlayAction.get(key);
if (mostRecent == null || mostRecent.getTimestamp() == null) {
mostRecentPlayAction.put(key, action);
- } else if (mostRecent.getTimestamp().before(action.getTimestamp())) {
+ } else if (action.getTimestamp() != null && mostRecent.getTimestamp().before(action.getTimestamp())) {
mostRecentPlayAction.put(key, action);
+ } else {
+ Log.d(TAG, "No date information in action, skipping it");
}
}
break;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
index f6ea38ab5..80ba4fca7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java
@@ -19,7 +19,6 @@ import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.RemoteControlClient;
import android.media.RemoteControlClient.MetadataEditor;
-import android.os.AsyncTask;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
@@ -33,12 +32,9 @@ import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
-import com.bumptech.glide.request.animation.GlideAnimation;
-import com.bumptech.glide.request.target.SimpleTarget;
import org.apache.commons.lang3.StringUtils;
-import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -405,11 +401,21 @@ public class PlaybackService extends Service {
}
@Override
+ public void onSleepTimerAlmostExpired() {
+ mediaPlayer.setVolume(0.1f);
+ }
+
+ @Override
public void onSleepTimerExpired() {
mediaPlayer.pause(true, true);
+ mediaPlayer.setVolume(1.0f);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
}
+ @Override
+ public void onSleepTimerReset() {
+ mediaPlayer.setVolume(1.0f);
+ }
@Override
public void onWidgetUpdaterTick() {
@@ -652,10 +658,9 @@ public class PlaybackService extends Service {
}
}
- public void setSleepTimer(long waitingTime) {
- Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
- + " milliseconds");
- taskManager.setSleepTimer(waitingTime);
+ public void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
+ Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds");
+ taskManager.setSleepTimer(waitingTime, shakeToReset, vibrate);
sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
index 835a8c1d1..f1aa50c91 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java
@@ -619,6 +619,32 @@ public class PlaybackServiceMediaPlayer {
return retVal;
}
+ /**
+ * Sets the playback speed.
+ * This method is executed on an internal executor service.
+ */
+ public void setVolume(final float volume) {
+ executor.submit(new Runnable() {
+ @Override
+ public void run() {
+ setVolumeSync(volume);
+ }
+ });
+ }
+
+ /**
+ * Sets the playback speed.
+ * This method is executed on the caller's thread.
+ */
+ private void setVolumeSync(float volume) {
+ playerLock.lock();
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ mediaPlayer.setVolume(volume, volume);
+ Log.d(TAG, "Media player volume was set to " + volume);
+ }
+ playerLock.unlock();
+ }
+
public MediaType getCurrentMediaType() {
return mediaType;
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
index fc73c9446..4872dd7bd 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
+import android.os.Vibrator;
import android.util.Log;
import org.apache.commons.lang3.Validate;
@@ -14,12 +15,10 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.QueueEvent;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.playback.Playable;
-
import de.greenrobot.event.EventBus;
@@ -168,7 +167,7 @@ public class PlaybackServiceTaskManager {
public synchronized void cancelPositionSaver() {
if (isPositionSaverActive()) {
positionSaverFuture.cancel(false);
- if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver");
+ Log.d(TAG, "Cancelled PositionSaver");
}
}
@@ -186,9 +185,9 @@ public class PlaybackServiceTaskManager {
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
- if (BuildConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater");
+ Log.d(TAG, "Started WidgetUpdater");
} else {
- if (BuildConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored.");
+ Log.d(TAG, "Call to startWidgetUpdater was ignored.");
}
}
@@ -199,16 +198,14 @@ public class PlaybackServiceTaskManager {
*
* @throws java.lang.IllegalArgumentException if waitingTime <= 0
*/
- public synchronized void setSleepTimer(long waitingTime) {
+ public synchronized void setSleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
Validate.isTrue(waitingTime > 0, "Waiting time <= 0");
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
- + " milliseconds");
+ Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime) + " milliseconds");
if (isSleepTimerActive()) {
sleepTimerFuture.cancel(true);
}
- sleepTimer = new SleepTimer(waitingTime);
+ sleepTimer = new SleepTimer(waitingTime, shakeToReset, vibrate);
sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS);
}
@@ -216,7 +213,11 @@ public class PlaybackServiceTaskManager {
* Returns true if the sleep timer is currently active.
*/
public synchronized boolean isSleepTimerActive() {
- return sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture.isCancelled() && !sleepTimerFuture.isDone() && sleepTimer.isWaiting;
+ return sleepTimer != null
+ && sleepTimerFuture != null
+ && !sleepTimerFuture.isCancelled()
+ && !sleepTimerFuture.isDone()
+ && sleepTimer.getWaitingTime() > 0;
}
/**
@@ -224,8 +225,7 @@ public class PlaybackServiceTaskManager {
*/
public synchronized void disableSleepTimer() {
if (isSleepTimerActive()) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Disabling sleep timer");
+ Log.d(TAG, "Disabling sleep timer");
sleepTimerFuture.cancel(true);
}
}
@@ -255,7 +255,7 @@ public class PlaybackServiceTaskManager {
public synchronized void cancelWidgetUpdater() {
if (isWidgetUpdaterActive()) {
widgetUpdaterFuture.cancel(false);
- if (BuildConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater");
+ Log.d(TAG, "Cancelled WidgetUpdater");
}
}
@@ -284,16 +284,14 @@ public class PlaybackServiceTaskManager {
Runnable chapterLoader = new Runnable() {
@Override
public void run() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Chapter loader started");
+ Log.d(TAG, "Chapter loader started");
if (media.getChapters() == null) {
media.loadChapterMarks();
if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) {
callback.onChapterLoaded(media);
}
}
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Chapter loader stopped");
+ Log.d(TAG, "Chapter loader stopped");
}
};
chapterLoaderFuture = schedExecutor.submit(chapterLoader);
@@ -324,63 +322,86 @@ public class PlaybackServiceTaskManager {
/**
* Sleeps for a given time and then pauses playback.
*/
- private class SleepTimer implements Runnable {
+ protected class SleepTimer implements Runnable {
private static final String TAG = "SleepTimer";
- private static final long UPDATE_INTERVALL = 1000L;
- private volatile long waitingTime;
- private volatile boolean isWaiting;
-
- public SleepTimer(long waitingTime) {
+ private static final long UPDATE_INTERVAL = 1000L;
+ private static final long NOTIFICATION_THRESHOLD = 10000;
+ private long waitingTime;
+ private final boolean shakeToReset;
+ private final boolean vibrate;
+ private ShakeListener shakeListener;
+
+ public SleepTimer(long waitingTime, boolean shakeToReset, boolean vibrate) {
super();
this.waitingTime = waitingTime;
- isWaiting = true;
+ this.shakeToReset = shakeToReset;
+ this.vibrate = vibrate;
}
@Override
public void run() {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Starting");
+ Log.d(TAG, "Starting");
+ boolean notifiedAlmostExpired = false;
+ long lastTick = System.currentTimeMillis();
while (waitingTime > 0) {
try {
- Thread.sleep(UPDATE_INTERVALL);
- waitingTime -= UPDATE_INTERVALL;
-
+ Thread.sleep(UPDATE_INTERVAL);
+ long now = System.currentTimeMillis();
+ waitingTime -= now - lastTick;
+ lastTick = now;
+
+ if(waitingTime < NOTIFICATION_THRESHOLD && !notifiedAlmostExpired) {
+ Log.d(TAG, "Sleep timer is about to expire");
+ if(vibrate) {
+ Vibrator v = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+ if(v != null) {
+ v.vibrate(500);
+ }
+ }
+ if(shakeListener == null && shakeToReset) {
+ shakeListener = new ShakeListener(context, this);
+ }
+ callback.onSleepTimerAlmostExpired();
+ notifiedAlmostExpired = true;
+ }
if (waitingTime <= 0) {
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Waiting completed");
- postExecute();
+ Log.d(TAG, "Sleep timer expired");
+ shakeListener.pause();
+ shakeListener = null;
if (!Thread.currentThread().isInterrupted()) {
callback.onSleepTimerExpired();
}
-
}
} catch (InterruptedException e) {
Log.d(TAG, "Thread was interrupted while waiting");
+ e.printStackTrace();
break;
}
}
- postExecute();
- }
-
- protected void postExecute() {
- isWaiting = false;
}
public long getWaitingTime() {
return waitingTime;
}
- public boolean isWaiting() {
- return isWaiting;
+ public void onShake() {
+ setSleepTimer(15 * 60 * 1000, shakeToReset, vibrate);
+ callback.onSleepTimerReset();
+ shakeListener.pause();
+ shakeListener = null;
}
}
- public static interface PSTMCallback {
+ public interface PSTMCallback {
void positionSaverTick();
+ void onSleepTimerAlmostExpired();
+
void onSleepTimerExpired();
+ void onSleepTimerReset();
+
void onWidgetUpdaterTick();
void onChapterLoaded(Playable media);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java
new file mode 100644
index 000000000..446061ea6
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ShakeListener.java
@@ -0,0 +1,65 @@
+package de.danoeh.antennapod.core.service.playback;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.util.FloatMath;
+import android.util.Log;
+
+public class ShakeListener implements SensorEventListener
+{
+ private static final String TAG = ShakeListener.class.getSimpleName();
+
+ private Sensor mAccelerometer;
+ private SensorManager mSensorMgr;
+ private PlaybackServiceTaskManager.SleepTimer mSleepTimer;
+ private Context mContext;
+
+ public ShakeListener(Context context, PlaybackServiceTaskManager.SleepTimer sleepTimer) {
+ mContext = context;
+ mSleepTimer = sleepTimer;
+ resume();
+ }
+
+ public void resume() {
+ // only a precaution, the user should actually not be able to activate shake to reset
+ // when the accelerometer is not available
+ mSensorMgr = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ if (mSensorMgr == null) {
+ throw new UnsupportedOperationException("Sensors not supported");
+ }
+ mAccelerometer = mSensorMgr.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (!mSensorMgr.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_UI)) { // if not supported
+ mSensorMgr.unregisterListener(this);
+ throw new UnsupportedOperationException("Accelerometer not supported");
+ }
+ }
+
+ public void pause() {
+ if (mSensorMgr != null) {
+ mSensorMgr.unregisterListener(this);
+ mSensorMgr = null;
+ }
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ float gX = event.values[0] / SensorManager.GRAVITY_EARTH;
+ float gY = event.values[1] / SensorManager.GRAVITY_EARTH;
+ float gZ = event.values[2] / SensorManager.GRAVITY_EARTH;
+
+ float gForce = FloatMath.sqrt(gX*gX + gY*gY + gZ*gZ);
+ if (gForce > 2.25f) {
+ Log.d(TAG, "Detected shake " + gForce);
+ mSleepTimer.onShake();
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ return;
+ }
+
+} \ No newline at end of file
diff --git a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
index 31eb2efd6..25a57f71b 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/syndication/namespace/NSRSS20.java
@@ -119,13 +119,14 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setItemIdentifier(content);
}
} else if (top.equals(TITLE)) {
+ String title = content.trim();
if (second.equals(ITEM)) {
- state.getCurrentItem().setTitle(content);
+ state.getCurrentItem().setTitle(title);
} else if (second.equals(CHANNEL)) {
- state.getFeed().setTitle(content);
+ state.getFeed().setTitle(title);
} else if (second.equals(IMAGE) && third != null
&& third.equals(CHANNEL)) {
- state.getFeed().getImage().setTitle(content);
+ state.getFeed().getImage().setTitle(title);
}
} else if (top.equals(LINK)) {
if (second.equals(CHANNEL)) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
index ba5428b81..42aa3b713 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java
@@ -652,9 +652,9 @@ public abstract class PlaybackController {
}
}
- public void setSleepTimer(long time) {
+ public void setSleepTimer(long time, boolean shakeToReset, boolean vibrate) {
if (playbackService != null) {
- playbackService.setSleepTimer(time);
+ playbackService.setSleepTimer(time, shakeToReset, vibrate);
}
}
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index c8fb83e46..f5ba1b636 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -385,6 +385,9 @@
<string name="sleep_timer_label">Sleep timer</string>
<string name="time_left_label">Time left:\u0020</string>
<string name="time_dialog_invalid_input">Invalid input, time has to be an integer</string>
+ <string name="timer_about_to_expire_label"><b>When timer is about to expire:</b></string>
+ <string name="shake_to_reset_label">Shake to reset timer</string>
+ <string name="timer_vibration_label">Vibrate</string>
<string name="time_seconds">seconds</string>
<string name="time_minutes">minutes</string>
<string name="time_hours">hours</string>